關於InnoDB事務的乙個「詭異」現象

2021-09-23 22:48:10 字數 3608 閱讀 8316

在隔離機制中,innodb預設採用的repeatable read 和mvcc機制保證在事務內部盡量保證邏輯一致性。但如下的現象依然讓人覺得不太合理。

1、復現

a)      表結構

create table `t` (

`a` int(11) not null default 『0′,

`b` int(11) default null,

primary key (`a`)

) engine=innodb default charset=gbk

表中2條記錄

| 1 |  100 |

| 4 |  400 |

+—+——+

b)      操作過程:開兩個session,操作序列如下

session 1

session 2

1)begin

2)select * from t;| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

3)insert into t vlaues(2, 200);

4)select * from t;| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

5)update t set b = 200 where a = 2;query ok, 0 rows affected (0.01 sec)

rows matched: 1  changed: 0  warnings: 0

6)select * from t;| 1 |  100 |

| 2 |  200 |

| 4 |  400 |

3 rows in set (0.01 sec)

從session 1整個過程看來,它試圖更新乙個不存在的記錄(a=2),結果更新成功,並且之後這個記錄可以訪問

2、分析

從其他正常的表象看來,在事務內,只要不涉及更新,事務外的任何更新都是不可見的。上面試驗中session 1內update之前執行的select *得到的結果仍是2條記錄。

雖然更新衝突時的策略見仁見智,但例子中的這個現象應該提供一種可以選擇的方式(至少應該允許配置)。

接下來的篇幅主要分析出現這種現象的原因,以及通過簡單修改實現如下的方式:對於查詢不可見的記錄,update操作不應該成功。

由於更新衝突策略的複雜性,本文不解決更多的問題,簡單比如:insert操作由於主鍵衝突的原因,插入依舊不允許。

3、原始碼相關

先來說明一下為什麼步驟4)中的查詢結果仍為2條記錄。

innodb內部每個事務開始時,都會有乙個事務id, 同時事務物件中還有乙個read_view變數,用於控制該事務可見的記錄範圍(mvcc)。對於每個訪問到的記錄行,會根據read_view的trx_id(事務id)與行記錄的trx_id比較,判斷記錄是否邏輯上可見。

session 2中插入的記錄不可見,原因即為session 1先於session 2,因此新插入的資料經過判斷,不在可見範圍內。對應的原始碼在row/row0sel.c [4040-4055].

發生的邏輯為

if(!lock_clust_rec_cons_read_sees(..)) //上乙個版本沒有這個記錄,放棄

注意到****現的rows matched: 1。 這裡是例子出現詭異的開始,也是根源。我們知道innodb內部更新資料實際上是「先查後改」,跟這個rows matched: 1結合起來,不難聯想到,在執行update操作是,在「查」的階段,事務能夠訪問到新插入的行。

猜測:問題出在,執行更新的時候,是否沒有判斷事務可見範圍?

事實上確實如此,源**上翻幾行可以看到,在行數[3897-4017-4071]這個if-else邏輯。

if (prebuilt->select_lock_type != lock_none)

執行查詢語句走的是else的邏輯,而控制版本可見範圍的**就在的位置中。

而當我們在session 1中執行update操作時,走的是if()的邏輯,這裡,沒有判斷版本可見範圍。

4、簡單修改

既然是因為update的「查」過程沒有檢查版本可見範圍造成,我們試著加上。

在row/row0sel.c[3907]行插入如下:

if(trx->read_view){ if (univ_likely(srv_force_recovery < 5) 

&& !lock_clust_rec_cons_read_sees(rec, clust_index, offsets, trx->read_view)) { 

rec_t*  old_vers;

err = row_sel_build_prev_vers_for_mysql(

trx->read_view, clust_index,

prebuilt, rec, &offsets, &heap,

&old_vers, &mtr);

if (err != db_success) {

goto lock_wait_or_error;

if (old_vers == null) {

goto next_rec;

新的執行結果為

session 1

session 2

1)begin

2)select * from t;| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

3)insert into t vlaues(2, 200);

4)select * from t;| 1 |  100 |

| 4 |  400 |

2 rows in set (0.01 sec)

5)update t set b = 200 where a = 2;query ok, 0 rows affected (0.01 sec)

rows matched: 0changed: 0  warnings: 0

6)select * from t;| 1 |  100 |

| 4 |  400 |

2 rows in set(0.01 sec)

重申:這個修改僅僅從本文的例子出發,達到「事務內查詢無法訪問的記錄,不能更新」這個目的, 其他更新衝突策略不在此範圍內。 僅作交流使用 -_-

關於InnoDB事務的乙個「詭異」現象

在隔離機制中,innodb預設採用的repeatable read 和mvcc機制保證在事務內部盡量保證邏輯一致性。但如下的現象依然讓人覺得不太合理。1 復現 a 表結構 create table t a int 11 not null default 0 b int 11 default null...

乙個看似詭異的錯誤

先上 客戶端 如下 include include include include include void str cli file stream,int fd int main int argc,char argv int socket fd 5 int i for i 0 i 5 i str ...

loadrunner 乙個詭異問題

最近使用loadrunner壓測乙個專案的時候,發現tps波動巨大 且平均值較低。使用jmeter壓測則沒有這個問題。經過多方排查發現乙個讓人極度費解的原因 原指令碼 指令碼其他 web submit data aaa action 此處為密文鏈結 事務判斷邏輯等 tps圖如下 修改後的 指令碼其他...