這次的內容是學習極客時間的MySQL實戰45講課程中的實驗和總結,具體課程是第21篇文章。
首先是課程中的總結的加鎖規則,兩個“原則”、兩個“優化”和一個“bug”(可重復讀的事務隔離級別下)。
原則 1:加鎖的基本單位是 next-key lock。希望你還記得,next-key lock 是前開后閉區間。 原則 2:查找過程中訪問到的對象才會加鎖。 優化 1:索引上的等值查詢,給唯一索引加鎖的時候,next-key lock 退化為行鎖。 優化 2:索引上的等值查詢,向右遍歷時且最后一個值不滿足等值條件的時候,next-key lock 退化為間隙鎖。 一個 bug:唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止。
然后是這次用到的表和數據
CREATE TABLE `t` ( `id` int(11) NOT NULL, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), KEY `c` (`c`) ) ENGINE=InnoDB; insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25);
再然后就是實際例子了
1.在主鍵上,訪問不存在的數據
根據原則1加的next-key lock鎖是id(5,10],因為id等於7的數據並不存在,所以不滿足優化1,根據優化2將會退化為間隙鎖id(5,10),從上面的實際例子中也能看出來,只有插入id=6的數據在等待鎖,id=4、11、10、5的數據在插入和更新的時候都正常。
2.在主鍵上,訪問存在的數據
這個和上一個例子類似區別是滿足優化1所以就從next-key lock鎖id(5,10],退化為id=5的行鎖,所以id=6,4的數據可以插入進去,只有id=5的數據在更新的時候等待鎖。
3.在主鍵上 范圍查詢時會根據掃描的情況加鎖
這種情況就是
- 根據原則1加的是next-key lock鎖id(0,5],
- 根據bug知道掃描到了10所以還加了next-key lock鎖id(0,10]
- 再然后根據優化2,因為這里有等值查詢(id=5),所以next-key lock鎖id(0,5]退化成了id=5行鎖
所以id=4、11的數據可以插入,而id=6、10的數據的更新和插入的時候需要等待鎖
4.在主鍵上 范圍查詢時會根據掃描的情況加鎖2
分析:
- 根據原則1加上了id(5,10],id(10,15]的next-key lock鎖
- 根據優化1id(10,15]的next-key lock鎖退化成行鎖id=10
但是實際情況上id=11的數據插入也需要等待鎖,這就是上面規則說的bug唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止,這里的id<=10,會一直掃描到id=15,所以會加上(10,15]的next-key lock鎖
5.在非唯一索引上等值查詢的情況
這其實是兩個例子,因為只有一個區別就放一起了,先按照規則來分析
- 根據原則1就能知道都加上了c(0,5],c(5,10]的next-key lock鎖
- 根據優化2c(5,10]的next-key lock鎖會退化為c(5,10)的間隙鎖
這兩個加鎖的區別就是對於id=5的鎖的問題,lock in share mode沒有對id=5加鎖,所以可以得到的信息是,lock in share mode只鎖覆蓋索引,但是如果是 for update 就不一樣了。 執行 for update 時,系統會認為你接下來要更新數據,因此會順便給主鍵索引上滿足條件的行加上行鎖。
6.在非唯一索引上范圍查詢的情況
這個和例3類似,區別是這里不是主鍵,也就是說不是唯一索引,所以這里的分析過程就是
- 根據原則1加上了c(0,5],c(5,10]的next-key lock鎖
- 因為不是唯一索引不滿足優化1,所以c(0,5]不會退化成c=5的行鎖,所以最終結果就是c(0,5],c(5,10]
7.在非唯一索引范圍查詢時 會根據掃描的情況加鎖
這個例子和例5類似,區別就是limit的問題,因為規則中總結的bug可以解釋,limit 1只需要掃描一行,所以c(5,10)的間隙鎖就沒加上,而limit 2就再次證明了,如果需要返回兩條數據需要再掃描就加上了c(5,10)的間隙鎖
先來分析沒有order by的情況:
- 根據原則1c(5,10]、c(10,15]、c(15,20]都加上了next-key lock鎖
- 根據優化2c(15,20]退化為間隙鎖,c(15,20)
- 根據bugc<=15會一直掃描到20,所以c(10,15]的鎖還是會加上
- 所以最終結果是c(5,10]、c(10,15]、c(15,20]都加上了鎖
然后是有order by desc的情況:
- 因為order by所以第一個定位的是索引上c=15的數據行,所以會加上c(10,15]的next-key lock鎖和c(15,20)的間隙鎖
- 因為向左掃描,掃描到了c=5才停下來,所以加上了c(0,5]、c(5,10]的next-key lock鎖
- 因為c=5、c=10、c=15都有值,且select id,所以這三行的id索引上加了3個行鎖
- 所以最終情況是c(5,20),id=5、10、15的鎖
總結
這上面總結的規則,其實沒啥道理可言,MySQL代碼就是這樣寫的,加鎖的意義是實際上為了保持數據的一致性和語義的正確,從例子4我們就能看到明明我們想鎖的是5-10,但是因為bug(10,15]也加上了鎖。所以考慮MySQL的這個加鎖問題的時候,最好還是根據掃描的情況來考慮
然后是一些細節的記錄:
- <=a 怎么判斷是間隙鎖還是行鎖,這個要看掃描的過程來看,要先找到這個a,這個用等值判斷來判定,然后在索引中掃描的過程使用范圍查找來判斷,就像最后一個例子中的order by desc一樣,向左向右掃描出現的情況也不一樣
- 加鎖的情況其實不僅僅是通過for update 和lock in share mode來決定的,還得看查詢的結果,如果是select * 查到了主鍵,一樣會鎖主鍵索引,這和訪問的對象有關,訪問到了就加鎖,就像掃描一樣
- 有行才會加行鎖,如果查詢沒有命中行就加next-key lock鎖,然后如果是等值查詢還需要根據優化2來判斷怎么加間隙鎖