什么是事務(wù)?
事務(wù)是邏輯上的一組操作袒哥,要么都執(zhí)行走诞,要么都不執(zhí)行会油。
事務(wù)最經(jīng)典也經(jīng)常被拿出來(lái)說例子就是轉(zhuǎn)賬了妥曲。
假如小明要給小紅轉(zhuǎn)賬1000元,這個(gè)轉(zhuǎn)賬會(huì)涉及到兩個(gè)關(guān)鍵操作就是:將小明的余額減少1000元钦购,將小紅的余額增加1000元檐盟。萬(wàn)一在這兩個(gè)操作之間突然出現(xiàn)錯(cuò)誤比如銀行系統(tǒng)崩潰,導(dǎo)致小明余額減少而小紅的余額沒有增加押桃,這樣就不對(duì)了葵萎。事務(wù)就是保證這兩個(gè)關(guān)鍵操作要么都成功,要么都要失敗唱凯。
事物的特性(ACID)
- 原子性(Atomicity): 事務(wù)是最小的執(zhí)行單位陌宿,不允許分割。事務(wù)的原子性確保動(dòng)作要么全部完成波丰,要么完全不起作用壳坪;因此事務(wù)的操作如果成功就必須要完全應(yīng)用到數(shù)據(jù)庫(kù),如果操作失敗則不能對(duì)數(shù)據(jù)庫(kù)有任何影響掰烟。
- 一致性(Consistency): 執(zhí)行事務(wù)前后爽蝴,數(shù)據(jù)保持一致;一致性是指事務(wù)必須使數(shù)據(jù)庫(kù)從一個(gè)一致性狀態(tài)變換到另一個(gè)一致性狀態(tài)纫骑,也就是說一個(gè)事務(wù)執(zhí)行之前和執(zhí)行之后都必須處于一致性狀態(tài)蝎亚。拿轉(zhuǎn)賬來(lái)說,小明和小紅兩者的錢加起來(lái)一共是5000先馆,那么不管A和B之間如何轉(zhuǎn)賬发框,轉(zhuǎn)幾次賬,事務(wù)結(jié)束后兩個(gè)用戶的錢相加起來(lái)應(yīng)該還得是5000煤墙,這就是事務(wù)的一致性梅惯。
- 隔離性(Isolation): 并發(fā)訪問數(shù)據(jù)庫(kù)時(shí),一個(gè)用戶的事物不被其他事物所干擾仿野,各并發(fā)事務(wù)之間數(shù)據(jù)庫(kù)是獨(dú)立的铣减;當(dāng)多個(gè)用戶并發(fā)訪問數(shù)據(jù)庫(kù)時(shí),比如操作同一張表時(shí)脚作,數(shù)據(jù)庫(kù)為每一個(gè)用戶開啟的事務(wù)葫哗,不能被其他事務(wù)的操作所干擾,多個(gè)并發(fā)事務(wù)之間要相互隔離球涛。即要達(dá)到這么一種效果:對(duì)于任意兩個(gè)并發(fā)的事務(wù)T1和T2劣针,在事務(wù)T1看來(lái),T2要么在T1開始之前就已經(jīng)結(jié)束亿扁,要么在T1結(jié)束之后才開始捺典,這樣每個(gè)事務(wù)都感覺不到有其他事務(wù)在并發(fā)地執(zhí)行。
- 持久性(Durability): 一個(gè)事務(wù)被提交之后魏烫。它對(duì)數(shù)據(jù)庫(kù)中數(shù)據(jù)的改變是持久的辣苏,即使數(shù)據(jù)庫(kù)發(fā)生故障也不應(yīng)該對(duì)其有任何影響肝箱。例如我們?cè)谑褂肑DBC操作數(shù)據(jù)庫(kù)時(shí),在提交事務(wù)方法后稀蟋,提示用戶事務(wù)操作完成煌张,當(dāng)我們程序執(zhí)行完成直到看到提示后,就可以認(rèn)定事務(wù)以及正確提交退客,即使這時(shí)候數(shù)據(jù)庫(kù)出現(xiàn)了問題骏融,也必須要將我們的事務(wù)完全執(zhí)行完成,否則就會(huì)造成我們看到提示事務(wù)處理完畢萌狂,但是數(shù)據(jù)庫(kù)因?yàn)楣收隙鴽]有執(zhí)行事務(wù)的重大錯(cuò)誤档玻。
并發(fā)事務(wù)帶來(lái)的問題
在典型的應(yīng)用程序中,多個(gè)事務(wù)并發(fā)運(yùn)行茫藏,經(jīng)常會(huì)操作相同的數(shù)據(jù)來(lái)完成各自的任務(wù)(多個(gè)用戶對(duì)統(tǒng)一數(shù)據(jù)進(jìn)行操作)误趴。并發(fā)雖然是必須的,但可能會(huì)導(dǎo)致以下的問題务傲。
-
臟讀(Dirty read): 當(dāng)一個(gè)事務(wù)正在訪問數(shù)據(jù)并且對(duì)數(shù)據(jù)進(jìn)行了修改凉当,而這種修改還沒有提交到數(shù)據(jù)庫(kù)中,這時(shí)另外一個(gè)事務(wù)也訪問了這個(gè)數(shù)據(jù)售葡,然后使用了這個(gè)數(shù)據(jù)看杭。因?yàn)檫@個(gè)數(shù)據(jù)是還沒有提交的數(shù)據(jù),那么另外一個(gè)事務(wù)讀到的這個(gè)數(shù)據(jù)是“臟數(shù)據(jù)”挟伙,依據(jù)“臟數(shù)據(jù)”所做的操作可能是不正確的楼雹。
例如:小紅向小明轉(zhuǎn)賬100元,對(duì)應(yīng)SQL命令如下
update account set money=money+100 where name=’小明’; (此時(shí)小紅通知小明) update account set money=money - 100 where name=’小紅’;
當(dāng)只執(zhí)行第一條SQL時(shí)尖阔,小紅通知小明查看賬戶贮缅,發(fā)現(xiàn)確實(shí)錢已到賬(此時(shí)即發(fā)生了臟讀),而之后無(wú)論第二條SQL是否執(zhí)行诺祸,只要該事務(wù)不提交携悯,則所有操作都將回滾,那么當(dāng)B以后再次查看賬戶時(shí)就會(huì)發(fā)現(xiàn)錢其實(shí)并沒有轉(zhuǎn)筷笨。
-
丟失修改(Lost to modify): 指在一個(gè)事務(wù)讀取一個(gè)數(shù)據(jù)時(shí),另外一個(gè)事務(wù)也訪問了該數(shù)據(jù)龟劲,那么在第一個(gè)事務(wù)中修改了這個(gè)數(shù)據(jù)后胃夏,第二個(gè)事務(wù)也修改了這個(gè)數(shù)據(jù)。這樣第一個(gè)事務(wù)內(nèi)的修改結(jié)果就被丟失昌跌,因此稱為丟失修改仰禀。
例如:事務(wù)1讀取某表中的數(shù)據(jù)A=20,事務(wù)2也讀取A=20蚕愤,事務(wù)1修改A=A-1答恶,事務(wù)2也修改A=A-1饺蚊,最終結(jié)果A=19,事務(wù)1的修改被丟失悬嗓。
-
不可重復(fù)讀(Unrepeatableread): 指在一個(gè)事務(wù)內(nèi)多次讀同一數(shù)據(jù)污呼。在這個(gè)事務(wù)還沒有結(jié)束時(shí),另一個(gè)事務(wù)也訪問該數(shù)據(jù)包竹。那么燕酷,在第一個(gè)事務(wù)中的兩次讀數(shù)據(jù)之間,由于第二個(gè)事務(wù)的修改導(dǎo)致第一個(gè)事務(wù)兩次讀取的數(shù)據(jù)可能不太一樣周瞎。這就發(fā)生了在一個(gè)事務(wù)內(nèi)兩次讀到的數(shù)據(jù)是不一樣的情況苗缩,因此稱為不可重復(fù)讀。
例如事務(wù)T1在讀取某一數(shù)據(jù)声诸,而事務(wù)T2立馬修改了這個(gè)數(shù)據(jù)并且提交事務(wù)給數(shù)據(jù)庫(kù)酱讶,事務(wù)T1再次讀取該數(shù)據(jù)就得到了不同的結(jié)果,發(fā)送了不可重復(fù)讀彼乌。
幻讀(Phantom read): 幻讀與不可重復(fù)讀類似泻肯。它發(fā)生在一個(gè)事務(wù)(T1)讀取了幾行數(shù)據(jù),接著另一個(gè)并發(fā)事務(wù)(T2)插入了一些數(shù)據(jù)時(shí)囤攀。在隨后的查詢中软免,第一個(gè)事務(wù)(T1)就會(huì)發(fā)現(xiàn)多了一些原本不存在的記錄,就好像發(fā)生了幻覺一樣焚挠,所以稱為幻讀膏萧。
不可重復(fù)讀和臟讀的區(qū)別:
臟讀是某一事務(wù)讀取了另一個(gè)事務(wù)未提交的臟數(shù)據(jù),而不可重復(fù)讀則是讀取了前一事務(wù)提交的數(shù)據(jù)蝌衔。
在某些情況下榛泛,不可重復(fù)讀并不是問題,比如我們多次查詢某個(gè)數(shù)據(jù)當(dāng)然以最后查詢得到的結(jié)果為主噩斟。但在另一些情況下就有可能發(fā)生問題曹锨,例如對(duì)于同一個(gè)數(shù)據(jù)A和B依次查詢就可能不同,A和B就可能打起來(lái)了……
不可重復(fù)度和幻讀區(qū)別:
不可重復(fù)讀的重點(diǎn)是修改剃允,幻讀的重點(diǎn)在于新增或者刪除沛简。
例1(同樣的條件, 你讀取過的數(shù)據(jù), 再次讀取出來(lái)發(fā)現(xiàn)值不一樣了 ):事務(wù)1中的A先生讀取自己的工資為 1000的操作還沒完成,事務(wù)2中的B先生就修改了A的工資為2000斥废,導(dǎo) 致A再讀自己的工資時(shí)工資變?yōu)?2000椒楣;這就是不可重復(fù)讀。
例2(同樣的條件, 第1次和第2次讀出來(lái)的記錄數(shù)不一樣 ):假某工資單表中工資大于3000的有4人牡肉,事務(wù)1讀取了所有工資大于3000的人捧灰,共查到4條記錄,這時(shí)事務(wù)2 又插入了一條工資大于3000的記錄统锤,事務(wù)1再次讀取時(shí)查到的記錄就變?yōu)榱?條毛俏,這樣就導(dǎo)致了幻讀炭庙。
事務(wù)隔離級(jí)別
現(xiàn)在來(lái)看看MySQL數(shù)據(jù)庫(kù)為我們提供的四種隔離級(jí)別:
① Serializable (串行化)
最高的隔離級(jí)別,完全服從ACID的隔離級(jí)別煌寇。所有的事務(wù)依次逐個(gè)執(zhí)行焕蹄,這樣事務(wù)之間就完全不可能產(chǎn)生干擾,也就是說唧席,該級(jí)別可避免臟讀擦盾、不可重復(fù)讀、幻讀的發(fā)生淌哟。迹卢。
② Repeatable read (可重復(fù)讀)
對(duì)同一字段的多次讀取結(jié)果都是一致的,除非數(shù)據(jù)是被本身事務(wù)自己所修改徒仓,可以阻止臟讀和不可重復(fù)讀腐碱,但幻讀仍有可能發(fā)生。
③ Read committed (讀已提交)
允許讀取并發(fā)事務(wù)已經(jīng)提交的數(shù)據(jù)掉弛,可以阻止臟讀症见,但是幻讀或不可重復(fù)讀仍有可能發(fā)生
④ Read uncommitted (讀未提交)
最低的隔離級(jí)別,允許讀取尚未提交的數(shù)據(jù)變更殃饿,可能會(huì)導(dǎo)致臟讀谋作、幻讀或不可重復(fù)讀
以上四種隔離級(jí)別最高的是Serializable級(jí)別,最低的是Read uncommitted級(jí)別乎芳,當(dāng)然級(jí)別越高遵蚜,執(zhí)行效率就越低。像Serializable這樣的級(jí)別奈惑,就是以鎖表的方式(類似于Java多線程中的鎖)使得其他的線程只能在鎖外等待吭净,所以平時(shí)選用何種隔離級(jí)別應(yīng)該根據(jù)實(shí)際情況。在MySQL數(shù)據(jù)庫(kù)中默認(rèn)的隔離級(jí)別為Repeatable read (可重復(fù)讀)肴甸。
在MySQL數(shù)據(jù)庫(kù)中寂殉,支持上面四種隔離級(jí)別, InnoDB 存儲(chǔ)引擎的默認(rèn)支持的隔離級(jí)別是Repeatable read (可重復(fù)讀)原在;而在Oracle數(shù)據(jù)庫(kù)中友扰,只支持Serializable (串行化)級(jí)別和Read committed (讀已提交)這兩種級(jí)別,其中默認(rèn)的為Read committed級(jí)別庶柿。
在MySQL數(shù)據(jù)庫(kù)中查看當(dāng)前事務(wù)的隔離級(jí)別:
select @@tx_isolation;
在MySQL數(shù)據(jù)庫(kù)中設(shè)置事務(wù)的隔離 級(jí)別:
set [glogal | session] transaction isolation level 隔離級(jí)別名稱;
set tx_isolation=’隔離級(jí)別名稱;’
例1:查看當(dāng)前事務(wù)的隔離級(jí)別:
例2:將事務(wù)的隔離級(jí)別設(shè)置為Read uncommitted級(jí)別:
或:
記谆烂省:設(shè)置數(shù)據(jù)庫(kù)的隔離級(jí)別一定要是在開啟事務(wù)之前!
這里需要注意的是:與 SQL 標(biāo)準(zhǔn)不同的地方在于InnoDB 存儲(chǔ)引擎在 REPEATABLE-READ(可重讀)事務(wù)隔離級(jí)別下使用的是Next-Key Lock 鎖算法澳泵,因此可以避免幻讀的產(chǎn)生,這與其他數(shù)據(jù)庫(kù)系統(tǒng)(如 SQL Server)是不同的兼呵。所以說InnoDB 存儲(chǔ)引擎的默認(rèn)支持的隔離級(jí)別是 REPEATABLE-READ(可重讀) 已經(jīng)可以完全保證事務(wù)的隔離性要求兔辅,即達(dá)到了 SQL標(biāo)準(zhǔn)的SERIALIZABLE(可串行化)隔離級(jí)別腊敲。
因?yàn)楦綦x級(jí)別越低,事務(wù)請(qǐng)求的鎖越少维苔,所以大部分?jǐn)?shù)據(jù)庫(kù)系統(tǒng)的隔離級(jí)別都是READ-COMMITTED(讀取提交內(nèi)容):碰辅,但是你要知道的是InnoDB 存儲(chǔ)引擎默認(rèn)使用 REPEATABLE-READ(可重讀)并不會(huì)有任何性能損失。
InnoDB 存儲(chǔ)引擎在 分布式事務(wù) 的情況下一般會(huì)用到SERIALIZABLE(可串行化)隔離級(jí)別介时。
實(shí)際情況演示
MySQL 命令行的默認(rèn)配置中事務(wù)都是自動(dòng)提交的没宾,即執(zhí)行SQL語(yǔ)句后就會(huì)馬上執(zhí)行 COMMIT 操作。如果要顯式地開啟一個(gè)事務(wù)需要使用命令:START TARNSACTION
沸柔。
我們可以通過下面的命令來(lái)設(shè)置隔離級(jí)別循衰。
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
我們?cè)賮?lái)看一下我們?cè)谙旅鎸?shí)際操作中使用到的一些并發(fā)控制語(yǔ)句:
?START TARNSACTION
|BEGIN
:顯式地開啟一個(gè)事務(wù)。
?COMMIT
:提交事務(wù)褐澎,使得對(duì)數(shù)據(jù)庫(kù)做的所有修改成為永久性会钝。
?ROLLBACK
回滾會(huì)結(jié)束用戶的事務(wù),并撤銷正在進(jìn)行的所有未提交的修改工三。
在下面我會(huì)使用 2 個(gè)命令行 MySQL 迁酸,模擬多線程(多事務(wù))。
臟讀(讀未提交)
避免臟讀(讀已提交)
不可重復(fù)讀
還是剛才上面的讀已提交的圖俭正,雖然避免了讀未提交奸鬓,但是卻出現(xiàn)了,一個(gè)事務(wù)還沒有結(jié)束掸读,就發(fā)生了 不可重復(fù)讀問題串远。
可重復(fù)讀
防止幻讀(可重復(fù)讀)
一個(gè)事務(wù)對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,這種操作的范圍是數(shù)據(jù)庫(kù)的全部行寺枉,然后第二個(gè)事務(wù)也在對(duì)這個(gè)數(shù)據(jù)庫(kù)操作抑淫,這種操作可以是插入一行記錄或刪除一行記錄,那么第一個(gè)是事務(wù)就會(huì)覺得自己出現(xiàn)了幻覺姥闪,怎么還有沒有處理的記錄呢? 或者 怎么多處理了一行記錄呢?
幻讀和不可重復(fù)讀有些相似之處 始苇,但是不可重復(fù)讀的重點(diǎn)是修改,幻讀的重點(diǎn)在于新增或者刪除筐喳。