本文主要討論數據庫事務隔離級別與原理,接下來將會從以下四點展開討論:
事務隔離的概念
采用葫蘆娃例子幫助理解在沒有事務隔離時引發的臟讀、不可重復讀和幻讀
數據庫常見四種隔離級別
“快照讀”概念
事務隔離的概念
任何支持事務的數據庫,都必須具備四個特性,分別是:
原子性(Atomicity)
一致性(Consistency)
隔離性(Isolation)
持久性(Durability)
以上四點也就是常說的事務ACID,數據庫具備以上特性才能保證事務(Transaction)中數據的正確性。而其中事務的隔離性指:事務間是相互獨立的,不會互相影響,既一個事務內部的操作及使用的數據對并發的其他事務是隔離的。
沒有事務隔離會引發什么問題?
如果沒有事務隔離那么可能會出現臟讀、不可重復讀、幻讀等情況。
為了幫助及加深理解,我們采用熟悉的“葫蘆娃”作為例子。最先藤上有七個葫蘆,每當有一個葫蘆娃誕生時,老爺爺就會將他的信息錄入數據庫表中,如下:
某一天,四娃誕生了。事務A(爺爺)訪問了數據庫,目的是往數據庫里添加新誕生葫蘆娃的信息,但是還沒有提交事務。
insert into T values (4, ‘四娃’,‘噴水’);
這時,來了另一個事務B(蛇精),她進行了查詢操作,想查詢所有已誕生的葫蘆娃信息
select * from T; //結果中出現了四娃的信息
這時,如果事務之間沒有有效隔離,那么 蛇精 查詢數據庫返回的結果中就會出現 四娃 的信息,這就是臟讀。
(1) 臟讀:指在自己的事務中讀到了別人未提交的數據
第二天,事務A(蛇精)一大早就查詢了數據庫中關于四娃的信息
select * from T where ID = 4; //名稱:四娃 能力:噴水
這時,事務B(爺爺)來了,因為爺爺發現四娃其實是會噴火而不是噴水,所以更新一下將改了四娃的能力為噴火,然后提交了事務。
update T set Ability= ‘噴火’ where ID = 4;
接著,蛇精(事務A)還想再查看一次用于確認四娃的信息,于是又執行了
select * from T where ID = 4;//名稱:四娃 能力:噴火
這時候她驚訝地發現兩次讀出來四娃的信息,能力竟然不相同!這就是不可重復讀。
(2) 不可重復讀:指在自己的事務中讀取兩次,前后的數據不一樣
第三天
事務A(蛇精)訪問了數據庫,她想要看看到底已經誕生的葫蘆娃有哪些,于是她執行了
select * from T; //查出一共有四條葫蘆娃信息
這時候因為五娃誕生了,所以事務B(爺爺)打開了數據庫并將五娃的信息錄入
insert into T values (5, ‘五’,‘噴水’);
這時候,事務A(蛇精)想要再查詢一次所有已誕生的葫蘆娃信息進行確認,于是又執行了查詢
select * from T; //查出一共有五條葫蘆娃信息
這個時候事務A(蛇精)可能就會蒙了,以為自己產生了幻覺。這種情況就叫“幻讀。
(3) 幻讀:指在自己的事務中兩個連續的查找之間一個并發的修改事務修改了查詢的數據集,導致這兩個查詢返回了不同的結果(注:不可重復讀與幻讀很相似,不可重復讀的重點是修改,而幻讀的重點在于新增或者刪除)
數據庫的隔離級別
想要避免以上的情況,我們可以通過設置數據庫的隔離級別(結合實際場景選擇最適合的級別)。一般數據庫都包括以下四種隔離級別:
讀提交(Read Committed)
可重復讀(Repeated Read)
串行化(Serializable)
這里以MySQL為例,在MySQL中事務隔離級別分為以下四級:
0級:TRANSACTION_READ_UNCOMMITTED 一切都可發生
1級:TRANSACTION_READ_COMMITTED--不可以發生臟讀,不可重復讀和幻讀可以發生
2級:TRANSACTION_REPEATABLE_READ--不可以發生臟讀和重復讀,可以發生幻讀
3級:TRANSACTION_SERIALIZABLE --都不可發生
(1) 讀未提交(TRANSACTION_READ_UNCOMMITTED)
讀未提交,指可以讀到未提交的內容。因為這種隔離級別下查詢是不會加鎖的,所以可能會產生“臟讀”、“不可重復讀”、“幻讀”。在實際開發中如無特殊情況基本是不會使用該隔離級別的。
(2) 讀提交(TRANSACTION_READ_COMMITTED)
讀提交,指只能讀到已經提交了的內容。這是最常用的一種隔離級別也是Oracle和SQLServer的默認級別,該級別可以有效地避免臟讀。(注意:除非顯示加鎖如共享鎖、排他鎖,否則查詢是默認不加鎖的。而區別于“讀未提交”,“讀提交”可避免臟讀的原因是采用了 “快照讀”)
(3) 可重復讀(TRANSACTION_REPEATABLE_READ)
可重復讀,該級別可以有效的避免“不可重復讀”,也是MySQL數據庫innodb默認的級別。在這個級別下,普通的查詢同樣是使用的“快照讀”,但是,和“讀提交”不同的是,當事務啟動時就不允許進行Update操作,而“不可重復讀”是因為兩次讀取之間進行了數據的修改所導致的。因此“可重復讀”能夠有效的避免“不可重復讀”,但卻避免不了“幻讀”,因為幻讀是由于“插入或者刪除操作”而產生的。
(4) 串行化(TRANSACTION_SERIALIZABLE)
串行化是數據庫最高的隔離級別,這種級別下事務串行化一個一個排隊順序執行,可避免臟讀、不可重復讀、幻讀。安全性高相應的執行效率低,性能開銷也最大,在實際開發中比較少用。
快照讀
數據庫讀分為:一致非鎖定讀、鎖定讀,上面提到“快照讀”也就是非鎖定讀。可簡單理解為執行SELECT語句的時候會生成一個快照。
注意:不同事務隔離級別下,快照讀是存在區別的:
READ COMMITTED 隔離級別下,事務中每次讀取都會重新生成一個快照,所以每次快照都是最新的。因此事務中每次執行SELECT也可以看到其它已commit事務所作的更改,因為讀取的是快照所以有效地避免了臟讀的情況。而假設如果沒有“快照讀(一致非鎖定讀)”使用的是“鎖定讀”,那么當一個更新的事務沒有提交時,另一個對更新數據進行查詢的事務會因為無法查詢而被阻塞,這種情況下并發能力及效率相對比較差。
REPEATED READ 隔離級別下,快照會在事務中第一次SELECT語句執行時生成,只有在本事務中對數據進行更改Update才會更新快照,因此,只有第一次SELECT之前其它已提交事務所作的更改可以看到。
總結
事務的隔離性指:事務間是相互獨立的,不會互相影響,既一個事務內部的操作及使用的數據對并發的其他事務是隔離的。當沒有進行事務隔離時可能會出現臟讀、幻讀、不可重復讀等情況。通過結合實際情況設置合理的隔離級別可以有效地避免以上問題。
數據庫中常見的四個隔離級別:讀未提交、讀提交、可重復讀和串行化,其中讀提交在實際開發中是比較常用的。而在其中引出了一個“讀快照”的概念,要注意的是不同隔離級別下“讀快照”是存在區別的,通過使用“讀快照”使得在發生并發操作時效率有所提升。
評論
查看更多