MySQL 事務(wù)與 MVCC 原理?這回總該懂了吧!
來(lái)源 | 一個(gè)優(yōu)秀的廢人(ID:feiren_java)
01 什么是事務(wù)?
數(shù)據(jù)庫(kù)事務(wù)指的是一組數(shù)據(jù)操作,事務(wù)內(nèi)的操作要么就是全部成功,要么就是全部失敗,什么都不做,其實(shí)不是沒做,是可能做了一部分但是只要有一步失敗,就要回滾所有操作,有點(diǎn)一不做二不休的意思。
在 MySQL 中,事務(wù)支持是在引擎層實(shí)現(xiàn)的。MySQL 是一個(gè)支持多引擎的系統(tǒng),但并不是所有的引擎都支持事務(wù)。比如 MySQL 原生的 MyISAM 引擎就不支持事務(wù),這也是 MyISAM 被 InnoDB 取代的重要原因之一。
1.1 四大特性
原子性(Atomicity):事務(wù)開始后所有操作,要么全部做完,要么全部不做,不可能停滯在中間環(huán)節(jié)。事務(wù)執(zhí)行過程中出錯(cuò),會(huì)回滾到事務(wù)開始前的狀態(tài),所有的操作就像沒有發(fā)生一樣。也就是說(shuō)事務(wù)是一個(gè)不可分割的整體,就像化學(xué)中學(xué)過的原子,是物質(zhì)構(gòu)成的基本單位。 一致性(Consistency):事務(wù)開始前和結(jié)束后,數(shù)據(jù)庫(kù)的完整性約束沒有被破壞 。比如 A 向 B 轉(zhuǎn)賬,不可能 A 扣了錢,B 卻沒收到。 隔離性(Isolation):同一時(shí)間,只允許一個(gè)事務(wù)請(qǐng)求同一數(shù)據(jù),不同的事務(wù)之間彼此沒有任何干擾。比如 A 正在從一張銀行卡中取錢,在 A 取錢的過程結(jié)束前,B 不能向這張卡轉(zhuǎn)賬。 持久性(Durability):事務(wù)完成后,事務(wù)對(duì)數(shù)據(jù)庫(kù)的所有更新將被保存到數(shù)據(jù)庫(kù),不能回滾。
1.2 隔離級(jí)別
SQL 事務(wù)的四大特性中原子性、一致性、持久性都比較好理解。但事務(wù)的隔離級(jí)別確實(shí)比較難的,今天主要聊聊 MySQL 事務(wù)的隔離性。
SQL 標(biāo)準(zhǔn)的事務(wù)隔離從低到高級(jí)別依次是:讀未提交(read uncommitted)、讀提交(read committed)、可重復(fù)讀(repeatable read)和串行化(serializable )。級(jí)別越高,效率越低。
讀未提交:一個(gè)事務(wù)還沒提交時(shí),它做的變更就能被別的事務(wù)看到。 讀提交:一個(gè)事務(wù)提交之后,它做的變更才會(huì)被其他事務(wù)看到。 可重復(fù)讀:一個(gè)事務(wù)執(zhí)行過程中看到的數(shù)據(jù),總是跟這個(gè)事務(wù)在啟動(dòng)時(shí)看到的數(shù)據(jù)是一致的。當(dāng)然在可重復(fù)讀隔離級(jí)別下,未提交變更對(duì)其他事務(wù)也是不可見的。 串行化:顧名思義是對(duì)于同一行記錄,“寫” 會(huì)加 “寫鎖”,“讀” 會(huì)加 “讀鎖”。當(dāng)出現(xiàn)讀寫鎖沖突的時(shí)候,后訪問的事務(wù)必須等前一個(gè)事務(wù)執(zhí)行完成,才能繼續(xù)執(zhí)行。所以種隔離級(jí)別下所有的數(shù)據(jù)是最穩(wěn)定的,但是性能也是最差的。
1.3 解決的并發(fā)問題
SQL 事務(wù)隔離級(jí)別的設(shè)計(jì)就是為了能最大限度的解決并發(fā)問題:
臟讀:事務(wù) A 讀取了事務(wù) B 更新的數(shù)據(jù),然后 B 回滾操作,那么 A 讀取到的數(shù)據(jù)是臟數(shù)據(jù) 不可重復(fù)讀:事務(wù) A 多次讀取同一數(shù)據(jù),事務(wù) B 在事務(wù) A 多次讀取的過程中,對(duì)數(shù)據(jù)作了更新并提交,導(dǎo)致事務(wù) A 多次讀取同一數(shù)據(jù)時(shí),結(jié)果不一致。 幻讀:系統(tǒng)管理員 A 將數(shù)據(jù)庫(kù)中所有學(xué)生的成績(jī)從具體分?jǐn)?shù)改為 ABCDE 等級(jí),但是系統(tǒng)管理員 B 就在這個(gè)時(shí)候插入了一條具體分?jǐn)?shù)的記錄,當(dāng)系統(tǒng)管理員 A 改結(jié)束后發(fā)現(xiàn)還有一條記錄沒有改過來(lái),就好像發(fā)生了幻覺一樣,這就叫幻讀。
SQL 不同的事務(wù)隔離級(jí)別能解決的并發(fā)問題也不一樣,如下表所示:只有串行化的隔離級(jí)別解決了全部這 3 個(gè)問題,其他的 3 個(gè)隔離級(jí)別都有缺陷。
| 事務(wù)隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
|---|---|---|---|
| 讀未提交 | 可能 | 可能 | 可能 |
| 讀已提交 | 不可能 | 可能 | 可能 |
| 可重復(fù)讀 | 不可能 | 不可能 | 可能 |
| 串行化 | 不可能 | 不可能 | 不可能 |
PS:不可重復(fù)讀的和幻讀很容易混淆,不可重復(fù)讀側(cè)重于修改,幻讀側(cè)重于新增或刪除。解決不可重復(fù)讀的問題只需鎖住滿足條件的行,解決幻讀需要鎖表
1.4 舉個(gè)栗子
這么說(shuō)可能有點(diǎn)難以理解,舉個(gè)栗子。還是之前的表結(jié)構(gòu)以及表數(shù)據(jù)
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 66 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

假設(shè)現(xiàn)在,我要同時(shí)啟動(dòng)兩個(gè)食物,一個(gè)事務(wù) A 查詢 id = 2 的學(xué)生的 age,一個(gè)事務(wù) B 更新 id = 2 的學(xué)生的 age。流程如下,在四種隔離級(jí)別下的 X1、X2、X3 的值分別是怎樣的呢?

讀未提交:X1 的值是 23,因?yàn)槭聞?wù) B 雖然沒提交但它的更改已被 A 看到。(如果 B 后面又回滾了 X1 的值就是臟的)。X2、X3 的值也是 23,這無(wú)可厚非。 讀已提交:X1 的值是 22,因?yàn)?B 雖然改了,但 A 看不到。(如果 B 后面回滾了,X1 的值不變,解決了臟讀),X2、X3 的值是 23,沒毛病,B 提交了,A 才能看到。 可重復(fù)讀:X1、X2 都是 22,A 開啟的時(shí)刻值是 22,那么在 A 的整個(gè)過程中,它的值都是 22。(不管 B 在這期間怎么修改,只要 A 還沒提交,都是看不見的,解決了不可重復(fù)讀),而 X3 的值是 23,因?yàn)?A 提交了,能看到 B 修改的值了。 串行化:B 在執(zhí)行更改期間會(huì)被鎖住,直至 A 提交。B 才能繼續(xù)執(zhí)行。(A 在讀期間,B 不能寫。得保證此時(shí)數(shù)據(jù)是最新的。解決了幻讀)所以 X1、X2 都是 22,而最后的 X3 在 B 提交之后執(zhí)行,它的值就是 23。
那為什么會(huì)出現(xiàn)這樣的結(jié)果呢?事務(wù)隔離級(jí)別到底是怎么實(shí)現(xiàn)的呢?
事務(wù)隔離級(jí)別是怎么是實(shí)現(xiàn)的呢?我在極客時(shí)間丁奇老師的課上找到了答案:
實(shí)際上,數(shù)據(jù)庫(kù)里面會(huì)創(chuàng)建一個(gè)視圖,訪問的時(shí)候以視圖的邏輯結(jié)果為準(zhǔn)。在 “可重復(fù)讀” 隔離級(jí)別下,這個(gè)視圖是在事務(wù)啟動(dòng)時(shí)創(chuàng)建的,整個(gè)事務(wù)存在期間都用這個(gè)視圖。在 “讀提交” 隔離級(jí)別下,這個(gè)視圖是在每個(gè) SQL 語(yǔ)句開始執(zhí)行的時(shí)候創(chuàng)建的。這里需要注意的是,“讀未提交” 隔離級(jí)別下直接返回記錄上的最新值,沒有視圖概念;而 “串行化” 隔離級(jí)別下直接用加鎖的方式來(lái)避免并行訪問。
1.5 設(shè)置事務(wù)隔離級(jí)別
不同的數(shù)據(jù)庫(kù)默認(rèn)設(shè)置的事務(wù)隔離級(jí)別也大不一樣,Oracle 數(shù)據(jù)庫(kù)的默認(rèn)隔離級(jí)別是讀提交,而 MySQL 是可重復(fù)讀。所以,當(dāng)你的系統(tǒng)需要把數(shù)據(jù)庫(kù)從 Oracle 遷移到 MySQL 時(shí),請(qǐng)把級(jí)別設(shè)置成與搬遷之前的(讀提交)一致,避免出現(xiàn)不可預(yù)測(cè)的問題。
1.5.1 查看事務(wù)隔離級(jí)別
# 查看事務(wù)隔離級(jí)別
5.7.20 之前
SELECT @@transaction_isolation
show variables like 'transaction_isolation';
# 5.7.20 以及之后
SELECT @@tx_isolation
show variables like 'tx_isolation'
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1.5.2 設(shè)置隔離級(jí)別
修改隔離級(jí)別語(yǔ)句格式是:set [作用域] transaction isolation level [事務(wù)隔離級(jí)別]
其中作用域可選:SESSION(會(huì)話)、GLOBAL(全局);隔離級(jí)別就是上面提到的 4 種,不區(qū)分大小寫。
例如:設(shè)置全局隔離級(jí)別為讀提交
set global transaction isolation level read committed;
1.6 事務(wù)的啟動(dòng)
MySQL 的事務(wù)啟動(dòng)有以下幾種方式:
顯式啟動(dòng)事務(wù)語(yǔ)句, begin 或 start transaction。配套的提交語(yǔ)句是 commit,或者回滾語(yǔ)句是 rollback。
# 更新學(xué)生名字
START TRANSACTION;
update student set name = '張三' where id = 2;
commit;
set autocommit = 0,這個(gè)命令會(huì)將線程的自動(dòng)提交關(guān)掉。意味著如果你只執(zhí)行一個(gè) select 語(yǔ)句,這個(gè)事務(wù)就啟動(dòng)了,而且并不會(huì)自動(dòng)提交。這個(gè)事務(wù)持續(xù)存在直到你主動(dòng)執(zhí)行 commit 或 rollback 語(yǔ)句,或者斷開連接。
set autocommit = 1,表示 MySQL 自動(dòng)開啟和提交事務(wù)。比如執(zhí)行一個(gè) update 語(yǔ)句,語(yǔ)句只完成后就自動(dòng)提交了。不需要顯示的使用 begin、commit 來(lái)開啟和提交事務(wù)。所以當(dāng)我們執(zhí)行多個(gè)語(yǔ)句的時(shí)候,就需要手動(dòng)的用 begin、commit 來(lái)開啟和提交事務(wù)。
start transaction with consistent snapshot;上面提到的 begin/start transaction 命令并不是一個(gè)事務(wù)的起點(diǎn),在執(zhí)行到它們之后的第一個(gè)操作 InnoDB 表的語(yǔ)句,事務(wù)才真正啟動(dòng)。如果你想要馬上啟動(dòng)一個(gè)事務(wù),可以使用 start transaction with consistent snapshot 命令。第一種啟動(dòng)方式,一致性視圖是在執(zhí)行第一個(gè)快照讀語(yǔ)句時(shí)創(chuàng)建的;第二種啟動(dòng)方式,一致性視圖是在執(zhí)行 start transaction with consistent snapshot 時(shí)創(chuàng)建的。
02 事務(wù)隔離的實(shí)現(xiàn)
理解了隔離級(jí)別,那事務(wù)的隔離是怎么實(shí)現(xiàn)的呢?要想理解事務(wù)隔離,先得了解 MVCC 多版本的并發(fā)控制這個(gè)概念。而 MVCC 又依賴于 undo log 和 read view 實(shí)現(xiàn)。
2.1 什么是 MVCC?
百度上的解釋是這樣的:
MVCC,全稱 Multi-Version Concurrency Control,即多版本并發(fā)控制。MVCC 是一種并發(fā)控制的方法,一般在數(shù)據(jù)庫(kù)管理系統(tǒng)中,實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的并發(fā)訪問,在編程語(yǔ)言中實(shí)現(xiàn)事務(wù)內(nèi)存。
MVCC 使得數(shù)據(jù)庫(kù)讀不會(huì)對(duì)數(shù)據(jù)加鎖,普通的 SELECT 請(qǐng)求不會(huì)加鎖,提高了數(shù)據(jù)庫(kù)的并發(fā)處理能力;數(shù)據(jù)庫(kù)寫才會(huì)加鎖。借助 MVCC,數(shù)據(jù)庫(kù)可以實(shí)現(xiàn) READ COMMITTED,REPEATABLE READ 等隔離級(jí)別,用戶可以查看當(dāng)前數(shù)據(jù)的前一個(gè)或者前幾個(gè)歷史版本,保證了 ACID 中的 I 特性(隔離性)。
MVCC 只在 REPEATABLE READ 和 READ COMMITIED 兩個(gè)隔離級(jí)別下工作。其他兩個(gè)隔離級(jí)別都和 MVCC 不兼容 ,因?yàn)?READ UNCOMMITIED 總是讀取最新的數(shù)據(jù)行,而不是符合當(dāng)前事務(wù)版本的數(shù)據(jù)行。而 SERIALIZABLE 則會(huì)對(duì)所有讀取的行都加鎖。
2.1.1 InnDB 中的 MVCC
InnDB 中每個(gè)事務(wù)都有一個(gè)唯一的事務(wù) ID,記為 transaction_id。它在事務(wù)開始時(shí)向 InnDB 申請(qǐng),按照時(shí)間先后嚴(yán)格遞增。
而每行數(shù)據(jù)其實(shí)都有多個(gè)版本,這就依賴 undo log 來(lái)實(shí)現(xiàn)了。每次事務(wù)更新數(shù)據(jù)就會(huì)生成一個(gè)新的數(shù)據(jù)版本,并把 transaction_id 記為 row trx_id。同時(shí)舊的數(shù)據(jù)版本會(huì)保留在 undo log 中,而且新的版本會(huì)記錄舊版本的回滾指針,通過它直接拿到上一個(gè)版本。
所以,InnDB 中的 MVCC 其實(shí)是通過在每行記錄后面保存兩個(gè)隱藏的列來(lái)實(shí)現(xiàn)的。一列是事務(wù) ID:trx_id;另一列是回滾指針:roll_pt。
2.2 undo log
回滾日志保存了事務(wù)發(fā)生之前的數(shù)據(jù)的一個(gè)版本,可以用于回滾,同時(shí)可以提供多版本并發(fā)控制下的讀(MVCC),也即非鎖定讀。
根據(jù)操作的不同,undo log 分為兩種:insert undo log 和 update undo log。
2.2.1 insert undo log
insert 操作產(chǎn)生的 undo log,因?yàn)?insert 操作記錄沒有歷史版本只對(duì)當(dāng)前事務(wù)本身可見,對(duì)于其他事務(wù)此記錄不可見,所以 insert undo log 可以在事務(wù)提交后直接刪除而不需要進(jìn)行 purge 操作。
purge 的主要任務(wù)是將數(shù)據(jù)庫(kù)中已經(jīng) mark del 的數(shù)據(jù)刪除,另外也會(huì)批量回收 undo pages
所以,插入數(shù)據(jù)時(shí)。它的初始狀態(tài)是這樣的:

