1.什麼是幻讀
幻讀指的是一個事務在前後兩次查詢同一個範圍的時候,後一次查詢看到了前一次查詢沒有看到的行
在可重複讀隔離級別下,普通的查詢是快照讀,是不會看到別的事務插入的數據的。因此,幻讀在“當前讀”下才會出現。
幻讀僅專指“新插入的行”
幻讀的例子:
- 假設有一個幻讀的情況:
- 表 T(id,a) 裏面只有一行數據 row(id=1,a=1,b=1)
時間順序 | 事務1 | 事務2 |
---|---|---|
time1 | select * from T where a = 1 for update (id=1,a=1) |
|
time2 | 什麼都不做 | insert(id=2,a=1); |
time3 | select * from T where a = 1 for update (id=1,a=1) (id=2,a=1) |
- time1的查詢結果是(id=1,a=1)
- time3的查詢結果是(id=1,a=1),(id=2,a=1)
- 同一個事務之中兩次查詢的結果不一致
2. 爲什麼要防止幻讀
If we regard a set of rows as a data item, the new phantom child would violate the isolation principle of transactions that a transaction should be able to run so that the data it has read does not change during the transaction.
因爲幻讀違背了事務隔離原則
例子中的情況,按道理來說應該在a=1
的數據上加鎖,但是這裏
事務在執行期間,事務讀取到的數據本應該是不會變化的
3. 怎麼幻讀是怎麼解決的
MySQL使用臨鍵鎖
來解決幻讀的問題,MySQL鎖介紹
臨鍵鎖
=記錄鎖
+間隙鎖
- 假設有個事務T1,進行了查詢(
當強讀
就是select ... for update
)操作 - MySQL會根據where條件,掃描索引(如果沒有普通索引則掃描聚簇索引)
- 掃描過的索引範圍上加上
記錄鎖
和間隙鎖
- 其他事務想要插入,得在插入操作之前加鎖
- 但是因爲where條件掃描過的範圍上已經被加上了
間隙鎖
和記錄鎖
,所以insert操作會被阻塞 - 必須等到事務T1釋放鎖,才能執行成功
例子
- 設有表t,表中只有三列(id,a,b),id是主鍵,a上有索引
- 表裏有2條數據(id=1,a=1,b=1)(id=10,a=10,b=10)
例1:
T1:
start transaction;
select * from t where a > 5 for update;
- 當前讀語句會在a的索引上加三個鎖
-
- 間隙鎖:
(1,10)開區間
- 間隙鎖:
-
- 記錄鎖:
a=10
- 記錄鎖:
-
- 間隙鎖:
(10,supremum)開區間
- 間隙鎖:
例2:
T1:
start transaction;
select * from t where a = 10 for update;
- 當前讀語句會在a的索引上加三個鎖
-
- 間隙鎖:
(1,10)開區間
- 間隙鎖:
-
- 記錄鎖:
a=10
- 記錄鎖:
-
- 間隙鎖:
(10,supremum)開區間
- 間隙鎖:
例3:
T1:
start transaction;
select * from t where b = 10 for update;
- 當前讀語句會在id聚簇索引上加5個鎖
-
- 間隙鎖:
(infimum,1)開區間
- 間隙鎖:
-
- 記錄鎖:
id=1
- 記錄鎖:
-
- 間隙鎖:
(1,10)開區間
- 間隙鎖:
-
- 記錄鎖:
id=10
- 記錄鎖:
-
- 間隙鎖:
(10,supremum)開區間
- 間隙鎖: