前面我寫了很多Mysql相關的知識點榜聂,到這一篇稍微可以串一下了搞疗,從SQL執(zhí)行流程、MVCC到鎖须肆,很多時候可能覺得對于間隙鎖和Next-Key Lock好像已經理解了匿乃,但是好像又覺得理解差那么一點意思,這篇文章從頭來梳理一下概念豌汇,明確一下這些知識幢炸。
鎖
首先,對于Mysql來說實現了兩種行級鎖:
共享鎖:允許事務讀一行數據拒贱,一般記為S宛徊,也稱為讀鎖
排他鎖:允許事務刪除或者更新一行數據,一般記為X柜思,也稱為寫鎖
關于讀寫鎖的互斥性岩调,應該都很清楚,讀鎖只能和讀鎖兼容赡盘,其他場景都無法兼容号枕,這里不再贅述吧。
隔離級別
繼續(xù)回顧下關于Mysql的4個隔離級別:
讀未提交Read Uncommitted:能讀到其他事務還沒有提交的數據陨享,這種現象叫做臟讀葱淳。
讀已提交Read Committed:只會讀取其他事務已經提交的數據,所以不會產生RC的臟讀問題抛姑。所以又帶來一個問題叫做不可重復讀赞厕,一個事務中兩次一樣的SQL查詢可能查到的結果不一樣。
可重復讀Repeatable Read:RR是Mysql的默認隔離級別定硝,一個事務中兩次SQL查詢總是會查到一樣的結果皿桑,不存在不可重復讀的問題,但是還是會有幻讀的問題。
串行Serializable:串行場景沒有任何問題诲侮,完全串行化的操作镀虐,讀加讀鎖,寫加寫鎖沟绪。
幻讀刮便、Next-Key Lock、MVCC
簡單的回顧完了基礎绽慈,那么我們看看RR級別下還會存在的幻讀到底是什么問題恨旱,Mysql官方文檔這樣描述的:
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a
SELECT
is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
翻譯過來就是,幻讀指的是同一事務下坝疼,不同的時間點搜贤,同樣的查詢,得到不同的行記錄的集合裙士。
如果說一個select
執(zhí)行了兩次入客,但是第二次比第一次多出來行記錄,這就是幻讀腿椎。
所以,對于幻讀來說那一定是新增插入的數據夭咬!
比如說在一個事務內啃炸,先查詢select * from user where age=10 for update
,得到的結果是id為[1,2,3]的記錄卓舵,再次執(zhí)行查詢南用,得到了結果為[1,2,3,4]的記錄,這是幻讀掏湾。
那怎么解決幻讀的問題裹虫?以前我在文章里說解決幻讀的原理是MVCC(MVCC原理看這里)很多網上的文章也有這么寫的,其實不能說錯融击,但是肯定也是不太對的筑公,準確地來說應該是通過MVCC+Next-Key Lock的方式才解決了幻讀的問題。
對于MVCC中的讀可以分為兩種尊浪,分別叫做快照讀和當前讀(這個當前讀的說法我在書里翻了半天也沒有找到匣屡,但是看網上一堆資料和大佬都叫當前讀,那么我們就叫當前讀吧拇涤,你知道的話可以告訴我哪本書有這個稱呼捣作,Mysql我只看見Lock reading或者鎖定讀的叫法,有的也說鎖定讀就是當前讀鹅士,但是并沒有找到當前讀這種稱呼的出處在哪兒)券躁。
快照讀就是簡單的select
查詢,查詢的都是快照版本,這個場景下因為都是基于MVCC來查詢快照的某個版本也拜,所以不會存在幻讀的問題旭贬,也可以認為是解決了幻讀的方案之一,對于RC級別來說搪泳,因為每次查詢都重新生成一個read view稀轨,也就是查詢的都是最新的快照數據,所以會可能每次查詢到不一樣的數據岸军,造成不可重復讀奋刽,而對于RR級別來說只有第一次的時候生成read view,查詢的是事務開始的時候的快照數據艰赞,所以就不存在不可重復讀的問題佣谐,當然就更不可能有幻讀的問題了。
所以方妖,現在我們說幻讀狭魂,其實不是指快照讀的場景,而是指的是當前讀的場景党觅。
當前讀指的是lock in share mode
雌澄、for update
、insert
杯瞻、update
镐牺、delete
這些需要加鎖的操作。對于MVCC來說就是解決的快照讀的場景魁莉,而對于當前讀那么就是Next-Key Lock要解決的事情睬涧。
那么Next-Key Lock是什么?怎么解決的幻讀旗唁?
行鎖有寫鎖X和讀鎖S兩種畦浓,實際上行鎖有3種實現算法,Next-Key Lock是其中之一检疫。
第一種叫做Record Lock讶请,字面意思,行記錄的鎖电谣,實際上指的是對索引記錄的鎖定秽梅。
比如執(zhí)行語句select * from user where age=10 for update
,將會鎖住user
表所有age=10
的行記錄剿牺,所有對age=10
的記錄的操作都會被阻塞企垦。
第二種都比較熟悉,叫做Gap Lock晒来,也就是間隙鎖钞诡,它用于鎖定的索引之間的間隙,但是不會包含記錄本身。
比如語句select * from user where age>1 and age<10 for update
荧降,將會鎖住age
在(1,10)的范圍區(qū)間接箫,此時其他事務對該區(qū)間的操作都會被阻塞。
間隙鎖是可重復讀RR隔離級別下特有的朵诫,另外還有幾種場景也會不使用間隙鎖辛友。
事務隔離級別設置為不可重復讀RC ,這樣肯定沒有間隙鎖了剪返。
Innodb_locks_unsafe_for_binlog
設置為1另外一種情況適用于主鍵索引或者唯一索引的等值查詢條件废累,比如
select * from user where id=1
,id
是主鍵索引脱盲,這樣只使用Record Lock就可以了邑滨,因為能唯一鎖定一條記錄,所以沒有必要再加間隙鎖了钱反,這是鎖降級的過程掖看。
而第三種Next-Key Lock實際上就是相當于Record Lock+Gap Lock的組合。比如索引有10面哥,20哎壳,30幾個值,那么被鎖住的區(qū)間可能會是(-∞,10]幢竹,(10,20]耳峦,(20,30],(30,+∞)焕毫。
解決幻讀
上一篇關于更新SQL執(zhí)行過程我們已經對這個基礎有了一定的了解,在這里我們去掉和這里內容無關的一些日志的細節(jié)驶乾,把給數據加鎖的流程加入進去邑飒,這樣通過SQL執(zhí)行可以更好地理解Next-Key Lock到底是如何解決幻讀的,執(zhí)行過程如下:
- 首先第一步Server層會來查詢數據
- 存儲引擎根據查詢條件查到數據之后對數據進行加鎖级乐,Record Lock或者間隙鎖疙咸,然后返回數據
- Server層拿到數據之后調用API去存儲引擎更新數據
- 最后存儲引擎返回結果,流程結束
搞一張表說明一下风科,user
表有4個字段撒轮,id
是主鍵索引,name
是唯一索引贼穆,age
是普通索引题山,city
沒有索引,然后插入一些測試數據故痊,下面區(qū)分一下幾種情況來說明是怎么加Next-Key Lock的顶瞳,然后就知道為啥會沒有幻讀的問題了。
沒有索引
更新語句update user set city='nanjing' where city='wuhan'
會發(fā)生什么?
因為city
是沒有索引的慨菱,所以存儲引擎只能給所有的記錄都加上鎖焰络,然后把數據都返回給Server層,然后Server層把city
改成nanjing
符喝,再更新數據闪彼。
因此,首先Record Lock會鎖住現有的7條記錄协饲,間隙鎖則會對主鍵索引的間隙全部加上間隙鎖畏腕。
所以,更新的時候沒有索引是非炒鸦可怕的一件事情郊尝,相當于把整個表都給鎖了,那表都給鎖了當然不存在幻讀了战惊。
普通索引
我們再假設一個語句select * from user where age=20 for update
流昏。
因為age
是一個普通索引,存儲引擎根據條件過濾查到所有匹配age=20
的記錄吞获,給他們加上寫鎖况凉,間隙鎖會加在(10,20),(20,30)的區(qū)間上各拷,因此現在無論怎樣都無法插入age=20
的記錄了
為什么要鎖定這兩個區(qū)間刁绒?如果不鎖定這兩個區(qū)間的話,那么還能插入比如id=11,age=20
或者id=21,age=20
的記錄烤黍,這樣就存在幻讀了知市。
(那實際上寫鎖不光是在會加在age
普通索引上,還會加在主鍵索引上速蕊,因為數據都是在主鍵索引下對吧嫂丙,這個肯定也要加鎖的,為了看起來簡單點规哲,就不畫出來了)
唯一&主鍵索引
如果查詢的是唯一索引又會發(fā)生什么呢跟啤?比如有查詢語句select * from user where name='b' for update
。
上面我們提到過唉锌,如果是唯一索引或者主鍵索引的話隅肥,并且是等值查詢,實際上會發(fā)生鎖降級袄简,降級為Record Lock腥放,就不會有間隙鎖了。
因為主鍵或者唯一索引能保證值是唯一的痘番,所以也就不需要再增加間隙鎖了捉片。
很顯然平痰,是無法插入name=b
的的記錄的,也不存在幻讀問題伍纫。
如果是范圍查詢比如id>1 and id<11
呢宗雇,實際上也是一樣的鎖定方式,不再贅述莹规。
相比稍微有點不同的是上面也說過赔蒲,唯一索引不光鎖定唯一索引,還會鎖定主鍵索引良漱,主鍵索引的話只要索引主鍵索引就行了舞虱。
總結
那最后說了這么多,RR級別下不是都已經解決了幻讀的問題嗎母市,怎么還說有幻讀的問題呢矾兜?
關于這個問題,可以看看這個報出的BUGhttps://bugs.mysql.com/bug.php?id=63870患久,回復說了這不是BUG椅寺,這是符合隔離規(guī)范的設計,有興趣的自己看看吧。