2.2.2 update undo log
UPDATE 和 DELETE 操作產(chǎn)生的 Undo log 都屬于同一類型:update_undo。(update 可以視為 insert 新數(shù)據(jù)到原位置,delete 舊數(shù)據(jù),undo log 暫時(shí)保留舊數(shù)據(jù))。
事務(wù)提交時(shí)放到 history list 上,沒有事務(wù)要用到這些回滾日志,即系統(tǒng)中沒有比這個(gè)回滾日志更早的版本時(shí),purge 線程將進(jìn)行最后的刪除操作。
一個(gè)事務(wù)修改當(dāng)前數(shù)據(jù):

另一個(gè)事務(wù)修改數(shù)據(jù):

這樣的同一條記錄在數(shù)據(jù)庫(kù)中存在多個(gè)版本,就是上面提到的多版本并發(fā)控制 MVCC。
另外,借助 undo log 通過回滾可以回到上一個(gè)版本狀態(tài)。比如要回到 V1 只需要順序執(zhí)行兩次回滾即可。
2.3 read-view
read view 是 InnDB 在實(shí)現(xiàn) MVCC 時(shí)用到的一致性讀視圖,用于支持 RC(讀提交)以及 RR(可重復(fù)讀)隔離級(jí)別的實(shí)現(xiàn)。
read view 不是真實(shí)存在的,只是一個(gè)概念,undo log 才是它的體現(xiàn)。它主要是通過版本和 undolog 計(jì)算出來(lái)的。作用是決定事務(wù)能看到哪些數(shù)據(jù)。
每個(gè)事務(wù)或者語(yǔ)句有自己的一致性視圖。普通查詢語(yǔ)句是一致性讀,一致性讀會(huì)根據(jù) row trx_id 和一致性視圖確定數(shù)據(jù)版本的可見性。
2.3.1 數(shù)據(jù)版本的可見性規(guī)則
read view 中主要包含當(dāng)前系統(tǒng)中還有哪些活躍的讀寫事務(wù),在實(shí)現(xiàn)上 InnDB 為每個(gè)事務(wù)構(gòu)造了一個(gè)數(shù)組,用來(lái)保存這個(gè)事務(wù)啟動(dòng)瞬間,當(dāng)前正活躍(還未提交)的事務(wù)。
前面說(shuō)了事務(wù) ID 隨時(shí)間嚴(yán)格遞增的,把系統(tǒng)中已提交的事務(wù) ID 的最大值記為數(shù)組的低水位,已創(chuàng)建過的事務(wù) ID + 1 記為高水位。
這個(gè)視圖數(shù)組和高水位就組成了當(dāng)前事務(wù)的一致性視圖(read view)
這個(gè)數(shù)組畫個(gè)圖,長(zhǎng)這樣:

