8. transaction id,row trx_id,undo log,檢視陣列,當前讀

weixin_33797791發表於2019-01-18

事務號 transaction id, undo log

InnoDB 裡面每個事務有一個唯一的事務 ID, transaction id。它是在事務開始的時候(第一個操作)向 InnoDB 的事務系統申請的,是按申請順序嚴格遞增的。

資料表中的一行記錄,可能有多個版本 (row)。每個版本是每次事務更新資料的時候產生的, 每個版本有個row trx_id, 就是 transaction id . 同時,舊的資料版本要保留,並且在新的資料版本中,可以訪問到老的版本(通過undo log )

如: 當前最新版本是 V4,k 的值是 22,它是被 transaction id = 25 的事務更新的,因此它的 row trx_id 也是 25。


4930604-9406a29b763a8f42.png

U1,U2,U3 這些箭頭就是 undo log
V1、V2、V3 並不真實存在的,
而是每次需要的時候根據當前版本和 undo log (箭頭後退)計算出來的。

一致性檢視(read-view)

可重複讀 (mysql預設隔離級別):
資料庫裡在事務啟動時建立一個檢視,
(start transaction with consistent snapshot; 開始檢視, 這句只有在可重複讀時有效, 讀提交 時 只是普通的開始事務)
整個事務存在期間看到的值都用這個檢視。
就是 這個事務啟動的時刻為準,如果一個資料版本(row trx_id)是在啟動之前生成的,就不可見, 繼續往前找( 用undo log回退 )。

在實現上, InnoDB 為每個事務構造了一個陣列,用來儲存這個事務啟動瞬間,當前正在“活躍”(事務啟動並還沒提交)的所有事務 ID, 這些事務雖然在本事務之前,但是還沒提交,之後也可能更新

陣列裡面事務 ID 的最小值記為低水位
當前系統裡面已經建立過的事務 ID 的最大值加 1 記為高水位

高水位不一定是自己, 因為事務啟動申請自己的事務 和 構造這個陣列 之間有時間差, 可能有其他事務產生

start transaction;之後
第一次操作才產生檢視,
start transaction; 和select之間有其他事務的更新提交了, 會被算做之後的事務,納入本事務快照讀承認的快照

4930604-42895f3271544489.png

就是當前事務的 一致性檢視(read-view)

對於當前事務的,一行資料版本的 row trx_id,有以下幾種可能:

1.綠色: 我開始前提交的,直接可見
2.紅色: 我開始後開始的,直接不可見
3.黃色, 需要查陣列,
陣列裡面: 我開始時還沒提交 不可見
不在裡面就可見

因為先開始的事務,不一定先提交, 在黃色區域的 事務7在本事務開始前提交了(可見),在他之前的事務6卻沒有提交
這樣分3段就可以迅速判斷一下, 在黃色區域再進一步查資料

例子

事務 A 開始前,系統裡面只有一個活躍事務 ID 是 99;

事務 A、B、C 的版本號分別是 100、101、102,且當前系統裡只有這四個事務;

三個事務開始前,(1,1)這一行資料的 row trx_id 是 90。

事務 A(100) 陣列: [99,100], 低水位99 高水位100
事務 B(101) 陣列: [99,100,101], 低水位99 ,高水位102
事務 C(102) 陣列: [99,100,101,102]. 低水位99.高水位102


4930604-e80628eacd5b71b3.png

A事務(100) 陣列: [99,100]:
雖然最先開始,但是做了其他的事,最後來讀取這行
查詢語句的讀資料流程是這樣的:

找到最新的 (1,3) 的時候, row trx_id=101,比高水位100大,不可見;
undo log 往前找,找到上一個歷史版本,一看 row trx_id=102,比高水位100大,不可見;
undo log 往前找,找到了(1,1),它的 row trx_id=90,比低水位99小,處於綠色區域,可見
A事務(100) 什麼時候讀都是(1,1),看到這行資料的結果都是一致的,所以我們稱之為一致性讀

當前讀

更新資料, 不能再讀檢視的資料來更新了, 不然就不對了, 因此只能讀當前的值,稱為“當前讀”(current read)
當前讀: 必須讀最新版本, 如果最新版本的另一個事務沒提交, 本事務只能堵塞 等他提交 再接著執行, 並且對自己讀到的資料加鎖 不讓其他事務改
select...lock in share mode ,(共享鎖)
select...for update(排他鎖)
update , delete , insert(排他鎖)
這些都是當前讀,就是加鎖, 會阻塞

事務B(101),如果在更新之前插一句讀取此行的資料 會讀到1, 因為事務c(102)的修改對它不可見,
但是事務B(101) 的+1修改, 必須基於最新的資料(1,2), 不然就丟失事務c(102)的更新了


4930604-7d1dbc0a3ee5baa8.png
更新的過程,包括當前讀current read

更新資料都是先讀後寫的,而這個讀,只能讀當前的值,稱為“當前讀”(current read)。

select 語句如果加鎖,也是當前讀
事務 A (100)的查詢語句 select * from t where id=1 修改一下,加上
lock in share mode :讀鎖(S 鎖,共享鎖)
mysql> select k from t where id=1 lock in share mode;
for update:寫鎖(X 鎖,排他鎖)
mysql> select k from t where id=1 for update;
也都可以讀到版本號是 101 的資料,返回的 k 的值是 3。

事務 B (101),查詢時,一看自己的版本號是 101,最新資料的版本號也是 101,是自己的更新,可以直接使用,所以查詢得到的 k 的值是 3。

行鎖

如果上面事務C 改成 update後不馬上commit, 在事務B update後 事務C 才commit

事務 C’沒提交,也就是說 (1,2) 這個版本上的寫鎖還沒釋放。而事務 B 是當前讀(更新),必須要讀最新版本,而且必須加鎖,因此就被鎖住了,必須等到事務 C’釋放這個鎖,才能繼續它的當前讀。

4930604-ac076ae282a2cc2f.png
鎖等待

讀提交情況下

每一個語句執行前都會重新算出一個新的檢視。
"start transaction with consistent snapshot; " 無效, 只相當於start transaction開始事務

4930604-c4fb965e79129b1c.png

事務 A .查詢語句的檢視陣列 執行這個語句的時候建立的,時序上 (1,2)、(1,3) 的生成時間都在建立這個檢視陣列的時刻之前。但是:

(1,3) 還沒提交,屬於情況 1,不可見;
(1,2) 提交了,屬於情況 3,可見。
所以,這時候事務 A 查詢語句返回的是 k=2。

事務 B 查詢結果 k=3( 自己改的)

相關文章