ACID
一個數(shù)據(jù)庫事務(wù)通常包含了一個序列的對數(shù)據(jù)庫的讀/寫操作。它的存在包含有以下兩個目的:
- 為數(shù)據(jù)庫操作序列提供了一個從失敗中恢復(fù)到正常狀態(tài)的方法氮发,同時提供了數(shù)據(jù)庫即使在異常狀態(tài)下仍能保持一致性的方法渴肉。
- 當(dāng)多個應(yīng)用程序在并發(fā)訪問數(shù)據(jù)庫時,可以在這些應(yīng)用程序之間提供一個隔離方法爽冕,以防止彼此的操作互相干擾仇祭。
上面對數(shù)據(jù)庫事務(wù)的定義摘自維基百科。先不用著急的去理解這個定義的具體含義颈畸,我們從事務(wù)的四個特性來逐步了解什么是事務(wù)乌奇。
數(shù)據(jù)庫事務(wù)擁有以下四個特性没讲,習(xí)慣上被稱之為ACID特性。
原子性(Atomicity):事務(wù)作為一個整體被執(zhí)行礁苗,包含在其中的對數(shù)據(jù)庫的操作要么全部被執(zhí)行爬凑,要么都不執(zhí)行。
一致性(Consistency):事務(wù)應(yīng)確保數(shù)據(jù)庫的狀態(tài)從一個一致狀態(tài)轉(zhuǎn)變?yōu)榱硪粋€一致狀態(tài)试伙。一致狀態(tài)的含義是數(shù)據(jù)庫中的數(shù)據(jù)應(yīng)滿足完整性約束嘁信。
隔離性(Isolation):多個事務(wù)并發(fā)執(zhí)行時,一個事務(wù)的執(zhí)行不應(yīng)影響其他事務(wù)的執(zhí)行疏叨。
持久性(Durability):已被提交的事務(wù)對數(shù)據(jù)庫的修改應(yīng)該永久保存在數(shù)據(jù)庫中潘靖。
用一個轉(zhuǎn)賬的例子來解釋,這個例子被用爛了蚤蔓,卻很經(jīng)典:
從A賬戶向B賬戶轉(zhuǎn)賬100元卦溢,可能分為以下幾個步驟:
讀取A賬戶,將A賬戶余額減100
A 賬戶余額寫回數(shù)據(jù)庫
讀取B賬戶秀又,將B賬戶余額加100
B賬戶余額寫回數(shù)據(jù)庫
-
一致性
什么叫數(shù)據(jù)一致性既绕?通常是由我們自己來定義的。在上面的場景中涮坐,就是在轉(zhuǎn)賬的前后,A賬戶和B賬戶的總額保持不變誓军。
再舉一個例子袱讹,之前做過一個版本管理系統(tǒng),用戶發(fā)布一個的版本(上傳若干附件)昵时,版本記錄表就要插入一行記錄捷雕,相對應(yīng)的,附件表也要插入若干的記錄(一對多的關(guān)系)壹甥。對于這兩個表的操作救巷,要么全做,要么不做句柠,如果附件表插入了記錄浦译,而版本記錄表沒有操作,不符合一致性定義溯职;如果版本記錄表插入了一條記錄精盅,而附件表沒有插入記錄或者插入記錄少了幾條,也不符合一致性的定義谜酒。想要保持一致性叹俏,那么需要對這兩個表的插入操作都放在同一個事務(wù)內(nèi)進(jìn)行。
在事務(wù)處理的ACID屬性中僻族,一致性是最基本的屬性粘驰,其它的三個屬性都為了保證一致性而存在的屡谐。
-
原子性
上面的轉(zhuǎn)賬的四個步驟要么全做,要么不做蝌数。假如做完第一步之后愕掏,計算機(jī)突然斷電了,那么數(shù)據(jù)庫重啟之后就需要執(zhí)行一個crash
recovery的過程籽前,之前的所有操作都應(yīng)該回滾到執(zhí)行事務(wù)之前的狀態(tài)亭珍。即A向B轉(zhuǎn)賬的操作失敗了。上面提到枝哄,其他三個屬性都是為了保持一致性而存在肄梨。只要原子性是否就可以保證一致性?答案當(dāng)然是否定的挠锥。
比如众羡,事務(wù)1 A向B轉(zhuǎn)賬100元,在第一步執(zhí)行完畢之后蓖租,恰好另外一個事務(wù)2操作是C向A轉(zhuǎn)賬200元粱侣,并且已經(jīng)執(zhí)行完畢,此時執(zhí)行事務(wù)1的第二步蓖宦,將A賬戶余額寫回數(shù)據(jù)庫齐婴,此時事務(wù)2的執(zhí)行結(jié)果就被事務(wù)1覆蓋掉了,造成了數(shù)據(jù)的不一致(A + B + C 的賬戶總額保持一致)稠茂。
可見柠偶,即使事務(wù)1最終執(zhí)行完畢,滿足了原子性睬关,因為另一個事務(wù)的影響诱担,還是造成了數(shù)據(jù)的不一致狀態(tài)。原子性并不能保證一致性电爹。
那么蔫仙,為什么會看到網(wǎng)上還有許多人再問原子性和一致性的問題呢?
我認(rèn)為是程序員很容易從數(shù)據(jù)庫事務(wù)原子性聯(lián)想到做應(yīng)用時多線程并發(fā)時的原子性丐箩。多線程并發(fā)時的原子性基本靠鎖來維持摇邦,我們認(rèn)為,有了鎖的保護(hù)雏蛮,臨界區(qū)的資源就不可以被另一個線程訪問了涎嚼。事實上,數(shù)據(jù)庫事務(wù)原子性與鎖關(guān)系不大挑秉,鎖涉及到了事務(wù)的另一個特性:隔離性法梯。
-
隔離性
就像在上面談到的,事務(wù)1 與事務(wù)2 并行發(fā)生,造成了數(shù)據(jù)的不一致狀態(tài)立哑。隔離性用來解決這個問題夜惭。
事務(wù)隔離性可以保證:如果在A給B轉(zhuǎn)賬的同時,有另外一個事務(wù)執(zhí)行了C給B轉(zhuǎn)賬的操作铛绰,那么當(dāng)兩個事務(wù)都結(jié)束的時候诈茧,B賬戶里面的錢應(yīng)該是A轉(zhuǎn)給B的錢加上C轉(zhuǎn)給B的錢再加上自己原有的錢。
-
持久性
持久性比較容易理解捂掰。即敢会,一旦事務(wù)提交(轉(zhuǎn)賬成功),所有的數(shù)據(jù)都會被寫入數(shù)據(jù)庫这嚣,落地到磁盤鸥昏。賬戶中的錢就真的發(fā)生了變化。
事務(wù)隔離性
鎖
從上文可以看出姐帚,當(dāng)并發(fā)事務(wù)同時訪問一個資源時吏垮,有可能導(dǎo)致數(shù)據(jù)不一致,因此需要一種機(jī)制來將數(shù)據(jù)訪問順序化罐旗,以保證數(shù)據(jù)庫數(shù)據(jù)的一致性膳汪。鎖就是其中的一種機(jī)制。我們通過使用鎖來保證事務(wù)隔離性九秀。
為了理解下面提到的隔離級別遗嗽,我們簡單認(rèn)識一下數(shù)據(jù)庫中的幾種鎖:
-
從鎖粒度劃分
表級鎖:開銷小,加鎖快鼓蜒;不會出現(xiàn)死鎖媳谁;鎖定粒度大,發(fā)生鎖沖突的概率最高友酱,并發(fā)度最低。
行級鎖:開銷大柔纵,加鎖慢缔杉;會出現(xiàn)死鎖;鎖定粒度最小搁料,發(fā)生鎖沖突的概率最低或详,并發(fā)度也最高。
頁面鎖:開銷和加鎖時間界于表鎖和行鎖之間郭计;會出現(xiàn)死鎖霸琴;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般昭伸。
-
從鎖性質(zhì)劃分
讀鎖(S 鎖):如果事務(wù)A對數(shù)據(jù)T加了該鎖之后梧乘,其他事務(wù)可以并發(fā)讀取T(獲取該數(shù)據(jù)的讀鎖),但任何事務(wù)都不能對數(shù)據(jù)T進(jìn)行修改(獲取數(shù)據(jù)上的寫鎖),直到已釋放所有讀鎖选调。
寫鎖(X 鎖):如果事務(wù)A對數(shù)據(jù)T加上寫鎖后夹供,則其他事務(wù)不能再對T加任任何類型的鎖。獲得寫鎖的事務(wù)既能讀數(shù)據(jù)仁堪,又能修改數(shù)據(jù)哮洽。
意向鎖: 設(shè)計目的主要是為了在一個事務(wù)中揭示下一行將要被請求鎖的類型。參考
更新鎖:引入它是因為多數(shù)數(shù)據(jù)庫在實現(xiàn)加X鎖時是執(zhí)行了如下流程:先加S鎖弦聂,添加成功后嘗試更換為X鎖鸟辅。這時如果有兩個事務(wù)同時加了S鎖,嘗試換X鎖莺葫,就會發(fā)生死鎖匪凉。因此增加U鎖,U鎖代表有更新意向徙融,只允許有一個事務(wù)拿到U鎖洒缀,該事務(wù)在發(fā)生寫后U鎖變X鎖,未寫時看做S鎖欺冀。
隔離級別
我們知道了并發(fā)事務(wù)的隔離性靠鎖機(jī)制來實現(xiàn)树绩,很多DBMS定義了多個不同的"事務(wù)隔離等級"來控制鎖的程度和并發(fā)能力。
SQL定義的標(biāo)準(zhǔn)隔離級別有四種隐轩,從高到底依次為:
可序列化(Serializable)
可重復(fù)讀(Repeatable reads)
提交讀(Read committed)
未提交讀(Read uncommitted)
隨著數(shù)據(jù)庫隔離級別的提高饺饭,數(shù)據(jù)的并發(fā)能力也會有所下降。
下面了解一下這幾種隔離級別在數(shù)據(jù)庫中可能的實現(xiàn)职车。注意瘫俊,下面的實現(xiàn)都是基于傳統(tǒng)數(shù)據(jù)庫,而不是MVCC的悴灵。
未提交讀
-
鎖機(jī)制:
事務(wù)在讀數(shù)據(jù)的時候并未對數(shù)據(jù)加鎖扛芽;
事務(wù)在修改數(shù)據(jù)的時候?qū)?shù)據(jù)增加行級S鎖。
舉例:
Transaction 1 | Transaction 2 |
---|---|
select * from users where id = 1 // will read 20 | |
update users set age = 21 where id = 1 | |
select age from users where is = 1 // will read 21 | |
roll back |
事務(wù)一在讀取某行數(shù)據(jù)的時候并未加任何鎖积瞒,事務(wù)二也能對這行數(shù)據(jù)進(jìn)行讀取和更新川尖;
事務(wù)二在更新某行數(shù)據(jù)的時候?qū)@行數(shù)據(jù)加了S鎖,事務(wù)一可以對這行數(shù)據(jù)進(jìn)行讀取茫孔,因此看到了事務(wù)二未提交的更改;
事務(wù)二更新某行數(shù)據(jù)對這行數(shù)據(jù)加了S鎖缰贝,事務(wù)一不能對這行數(shù)據(jù)進(jìn)行更新馍悟,直到事務(wù)二結(jié)束。
可以看到剩晴,事務(wù)一第二次查詢看到了事務(wù)二未提交的更改,之后這些數(shù)據(jù)被事務(wù)二進(jìn)行了回滾毛嫉,于是事務(wù)一查詢到的數(shù)據(jù)就成了臟數(shù)據(jù)承粤,這種現(xiàn)象稱之為臟讀彻舰。
未提交讀會造成臟讀。
提交讀
-
鎖機(jī)制
事務(wù)對當(dāng)前被讀取的數(shù)據(jù)加行級S鎖(讀到時才加),一旦讀完該行就釋放S鎖唯卖;
事務(wù)在更新某數(shù)據(jù)時昧廷,必須先對其加行級X鎖淹办,直到事務(wù)結(jié)束才釋放姥宝。
舉例
Transaction 1 | Transaction 2 |
---|---|
select * from users where id =1 //will read 20 | |
update users set age = 21 where id =1; commit | |
select * from users where id = 1 //will read 21 |
事務(wù)二在更新數(shù)據(jù)的時候?qū)?shù)據(jù)加了X鎖,直到事務(wù)結(jié)束才釋放。所以事務(wù)一讀取不到事務(wù)二未提交的數(shù)據(jù)穷缤。
事務(wù)二結(jié)束后事務(wù)一讀取到了與第一次讀取中不一致的數(shù)據(jù)。造成了事務(wù)一中兩次讀取的結(jié)果不一致,產(chǎn)生了不可重復(fù)讀問題笔呀。
可重復(fù)讀
-
鎖機(jī)制
事務(wù)對當(dāng)前被讀取的數(shù)據(jù)加行級S鎖许师,直到事務(wù)結(jié)束才釋放搭幻;
事務(wù)在更新某數(shù)據(jù)時咧擂,對其加行級X鎖,直到事務(wù)結(jié)束才釋放檀蹋。
舉例
Transaction 1 | Transaction 2 |
---|---|
select * from users where id =1; commit | |
update users set age = 21 where id =1; commit |
事務(wù)一在讀取數(shù)據(jù)時松申,對數(shù)據(jù)加了S鎖,直到事務(wù)結(jié)束才釋放续扔。因此在此期間攻臀,事務(wù)二只能讀取該數(shù)據(jù),不能更新纱昧。這樣保證了事務(wù)一在整個事務(wù)期間刨啸,無論讀取多少次該數(shù)據(jù),結(jié)果都是一致的识脆,解決了不可重復(fù)讀的問題设联。
事務(wù)二在更新數(shù)據(jù)時對數(shù)據(jù)加了X鎖,直到事務(wù)結(jié)束才釋放灼捂,在此期間事務(wù)一都無法訪問和更新該數(shù)據(jù)离例,解決了臟讀的問題。
Transaction 1 | Transaction 2 |
---|---|
select * from users where age between 20 and 30 | |
insert into users values(3, 'bob', 25); commit | |
select * from users where age between 20 and 30 |
上面的例子中:
事務(wù)一查詢年齡20到30之間的用戶悉稠,假設(shè)取到10條數(shù)據(jù)宫蛆,那么對這10條數(shù)據(jù)加上了行級S鎖;
事務(wù)二插入一條數(shù)據(jù)的猛。由于此時沒有任何事務(wù)對表添加了表級鎖耀盗,因此順利插入;
事務(wù)一再一次查詢年齡20到30之間的用戶卦尊,發(fā)現(xiàn)與第一次讀取時的數(shù)據(jù)不一致了叛拷,多出了一條數(shù)據(jù)。
這種現(xiàn)象就是幻讀岂却。這是一種特殊的不可重復(fù)讀現(xiàn)象忿薇。
可序列化
-
鎖機(jī)制
事務(wù)對當(dāng)前被讀取的數(shù)據(jù)加表級S鎖,直到事務(wù)結(jié)束才釋放躏哩;
事務(wù)在更新某數(shù)據(jù)時署浩,對其加表級X鎖,直到事務(wù)結(jié)束才釋放扫尺。
事務(wù)一在讀取表記錄時筋栋,事務(wù)二也可以讀取該表,但不能對表進(jìn)行更新器联、刪除二汛、插入等操作;
事務(wù)一在更新表記錄時拨拓,事務(wù)二不能夠讀取該表的任何記錄肴颊,也不能對表進(jìn)行更新操作。
可序列化隔離級別避免了臟讀渣磷、不可重復(fù)讀和幻讀婿着,是最高的隔離級別。