規(guī)則如下:
1 如果 trx_id 在灰色區(qū)域,表明被訪問版本的 trx_id 小于數(shù)組中低水位的 id 值,也即生成該版本的事務(wù)在生成 read view 前已經(jīng)提交,所以該版本可見,可以被當(dāng)前事務(wù)訪問。 2 如果 trx_id 在橙色區(qū)域,表明被訪問版本的 trx_id 大于數(shù)組中高水位的 id 值,也即生成該版本的事務(wù)在生成 read view 后才生成,所以該版本不可見,不能被當(dāng)前事務(wù)訪問。 3 如果在綠色區(qū)域,就會(huì)有兩種情況: a) trx_id 在數(shù)組中,證明這個(gè)版本是由還未提交的事務(wù)生成的,不可見 b) trx_id 不在數(shù)組中,證明這個(gè)版本是由已提交的事務(wù)生成的,可見
第三點(diǎn)我在看教程的時(shí)候也有點(diǎn)疑惑,好在有熱心網(wǎng)友解答:
落在綠色區(qū)域意味著是事務(wù) ID 在低水位和高水位這個(gè)范圍里面,而真正是否可見,看綠色區(qū)域是否有這個(gè)值。如果綠色區(qū)域沒有這個(gè)事務(wù) ID,則可見,如果有,則不可見。在這個(gè)范圍里面并不意味著這個(gè)范圍就有這個(gè)值,比如 [1,2,3,5],4 在這個(gè)數(shù)組 1-5 的范圍里,卻沒在這個(gè)數(shù)組里面。
這樣說(shuō)可能有點(diǎn)難以理解,我假設(shè)一個(gè)場(chǎng)景:三個(gè)事務(wù)對(duì)同一條數(shù)據(jù)進(jìn)行查詢更新等操作,為此畫了張圖以方便理解:

