是指做爲單個邏輯工做單元執行的一系列操做,要麼徹底地執行,要麼徹底地不執行。 事務處理能夠確保除非事務性單元內的全部操做都成功完成,不然不會永久更新面向數據的資源。經過將一組相關操做組合爲一個要麼所有成功要麼所有失敗的單元,能夠簡化錯誤恢復並使應用程序更加可靠。一個邏輯工做單元要成爲事務,必須知足所謂的ACID(原子性、一致性、隔離性和持久性)屬性。事務是數據庫運行中的一個邏輯工做單位,由DBMS中的事務管理子系統負責事務的處理。java
舉個例子加深一下理解:同一個銀行轉帳,A轉1000塊錢給B,這裏存在兩個操做,一個是A帳戶扣款1000元,兩一個操做是B帳戶增長1000元,二者就構成了轉帳這個事務。python
兩個操做都成功,A帳戶扣款1000元,B帳戶增長1000元,事務成功
兩個操做都失敗,A帳戶和B帳戶金額都沒變,事務失敗mysql
最後思考一下,怎麼樣會出現A帳戶扣款1000元,B帳戶金額不變?若是你是把兩個操做放在一個事務裏面,而且是數據庫提供的內在事務支持,那就不會有問題,可是開發人員把兩個操做放在兩個事務裏面,而第二個事務失敗就會出現中間狀態。現實中本身實現的分佈式事務處理不當也會出現中間狀態,這並非事務的錯,事務自己就是規定不會出現中間狀態,是事務實現者作出來的方案有問題。sql
原子性(Atomic):事務必須是原子工做單元;對於其數據修改,要麼全都執行,要麼全都不執行。一般,與某個事務關聯的操做具備共同的目標,而且是相互依賴的。若是系統只執行這些操做的一個子集,則可能會破壞事務的整體目標。原子性消除了系統處理操做子集的可能性。數據庫
一致性(Consistency):事務的一致性指的是在一個事務執行以前和執行以後數據庫都必須處於一致性狀態。這種特性稱爲事務的一致性。假如數據庫的狀態知足全部的完整性約束,就說該數據庫是一致的。編程
隔離性(Isolation):由併發事務所做的修改必須與任何其它併發事務所做的修改隔離。事務查看數據時數據所處的狀態,究竟是另外一個事務執行以前的狀態仍是中間某個狀態,相互之間存在什麼影響,是能夠經過隔離級別的設置來控制的。安全
持久性(Durability):事務結束後,事務處理的結果必須可以獲得固化,即寫入數據庫文件中即便機器宕機數據也不會丟失,它對於系統的影響是永久性的。服務器
咱們從另一個方向來講說,若是不對事務進行併發控制,咱們看看數據庫併發操做是會有那些異常情形,有些使咱們能夠接受的,有些是不能接受的,注意這裏的異常就是特定語境下的,並不必定就是錯誤什麼的。假設有一個order表,有個字段叫count,做爲計數用,當前值爲100session
第一類丟失更新(Update Lost):此種更新丟失是由於回滾的緣由,因此也叫回滾丟失。此時兩個事務同時更新count,兩個事務都讀取到100,事務一更新成功並提交,count=100+1=101,事務二出於某種緣由更新失敗了,而後回滾,事務二就把count還原爲它一開始讀到的100,此時事務一的更新就這樣丟失了。多線程
髒讀(Dirty Read):此種異常時由於一個事務讀取了另外一個事務修改了可是未提交的數據。舉個例子,事務一更新了count=101,可是沒有提交,事務二此時讀取count,值爲101而不是100,而後事務一出於某種緣由回滾了,而後第二個事務讀取的這個值就是噩夢的開始。
不可重複讀(Not Repeatable Read):此種異常是一個事務對同一行數據執行了兩次或更屢次查詢,可是卻獲得了不一樣的結果,也就是在一個事務裏面你不能重複(即屢次)讀取一行數據,若是你這麼作了,不能保證每次讀取的結果是同樣的,有可能同樣有可能不同。形成這個結果是在兩次查詢之間有別的事務對該行數據作了更新操做。舉個例子,事務一先查詢了count,值爲100,此時事務二更新了count=101,事務一再次讀取count,值就會變成101,兩次讀取結果不同。
第二類丟失更新(Second Update Lost):此種更新丟失是由於更新被其餘事務給覆蓋了,也能夠叫覆蓋丟失。舉個例子,兩個事務同時更新count,都讀取100這個初始值,事務一先更新成功並提交,count=100+1=101,事務二後更新成功並提交,count=100+1=101,因爲事務二count仍是從100開始增長,事務一的更新就這樣丟失了。
幻讀(Phantom Read):幻讀和不可重複讀有點像,只是針對的不是數據的值而是數據的數量。此種異常是一個事務在兩次查詢的過程當中數據的數量不一樣,讓人覺得發生幻覺,幻讀大概就是這麼得來的吧。舉個例子,事務一查詢order表有多少條記錄,事務二新增了一條記錄,而後事務一查了一下order表有多少記錄,發現和第一次不同,這就是幻讀。
看到上面提到的幾種問題,你可能會想,我擦,這麼多坑怎麼辦啊。其實上面幾種狀況並非必定都要避免的,具體看你的業務要求,包括你數據庫的負載都會影響你的決定。不知道你們發現沒有,上面各類異常狀況都是多個事務之間相互影響形成的,這說明兩個事務之間須要某種方式將他們從某種程度上分開,下降直至避免相互影響。這時候數據庫事務隔離級別就粉墨登場了,而數據庫的隔離級別實現通常是經過數據庫鎖實現的。
讀未提交(Read Uncommitted):該隔離級別指即便一個事務的更新語句沒有提交,可是別的事務能夠讀到這個改變,幾種異常狀況均可能出現。極易出錯,沒有安全性可言,基本不會使用。
讀已提交(Read Committed):該隔離級別指一個事務只能看到其餘事務的已經提交的更新,看不到未提交的更新,消除了髒讀和第一類丟失更新,這是大多數數據庫的默認隔離級別,如Oracle,Sqlserver。
可重複讀(Repeatable Read):該隔離級別指一個事務中進行兩次或屢次一樣的對於數據內容的查詢,獲得的結果是同樣的,但不保證對於數據條數的查詢是同樣的,只要存在讀改行數據就禁止寫,消除了不可重複讀和第二類更新丟失,這是Mysql數據庫的默認隔離級別。
串行化(Serializable):意思是說這個事務執行的時候不容許別的事務併發執行.徹底串行化的讀,只要存在讀就禁止寫,但能夠同時讀,消除了幻讀。這是事務隔離的最高級別,雖然最安全最省心,可是效率過低,通常不會用。
下面是各類隔離級別對各異常的控制能力:
通常能夠分爲兩類,一個是悲觀鎖,一個是樂觀鎖,悲觀鎖通常就是咱們一般說的數據庫鎖機制,樂觀鎖通常是指用戶本身實現的一種鎖機制,編程語言也有樂觀鎖的思想的應用。
悲觀鎖:顧名思義,就是很悲觀,它對於數據被外界修改持保守態度,認爲數據隨時會修改,因此整個數據處理中須要將數據加鎖。悲觀鎖通常都是依靠關係數據庫提供的鎖機制,事實上關係數據庫中的行鎖,表鎖不管是讀寫鎖都是悲觀鎖。
悲觀鎖按照使用性質劃分:
共享鎖(Share locks簡記爲S鎖):也稱讀鎖,事務A對對象T加s鎖,其餘事務也只能對T加S,多個事務能夠同時讀,但不能有寫操做,直到A釋放S鎖。
排它鎖(Exclusivelocks簡記爲X鎖):也稱寫鎖,事務A對對象T加X鎖之後,其餘事務不能對T加任何鎖,只有事務A能夠讀寫對象T直到A釋放X鎖。
更新鎖(簡記爲U鎖):用來預約要對此對象施加X鎖,它容許其餘事務讀,但不容許再施加U鎖或X鎖;當被讀取的對象將要被更新時,則升級爲X鎖,主要是用來防止死鎖的。由於使用共享鎖時,修改數據的操做分爲兩步,首先得到一個共享鎖,讀取數據,而後將共享鎖升級爲排它鎖,而後再執行修改操做。這樣若是同時有兩個或多個事務同時對一個對象申請了共享鎖,在修改數據的時候,這些事務都要將共享鎖升級爲排它鎖。這些事務都不會釋放共享鎖而是一直等待對方釋放,這樣就形成了死鎖。若是一個數據在修改前直接申請更新鎖,在數據修改的時候再升級爲排它鎖,就能夠避免死鎖。
悲觀鎖按照做用範圍劃分:
行鎖:鎖的做用範圍是行級別,數據庫可以肯定那些行須要鎖的狀況下使用行鎖,若是不知道會影響哪些行的時候就會使用表鎖。舉個例子,一個用戶表user,有主鍵id和用戶生日birthday當你使用update … where id=?這樣的語句數據庫明確知道會影響哪一行,它就會使用行鎖,當你使用update … where birthday=?這樣的的語句的時候由於事先不知道會影響哪些行就可能會使用表鎖。
表鎖:鎖的做用範圍是整張表。
樂觀鎖:顧名思義,就是很樂觀,每次本身操做數據的時候認爲沒有人回來修改它,因此不去加鎖,可是在更新的時候會去判斷在此期間數據有沒有被修改,須要用戶本身去實現。既然都有數據庫提供的悲觀鎖能夠方便使用爲何要使用樂觀鎖呢?對於讀操做遠多於寫操做的時候,大多數都是讀取,這時候一個更新操做加鎖會阻塞全部讀取,下降了吞吐量。最後還要釋放鎖,鎖是須要一些開銷的,咱們只要想辦法解決極少許的更新操做的同步問題。換句話說,若是是讀寫比例差距不是很是大或者你的系統沒有響應不及時,吞吐量瓶頸問題,那就不要去使用樂觀鎖,它增長了複雜度,也帶來了額外的風險。
樂觀鎖實現方式:
版本號(記爲version):就是給數據增長一個版本標識,在數據庫上就是表中增長一個version字段,每次更新把這個字段加1,讀取數據的時候把version讀出來,更新的時候比較version,若是仍是開始讀取的version就能夠更新了,若是如今的version比老的version大,說明有其餘事務更新了該數據,並增長了版本號,這時候獲得一個沒法更新的通知,用戶自行根據這個通知來決定怎麼處理,好比從新開始一遍。這裏的關鍵是判斷version和更新兩個動做須要做爲一個原子單元執行,不然在你判斷能夠更新之後正式更新以前有別的事務修改了version,這個時候你再去更新就可能會覆蓋前一個事務作的更新,形成第二類丟失更新,因此你可使用update … where … and version="old version"這樣的語句,根據返回結果是0仍是非0來獲得通知,若是是0說明更新沒有成功,由於version被改了,若是返回非0說明更新成功。
時間戳(timestamp):和版本號基本同樣,只是經過時間戳來判斷而已,注意時間戳要使用數據庫服務器的時間戳不能是業務系統的時間。
待更新字段:和版本號方式類似,只是不增長額外字段,直接使用有效數據字段作版本控制信息,由於有時候咱們可能沒法改變舊系統的數據庫表結構。假設有個待更新字段叫count,先去讀取這個count,更新的時候去比較數據庫中count的值是否是我指望的值(即開始讀的值),若是是就把我修改的count的值更新到該字段,不然更新失敗。java的基本類型的原子類型對象如AtomicInteger就是這種思想。
全部字段:和待更新字段相似,只是使用全部字段作版本控制信息,只有全部字段都沒變化纔會執行更新。
樂觀鎖幾種方式的區別:
新系統設計可使用version方式和timestamp方式,須要增長字段,應用範圍是整條數據,不論那個字段修改都會更新version,也就是說兩個事務更新同一條記錄的兩個不相關字段也是互斥的,不能同步進行。舊系統不能修改數據庫表結構的時候使用數據字段做爲版本控制信息,不須要新增字段,待更新字段方式只要其餘事務修改的字段和當前事務修改的字段沒有重疊就能夠同步進行,併發性更高。
實踐是檢驗真理的惟一標準,掌握上面的理論以後,咱們在數據庫上實戰一番家裏更好地掌握也加深理解,同時有助於解決實際問題。不一樣數據庫不少實現可能不一樣,這裏以mysql爲例講解各類隔離級別下的狀況,測試表爲
隔離級別:read-uncommitted
髒讀測試流程:
一、A設置隔離級別爲read-uncommitted(注意這裏未聲明都是session級別,而非全局的),開啓事務,查詢id=1的記錄
二、B設置隔離級別爲read-uncommitted,開啓事務,修改id=1的記錄,但不提交
三、A再次查詢id=1的記錄,和第一次查詢的比較一下
四、B事務回滾,A事務回滾。
A:
B:
結論:A讀到了B沒有提交的內容,隔離級別爲read-uncommitted的時候出現髒讀。
第一類更新丟失測試流程:
一、A設置隔離級別爲read-uncommitted,開啓事務,查詢id=1的記錄
二、B設置隔離級別爲read-uncommitted,開啓事務,查詢id=1的記錄
三、A修改id=1的記錄
四、B修改id=1的記錄
五、A提交
六、B回滾
七、A在查詢一次id=1的記錄,看看本身的修改是否成功
結論:結果不如我所想的,A的更新成功了,爲何呢?A執行update語句的時候對該條記錄加鎖了,B這時候根本沒法修改直至超時,也就是至少在mysql中在read-uncommitted隔離級別下驗證第一類丟失更新,據瞭解有的數據庫好像能夠設置不加鎖,若是可以不加鎖的話則能夠實現,也貼一下圖吧。
A:
B:
不可重複讀測試流程(省略):
結論:流程和測試髒讀同樣,其實在第一次測試髒讀的時候就能夠發現會出現不可重複讀,A兩次讀取id=1的數據內容不一樣。
第二類丟失更新流程:
一、A開啓事務,查詢order_id=1的記錄
二、B開啓事務,查詢order_id=1的記錄
三、A把查出來的count加1後更新
四、B把查出來的count加1更新
五、A提交,B也提交
A:
B:
結論:A的更新丟失,咱們但願的結果是3,而實際結果是2,跟java的多線程很像對不對,read-uncommitted隔離模式下會出現第二類丟失更新。
幻讀測試流程:
一、A開啓事務,查詢user表全部數據
二、B開啓事務,新增一條記錄
三、A再次查詢user表全部記錄,和第一次做比對
四、A回滾,B回滾
A:
B:
結論:A兩次查詢全表數據結果不一樣,read-uncommitted隔離模式下會出現幻讀。
注:由於後面對這幾種異常狀況的測試流程基本和上面同樣,個別有些差異讀者本身注意,另外注意更改隔離級別便可,就能看到對應結果,後面的我只給出進一步能解決的異常測試截圖,結論能夠參照前面的對照表。
隔離級別:read-committed
髒讀測試截圖
A:
B:
結論:A沒有讀到B沒有提交的內容,隔離級別爲read-committed的時候不會出現髒讀。
隔離級別:repeatable-read
不可重複讀測試截圖
A:
B:
結論:A兩次讀取id=1的數據內容相同,repeatable-read隔離模式下不會出現不可重複讀。
隔離級別:Serializable
幻讀測試截圖
A:
B:
結論:由於A事務未提交以前,B事務插入操做沒法得到鎖而超時,Serializable隔離模式下不會出現幻讀。
做者:阿駱麥迪
連接:https://www.jianshu.com/p/eb41df600775
-END-