今天來聊聊事務的四大特性以及其實現原理,需結合之前寫的mysql是如何實現mvcc的來理解,因爲大多數的實現都是基於mvcc的,理論介紹完後會通過實例來演示mvcc又是如何實現這些隔離級別的
事務的四大特性
- 原子性
事務的執行要麼全部成功要麼全部失敗,成功的時候直接提交就好,某些環節失敗的時候是需要回滾的,這就要用到回滾日誌(undolog)根據當前記錄的回滾指針去undolog定位歷史記錄了; - 一致性
事務的其他特性就是爲了保證一致性的 - 隔離性
兩個事務之間的執行應該是隔離開的,各自造成的數據改變不會相互影響,有四個隔離級別下面會一一解釋 - 持久性
事務提交後持久化到硬盤,通過redo log實現
隔離級別
說明:下面的概念解釋假設有兩個事務A、B處於活躍狀態
- 讀未提交(READ-UNCOMMITTED)
事務B讀到了事務A新增但未提交的數據;這種事務未提交就被其他事務讀取到的現象叫做髒讀;而在實際生產中這是毫無意義的,所以幾乎不會被用到 - 讀已提交(READ-UNCOMMITTED)
是oracle的默認隔離級別;事務B讀到了事務A修改了並且已經提交了的數據;分兩種情況,一是事務A修改了數據導致B在第二次讀的時候與第一次讀到的結果不一樣,這種現象叫做不可重複讀;二是事務A新增了數據導致B在第二次查詢時候與第一次查詢時讀到的記錄數不一致,這種現狀叫做幻讀;即RC級別下可能會出現幻讀和不可重複讀的現象 - 可重複讀(REPEATABLE-READ)
是mysql的默認隔離級別;事務A對數據的操作不影響事務B,即事務B在事務A修改數據前後查的數據是一致的;這種級別看似不會出現問題,但是噹噹前讀和快照讀混用的時候也是會出現幻讀或者不可重複讀的問題的,下面我會演示這一現象;而mysql是通過mvcc+間隙鎖來解決這一問題的 - 串行(SERIALIZABLE)
事務串行執行,效率低但不會出現髒讀、幻讀、不可重複讀的現象
小總結:綜上所述,幻讀可以理解成相同條件下事務前後讀取到的記錄數不一樣,而不可重複讀可理解成事務前後讀取都的內容不一樣,記錄數可以一樣;
不同隔離級別下的實操
數據準備
- 創建一個測試表test
CREATE TABLE test (
id int NOT NULL AUTO_INCREMENT,
name varchar(255) DEFAULT NULL,
age int DEFAULT NULL,
PRIMARY KEY (id)
)
- 插入原始測試數據
insert into test values(10,'zhangsan',19);
insert into test values(20,'lisi',21);
insert into test values(30,'wangwu',21);
可重複讀(RR)隔離級別下測試
-
1.打開兩個命令行窗口並關閉事務的自動提交;然後分別在兩個窗口手動開啓事務;如圖:
-
2.在A事務窗口執行查詢操作:select * from test where age = 21;結果和預期一致符合條件的是兩條數據,如圖:
-
3.在B事務窗口執行插入操作insert into test values(40,'zhaoliu',21);後提交事務並進行查詢select * from test where age = 21;此時查詢到的是三條記錄,如圖:
-
4.在事務A窗口重新執行select * from test where age = 21;發現得到的結果與第一次一致還是兩條
分析:
綜上可以發現,事務A在事務週期內在事務B對錶做了新增數據的情況下兩次查詢結果仍然一致,沒有出現幻讀、不可重複讀的情況,是因爲mysql的默認隔離級別是RR(可重複讀),而正常情況下RR是可以避免幻讀和不可重複讀的情況的,除非在事務週期內混用了當前讀和快照讀,下面來演示RR級別下出現幻讀的情況;
- 5.在上面的基礎上我們在事務A的查詢語句基礎上新增一個for update語句:select * from test where age = 21 for update;此時我們就會發現記錄數變成了3條(如圖),即出現了幻讀的情況,因爲類似for update、delete等語句是會使用當前讀的,而之前的查詢語句用的是快照讀,這就出現了快照讀和當前讀混用的現象從而出現了幻讀
讀已提交(RC)隔離界別下的測試
步驟與上面幾乎一致,只是需要在開啓事務之前將隔離級別設置成讀已提交(RC),如圖:
接下來重複上面的步驟:
- 1.關閉事務的自動提交;手動開啓兩個事務,這裏就不上圖了
- 2.事務A執行select * from test where age = 21;得到的結果是三條(上面的測試新增了一條數據),如圖:
- 3.事務B執行insert into test values(50,'qianqi',21);後提交事務並進行查詢select * from test where age = 21;此時查詢到的是四條記錄,如圖:
- 4.在事務A窗口重新執行select * from test where age = 21;此時我們發現得到結果卻是4條記錄與第一次查詢的結果不一致,這就出現了幻讀的現象,如圖:
思考:爲什麼在測試步驟和數據不變的情況下不同的隔離級別下查詢的結果不一致呢?
這是因爲兩種隔離級別下生成readView的時機不一樣,mysql默認的可重複讀級別下是在事務中的第一次查詢時生成的readview,後續在沒有當前讀的情況下事務內所有的查詢都是基於第一次查詢生成的readview的快照讀;而讀已提交的隔離級別下在事務內的每次查詢都會生成一個readview,所以造成了兩種不同的現象;