原始數(shù)據(jù)還是下圖這樣的,對(duì) id = 2 的張三進(jìn)行信息的更新:

針對(duì)上圖,我想提個(gè)問題。** 分別在 RC(讀提交)以及 RR(可重復(fù)讀)隔離級(jí)別下,T4 和 T5 時(shí)間點(diǎn)的查詢 age 值分別是多少呢?T4 更新的值又是多少呢?** 思考片刻,相信大家都有自己的答案。答案在文末,希望大家能帶著自己的疑問繼續(xù)讀下去。
2.3.2 RR(可重復(fù)讀)下的結(jié)果
RR 級(jí)別下,查詢只承認(rèn)在事務(wù)啟動(dòng)前就已經(jīng)提交完成的數(shù)據(jù),一旦啟動(dòng)事務(wù)就會(huì)建視圖。所以使用 start transaction with consistent snapshot 命令,馬上就會(huì)建視圖。
現(xiàn)在假設(shè):
事務(wù) A 開始前,只有一個(gè)活躍的事務(wù),ID = 2, 已提交的事務(wù)也就是插入數(shù)據(jù)的事務(wù) ID = 1 事務(wù) A、B、C 的事務(wù) ID 分別是 3、4、5
在這種隔離級(jí)別下,他們創(chuàng)建視圖的時(shí)刻如下:

