在隔離機制中,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圖如下 修改後的 指令碼其他...