要知道什麼是幻讀,首先要知道以下四點:
一、幻讀定義
幻讀是指在同一個事務中,存在前後兩次查詢同一個範圍的資料,但是第二次查詢卻看到了第一次查詢沒看到的行,一般情況下特指事務執行中新增的其他行。
二、幻讀示例
測試表資料:
mysql> select * from LOL;
+----+--------------+--------------+-------+
| id | hero_title | hero_name | price |
+----+--------------+--------------+-------+
| 1 | 刀鋒之影 | 泰隆 | 6300 |
| 2 | 迅捷斥候 | 提莫 | 6300 |
| 3 | 光輝女郎 | 拉克絲 | 1350 |
| 4 | 發條魔靈 | 奧莉安娜 | 6300 |
| 5 | 至高之拳 | 李青 | 6300 |
| 6 | 無極劍聖 | 易 | 450 |
| 7 | 疾風劍豪 | 亞索 | 6300 |
+----+--------------+--------------+-------+
7 rows in set (0.00 sec)
下面是一個出現幻讀情況示例,我們一起來看一下;

可以看到,session A 裡執行了三次查詢,分别是 Q1、Q2 和 Q3。它們的 SQL 語句相同,都是 select * from LOL where price=450 for update。
這個語句的意思你應該很清楚了,查所有 price=450 的行,而且使用的是目前讀,并且加上寫鎖。現在,我們來看一下這三條 SQL 語句,分别會傳回什麼結果。
Q1 隻傳回 “無極劍聖” 這一行;
在 T2 時刻,session B 把 “疾風劍豪” 這一行的 price 值改成了 450,是以 T3 時刻 Q2 查出來的是 “無極劍聖” 和 “疾風劍豪” 這兩行;
在 T4 時刻,session C 又插入一行 (10,‘雪人騎士’,‘努努’,‘450’),是以 T5 時刻 Q3 查出來 price = 450 的是"無極劍聖" 、“疾風劍豪” 和 “雪人騎士” 這三行。
其中,Q3 讀到 (10,‘雪人騎士’,450) 這一行的現象,被稱為“幻讀”。也就是說,幻讀指的是一個事務在前後兩次查詢同一個範圍的時候,後一次查詢看到了前一次查詢沒有看到的行。
三、幻讀出現的場景
幻讀出現在可重複讀(RR)隔離級别下,普通的SELECT查詢就是快照讀,是不會看到别的事務插入的資料的。
是以,幻讀在“目前讀”下才會出現。(目前讀會生成行鎖,但行鎖隻能鎖定存在的行,針對新插入的操作沒有限定)
上面 session B 的修改結果,被 session A 之後的 select 語句用“目前讀”看到,不能稱為幻讀。幻讀僅專指“新插入的行”。
因為這三個查詢都是加了 for update,都是目前讀。而目前讀的規則,就是要能讀到所有已經送出的記錄的最新值。并且,session B 和 sessionC 的兩條語句,執行後就會送出,是以 Q2 和 Q3 就是應該看到這兩個事務的操作效果,而且也看到了,這跟事務的可見性規則并不沖突。
四、解決幻讀問題的必要性
在高并發資料庫系統中,需要保證事務與事務之間的隔離性,還有事務本身的一緻性。
如何解決幻讀
一、原了解讀
那麼幻讀能僅通過行鎖解決麼?答案是否定的,如上面示例,首先說明一下,select xx for update(目前讀)是将所有條件涉及到的(符合where條件)行加上行鎖。
但是,就算我在select xx for update 事務開啟時将所有的行都加上行鎖。那麼也鎖不住Session C新增的行,因為在我給資料加鎖的時刻,壓根就還沒有新增的那行,自然也不會給新增行加上鎖。
是以要解決幻讀,就必須得解決新增行的問題。
現在你應該明白了,産生幻讀的原因是:行鎖隻能鎖住行,但是新插入記錄這個動作,要更新的是記錄之間的“間隙”。是以,為了解決幻讀問題,InnoDB 隻好引入新的鎖,也就是間隙鎖 (Gap Lock)。
顧名思義,間隙鎖,鎖的就是兩個值之間的空隙。比如文章開頭的表 LOL,初始化插入了 7 個記錄,這就産生了 8 個間隙。
二、next-key lock
這樣,當你執行 select * from LOL where hero_title = ‘疾風劍豪’ for update 的時候,就不止是給資料庫中已有的 7 個記錄加上了行鎖,還同時加了 8 個間隙鎖。
這樣就確定了無法再插入新的記錄,也就是Session C在T4新增(10,‘雪人騎士’,‘努努’,‘450’) 行時,由于ID大于7,被間隙鎖(7,+∞)鎖住。