前言
本文介紹什么是幻讀,幻讀存在的問題和解決方式,以及間隙鎖帶來的困擾,
什么是幻讀
什么是幻讀,有兩個條件:
- 必須是“當前讀”情況下才可能發生,“快照讀”不會出現
- 只有插入操作才算幻讀,更新和洗掉不算
幻讀的問題
幻讀會造成哪些問題?主要有兩點:
- 在同一個事務內執行的兩次或多次“當前讀”操作,結果不一致
- 資料和日志記錄不一致

執行上述事務操作后,binlog 記錄如下:
insert into t values(1,1,5); /*(1,1,5)*/
update t set c=5 where id=1; /*(1,5,5)*/
update t set d=100 where d=5;/*所有d=5的行,d改成100*/
update t set d=5 where id=0; /*(0,0,5)*/
update t set c=5 where id=0; /*(0,5,5)*/
執行的結果 id=1 這行是 (1,5,5),而 binlog 日志記錄的是 (1,5,100),可見資料和日志是不一致的,
如何解決幻讀
MySQL 在行鎖(Record Lock)的基礎之上,又引入了間隙鎖(Gap Lock),間隙鎖就是對兩條記錄之間的間隙上鎖,避免幻讀在已經上鎖情況下插入記錄,注意,間隙鎖之間不是互斥的,
行鎖和間隙鎖的結合稱之為 next-key 鎖,是一個前開后閉的區間,比如 (-∞,0]、(0,5]、(5,10]、(10,+suprenum],其中,suprenum 是 InnoDB 為每個索引添加的不存在的最大值,
間隙鎖的困擾
間隙鎖解決了幻讀的問題,但也帶來了事務并發度降低和死鎖的問題,比如下面的業務邏輯:
begin;
select * from t where id=N for update;
/*如果行不存在*/
insert into t values(N,N,N);
/*如果行存在*/
update t set d=N set id=N;
commit;
為什么會產生死鎖呢?看下面的事務流程:

session A 和 session B 都對 (5,10) 間隙上鎖,之后判斷記錄不存在,A 和 B 都進行記錄插入操作,然后因為對方對 (5,10) 間隙上鎖,導致鎖等待,進入死鎖狀態,MySQL 檢測到死鎖后,重啟 session A 事務,讓 session B 先執行成功,
那有沒有簡單的方法避免這個死鎖問題呢?
可以考慮將事務隔離級別設定為“提交讀”,避免間隙鎖同時提高事務的并發度,但是為了解決資料和日志的不一致問題,還需要將 binlog 格式設定為 row 模式,
參考
- [1] 幻讀是什么,幻讀有什么問題
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/302081.html
標籤:MySQL