根據(jù)上圖得,事務(wù) A 的視圖數(shù)組是 [2,3];事務(wù) B 的視圖數(shù)組是 [2,3,4];事務(wù) C 的視圖數(shù)組是 [2,3,4,5]。分析一波:
T4 時(shí)刻,B 讀數(shù)據(jù)都是從當(dāng)前版本讀起,過程是這樣的: 讀到當(dāng)前版本的 trx_id = 4,剛好是自己,可見 所以 age = 24 T5 時(shí)刻,A 讀數(shù)據(jù)都是從當(dāng)前版本讀起,過程是這樣的: 讀到當(dāng)前版本的 trx_id = 4,比自己視圖數(shù)組的高水位大,不可見 再往上讀到 trx_id = 5,比自己視圖數(shù)組高水位大,不可見 再往上讀到 trx_id = 1,比自己視圖數(shù)組低水位小,可見 所以 age = 22
這樣執(zhí)行下來(lái),雖然期間這一行數(shù)據(jù)被修改過,但是事務(wù) A 不論在什么時(shí)候查詢,看到這行數(shù)據(jù)的結(jié)果都是一致的,所以我們稱之為一致性讀。
其實(shí)視圖是否可見主要看創(chuàng)建視圖和提交的時(shí)機(jī),總結(jié)下規(guī)律:
版本未提交,不可見 版本已提交,但在視圖創(chuàng)建后提交,不可見 版本已提交,但在視圖創(chuàng)建前提交,可見
2.3.2.1 快照讀和當(dāng)前讀
事務(wù) B 的 update 語(yǔ)句,如果按照上圖的一致性讀,好像結(jié)果不大對(duì)?
如下圖周明,B 的視圖數(shù)組是先生成的,之后事務(wù) C 才提交。那就應(yīng)該看不見 C 修改的 age = 23 呀?最后 B 怎么得出 24 了?

沒錯(cuò),如果 B 在更新之前執(zhí)行查詢語(yǔ)句,那返回的結(jié)果肯定是 age = 22。問題是更新就不能在歷史版本更新了呀,否則 C 的更新不就丟失了?
所以,更新有個(gè)規(guī)則:更新數(shù)據(jù)都是先讀后寫(讀是更新語(yǔ)句執(zhí)行,不是我們手動(dòng)執(zhí)行),讀的就是當(dāng)前版本的值,叫當(dāng)前讀;而我們普通的查詢語(yǔ)句就叫快照讀。
因此,在更新時(shí),當(dāng)前讀讀到的是 age = 23,更新之后就成 24 啦。
2.3.2.2 select 當(dāng)前讀
除了更新語(yǔ)句,查詢語(yǔ)句如果加鎖也是當(dāng)前讀。如果把事務(wù) A 的查詢語(yǔ)句 select age from t where id = 2 改一下,加上鎖(lock in mode 或者 for update),也都可以得到當(dāng)前版本 4 返回的 age = 24
下面就是加了鎖的 select 語(yǔ)句:
select age from t where id = 2 lock in mode;
select age from t where id = 2 for update;
2.3.2.3 事務(wù) C 不馬上提交
假設(shè)事務(wù) C 不馬上提交,但是 age = 23 版本已生成。事務(wù) B 的更新將會(huì)怎么走呢?

事務(wù) C 還沒提交,寫鎖還沒釋放,但是事務(wù) B 的更新必須要當(dāng)前讀且必須加鎖。所以事務(wù) B 就阻塞了,必須等到事務(wù) C 提交,釋放鎖才能繼續(xù)當(dāng)前的讀。

2.3.3 RC(讀提交)下的結(jié)果
在讀提交隔離級(jí)別下,查詢只承認(rèn)在語(yǔ)句啟動(dòng)前就已經(jīng)提交完成的數(shù)據(jù);每一個(gè)語(yǔ)句執(zhí)行之前都會(huì)重新算出一個(gè)新的視圖。
注意:在上圖的表格中用于啟動(dòng)事務(wù)的是 start transaction with consistent snapshot 命令,它會(huì)創(chuàng)建一個(gè)持續(xù)整個(gè)事務(wù)的視圖。所以,在 RC 級(jí)別下,這命令其實(shí)不起作用。等效于普通的 start transaction(在執(zhí)行 sql 語(yǔ)句之前才算是啟動(dòng)了事務(wù))。所以,事務(wù) B 的更新其實(shí)是在事務(wù) C 之后的,它還沒真正啟動(dòng)事務(wù),而 C 已提交。
現(xiàn)在假設(shè):
事務(wù) A 開始前,只有一個(gè)活躍的事務(wù),ID = 2, 已提交的事務(wù)也就是插入數(shù)據(jù)的事務(wù) ID = 1 事務(wù) A、B、C 的事務(wù) ID 分別是 3、4、5
在這種隔離級(jí)別下,他們創(chuàng)建視圖的時(shí)刻如下:

根據(jù)上圖得,事務(wù) A 的視圖數(shù)組是 [2,3,4],但它的高水位是 6 或者更大(已創(chuàng)建事務(wù) ID + 1);事務(wù) B 的視圖數(shù)組是 [2,4];事務(wù) C 的視圖數(shù)組是 [2,5]。分析一波:
T4 時(shí)刻,B 讀數(shù)據(jù)都是從當(dāng)前版本讀起,過程是這樣的: 讀到當(dāng)前版本的 trx_id = 4,剛好是自己,可見 所以 age = 24 T5 時(shí)刻,A 讀數(shù)據(jù)都是從當(dāng)前版本讀起,過程是這樣的: 讀到當(dāng)前版本的 trx_id = 4,在自己一致性視圖范圍內(nèi)但包含 4,不可見 再往上讀到 trx_id = 5,在自己一致性視圖范圍內(nèi)但不包含 5,可見 所以 age = 23
03 巨人的肩膀
cnblogs.com/wyaokai/p/10921323.html time.geekbang.org/column/article/70562 zhuanlan.zhihu.com/p/117476959 cnblogs.com/xd502djj/p/6668632.html blog.csdn.net/article/details/109044141 blog.csdn.net/u014078930/article/details/99659272
04 總結(jié)
本文詳細(xì)聊了事務(wù)的方方面面,比如:四大特性、隔離級(jí)別、解決的并發(fā)問題、如何設(shè)置、查看隔離級(jí)別、如何啟動(dòng)事務(wù)等。除此以外,還深入了解了 RR 和 RC 兩個(gè)級(jí)別的隔離是怎么實(shí)現(xiàn)的?包括詳解 MVCC、undo log 和 read view 是怎么配合實(shí)現(xiàn) MVCC 的。最后還聊了快照讀、當(dāng)前讀等等??梢哉f(shuō),事務(wù)相關(guān)的知識(shí)點(diǎn)都在這了。看完這一篇還不懂的話,你來(lái)捶我呀!
往 期 推 薦 1、阿里云盤正式公測(cè)!免費(fèi)領(lǐng)1年云盤擴(kuò)容碼,速來(lái),先到先得! 2、牛逼!IntelliJ IDEA居然支持視頻聊天了~速來(lái)嘗鮮!快來(lái)沖一波 3、微信這些表情包,我可能再也不敢用了!你還用嗎? 4、知名國(guó)產(chǎn)網(wǎng)盤翻車?清空免費(fèi)用戶文件后,又開始清理付費(fèi)用戶資源 5、Chrome新功能曝光:你訪問的敏感網(wǎng)站可以自動(dòng)隱藏起來(lái) 6、萬(wàn)萬(wàn)沒想到,“紅孩兒”竟然做了程序員,還是CTO! 7、徒手?jǐn)]一個(gè)Spring Boot中的starter,解密自動(dòng)化配置,超級(jí)棒!

點(diǎn)分享

點(diǎn)收藏

點(diǎn)點(diǎn)贊

點(diǎn)在看

