30 - MySQL深入理解加鎖規(guī)則

加鎖規(guī)則如下洽洁,這個規(guī)則中粘都,包含了兩個“原則”肤晓、兩個“優(yōu)化”和一個“bug”:

  • 原則 1:加鎖的基本單位是 next-key lock氛雪。next-key lock 是前開后閉區(qū)間。
  • 原則 2:查找過程中訪問到的對象才會加鎖卦尊。
  • 優(yōu)化 1:索引上的等值查詢叛拷,給唯一索引加鎖的時候,next-key lock 退化為行鎖岂却。
  • 優(yōu)化 2:索引上的等值查詢忿薇,向右遍歷時且最后一個值不滿足等值條件的時候,next-key lock 退化為間隙鎖躏哩。
  • 一個 bug:唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止署浩。

接下來,我們的討論還是基于下面這個表 t:

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);

不等號條件里的等值查詢

  • 有同學(xué)對“等值查詢”提出了疑問:等值查詢和“遍歷”有什么區(qū)別扫尺?為什么我們文章的例子里面筋栋,where 條件是不等號,這個過程里也有等值查詢正驻?
  • 我們一起來看下這個例子二汛,分析一下這條查詢語句的加鎖范圍:
begin;
select * from t where id>9 and id<12 order by id desc for update;
  • 利用上面的加鎖規(guī)則,我們知道這個語句的加鎖范圍是主鍵索引上的 (0,5]拨拓、(5,10]和 (10, 15)肴颊。也就是說,id=15 這一行渣磷,并沒有被加上行鎖婿着。為什么呢?
  • 我們說加鎖單位是 next-key lock醋界,都是前開后閉區(qū)間竟宋,但是這里用到了優(yōu)化 2,即索引上的等值查詢形纺,向右遍歷的時候 id=15 不滿足條件丘侠,所以 next-key lock 退化為了間隙鎖 (10, 15)。
  • 但是逐样,我們的查詢語句中 where 條件是大于號和小于號蜗字,這里的“等值查詢”又是從哪里來的呢打肝?
  • 要知道,加鎖動作是發(fā)生在語句執(zhí)行過程中的挪捕,所以你在分析加鎖行為的時候粗梭,要從索引上的數(shù)據(jù)結(jié)構(gòu)開始。這里级零,再把這個過程拆解一下断医。
  • 如下圖所示,是這個表的索引 id 的示意圖奏纪。
索引id示意圖
  1. 首先這個查詢語句的語義是 order by id desc鉴嗤,要拿到滿足條件的所有行,優(yōu)化器必須先找到“第一個 id<12 的值”序调。
  2. 這個過程是通過索引樹的搜索過程得到的醉锅,在引擎內(nèi)部,其實是要找到 id=12 的這個值炕置,只是最終沒找到荣挨,但找到了 (10,15) 這個間隙男韧。
  3. 然后向左遍歷朴摊,在遍歷過程中,就不是等值查詢了此虑,會掃描到 id=5 這一行甚纲,所以會加一個 next-key lock (0,5]。
  • 也就是說朦前,在執(zhí)行過程中介杆,通過樹搜索的方式定位記錄的時候,用的是“等值查詢”的方法韭寸。

等值查詢的過程

  • 一起思考下春哨,下面這個語句的加鎖范圍是什么?
begin;
select id from t where c in(5,20,10) lock in share mode;
  • 這條查詢語句里用的是 in恩伺,我們先來看這條語句的 explain 結(jié)果赴背。
in語句的explain結(jié)果
  • 可以看到,這條 in 語句使用了索引 c 并且 rows=3晶渠,說明這三個值都是通過 B+ 樹搜索定位的凰荚。
  • 在查找 c=5 的時候,先鎖住了 (0,5]褒脯。但是因為 c 不是唯一索引便瑟,為了確認(rèn)還有沒有別的記錄 c=5,就要向右遍歷番川,找到 c=10 才確認(rèn)沒有了到涂,這個過程滿足優(yōu)化 2脊框,所以加了間隙鎖 (5,10)。
  • 同樣的养盗,執(zhí)行 c=10 這個邏輯的時候缚陷,加鎖的范圍是 (5,10] 和 (10,15);執(zhí)行 c=20 這個邏輯的時候往核,加鎖的范圍是 (15,20] 和 (20,25)箫爷。
  • 通過這個分析,我們可以知道聂儒,這條語句在索引 c 上加的三個記錄鎖的順序是:先加 c=5 的記錄鎖虎锚,再加 c=10 的記錄鎖,最后加 c=20 的記錄鎖衩婚。
  • 你可能會說窜护,這個加鎖范圍,不就是從 (5,25) 中去掉 c=15 的行鎖嗎非春?為什么這么麻煩地分段說呢柱徙?
    • 這些鎖是“在執(zhí)行過程中一個一個加的”,而不是一次性加上去的奇昙。
    • 理解了這個加鎖過程之后护侮,我們就可以來分析下面例子中的死鎖問題了。
  • 如果同時有另外一個語句储耐,是這么寫的:
select id from t where c in(5,20,10) order by c desc for update;
  • 此時的加鎖范圍羊初,又是什么呢?
    • 我們現(xiàn)在都知道間隙鎖是不互鎖的什湘,但是這兩條語句都會在索引 c 上的 c=5长赞、10、20 這三行記錄上加記錄鎖闽撤。
    • 這里你需要注意一下得哆,由于語句里面是 order by c desc, 這三個記錄鎖的加鎖順序哟旗,是先鎖 c=20贩据,然后 c=10,最后是 c=5热幔。
  • 也就是說乐设,這兩條語句要加鎖相同的資源,但是加鎖順序相反绎巨。當(dāng)這兩條語句并發(fā)執(zhí)行的時候近尚,就可能出現(xiàn)死鎖。
  • 關(guān)于死鎖的信息场勤,MySQL 只保留了最后一個死鎖的現(xiàn)場戈锻,但這個現(xiàn)場還是不完備的歼跟。接下來我們分析下這個死鎖場景

怎么看死鎖?

  • 下圖是在出現(xiàn)死鎖后格遭,執(zhí)行 show engine innodb status 命令得到的部分輸出哈街。這個命令會輸出很多信息,有一節(jié) LATESTDETECTED DEADLOCK拒迅,就是記錄的最后一次死鎖信息骚秦。
死鎖現(xiàn)場
  • 我們來看看這圖中的幾個關(guān)鍵信息。
  1. 這個結(jié)果分成三部分:
    • (1) TRANSACTION璧微,是第一個事務(wù)的信息作箍;
    • (2) TRANSACTION,是第二個事務(wù)的信息前硫;
    • WE ROLL BACK TRANSACTION (1)胞得,是最終的處理結(jié)果,表示回滾了第一個事務(wù)屹电。
  2. 第一個事務(wù)的信息中:
    • WAITING FOR THIS LOCK TO BE GRANTED阶剑,表示的是這個事務(wù)在等待的鎖信息;
    • index c of table test.t危号,說明在等的是表 t 的索引 c 上面的鎖牧愁;
    • lock mode S waiting 表示這個語句要自己加一個讀鎖,當(dāng)前的狀態(tài)是等待中葱色;
    • Record lock 說明這是一個記錄鎖递宅;
    • n_fields 2 表示這個記錄是兩列娘香,也就是字段 c 和主鍵字段 id苍狰;
    • 0: len 4; hex 0000000a; asc ;; 是第一個字段,也就是 c烘绽。值是十六進(jìn)制 a淋昭,也就是 10;
    • 1: len 4; hex 0000000a; asc ;; 是第二個字段安接,也就是主鍵 id翔忽,值也是 10;
    • 這兩行里面的 asc 表示的是盏檐,接下來要打印出值里面的“可打印字符”歇式,但 10 不是可打印字符,因此就顯示空格胡野。
    • 第一個事務(wù)信息就只顯示出了等鎖的狀態(tài)材失,在等待 (c=10,id=10) 這一行的鎖。
    • 當(dāng)然你是知道的硫豆,既然出現(xiàn)死鎖了龙巨,就表示這個事務(wù)也占有別的鎖笼呆,但是沒有顯示出來。別著急旨别,我們從第二個事務(wù)的信息中推導(dǎo)出來诗赌。
  3. 第二個事務(wù)顯示的信息要多一些:
    • “ HOLDS THE LOCK(S)”用來顯示這個事務(wù)持有哪些鎖;
    • index c of table test.t 表示鎖是在表 t 的索引 c 上秸弛;
    • hex 0000000a 和 hex 00000014 表示這個事務(wù)持有 c=10 和 c=20 這兩個記錄鎖铭若;
    • WAITING FOR THIS LOCK TO BE GRANTED,表示在等 (c=5,id=5) 這個記錄鎖递览。
  • 從上面這些信息中奥喻,我們就知道:
    • “l(fā)ock in share mode”的這條語句,持有 c=5 的記錄鎖非迹,在等 c=10 的鎖环鲤;
    • “for update”這個語句,持有 c=20 和 c=10 的記錄鎖憎兽,在等 c=5 的記錄鎖冷离。
  • 因此導(dǎo)致了死鎖。這里纯命,我們可以得到兩個結(jié)論:
    • 由于鎖是一個個加的西剥,要避免死鎖,對同一組資源亿汞,要按照盡量相同的順序訪問瞭空;
    • 在發(fā)生死鎖的時刻,for update 這條語句占有的資源更多疗我,回滾成本更大咆畏,所以 InnoDB 選擇了回滾成本更小的 lock in share mode 語句,來回滾吴裤。

怎么看鎖等待旧找?

  • 看完死鎖,我們再來看一個鎖等待的例子麦牺。
delete導(dǎo)致間隙變化
  • 可以看到钮蛛,由于 session A 并沒有鎖住 c=10 這個記錄,所以 session B 刪除 id=10 這一行是可以的剖膳。但是之后魏颓,session B 再想 insert id=10 這一行回去就不行了。
  • 現(xiàn)在我們一起看一下此時 show engine innodb status 的結(jié)果吱晒,看看能不能給我們一些提示甸饱。鎖信息是在這個命令輸出結(jié)果的 TRANSACTIONS 這一節(jié)。
鎖等待信息
  • 我們來看幾個關(guān)鍵信息枕荞。
  1. index PRIMARY of table test.t 柜候,表示這個語句被鎖住是因為表 t 主鍵上的某個鎖搞动。
  2. lock_mode X locks gap before rec insert intention waiting 這里有幾個信息:
    • insert intention 表示當(dāng)前線程準(zhǔn)備插入一個記錄,這是一個插入意向鎖渣刷。為了便于理解鹦肿,你可以認(rèn)為它就是這個插入動作本身。
    • gap before rec 表示這是一個間隙鎖辅柴,而不是記錄鎖箩溃。
  3. 那么這個 gap 是在哪個記錄之前的呢?接下來的 0~4 這 5 行的內(nèi)容就是這個記錄的信息碌嘀。
  4. n_fields 5 也表示了涣旨,這一個記錄有 5 列:
    • 0: len 4; hex 0000000f; asc ;; 第一列是主鍵 id 字段,十六進(jìn)制 f 就是 id=15股冗。所以霹陡,這時我們就知道了,這個間隙就是 id=15 之前的止状,因為 id=10 已經(jīng)不存在了烹棉,它表示的就是 (5,15)栋艳。
    • 1: len 6; hex 000000000513; asc ;; 第二列是長度為 6 字節(jié)的事務(wù) id惨险,表示最后修改這一行的是 trx id 為 1299 的事務(wù)。
    • 2: len 7; hex b0000001250134; asc % 4;; 第三列長度為 7 字節(jié)的回滾段信息嘶摊〖停可以看到伏社,這里的 acs 后面有顯示內(nèi)容 (% 和 4),這是因為剛好這個字節(jié)是可打印字符塔淤。
    • 后面兩列是 c 和 d 的值摘昌,都是 15。
  • 因此凯沪,我們就知道了第焰,由于 delete 操作把 id=10 這一行刪掉了买优,原來的兩個間隙 (5,10)妨马、(10,15)變成了一個 (5,15)。
  • 說到這里杀赢,你可以聯(lián)合起來再思考一下這兩個現(xiàn)象之間的關(guān)聯(lián):
    • session A 執(zhí)行完 select 語句后烘跺,什么都沒做,但它加鎖的范圍突然“變大”了脂崔;
    • 舉例說明滤淳,我們執(zhí)行 select * from t where c>=15 and c<=20 order by c desc lock in share mode; 向左掃描到 c=10 的時候,要把 (5, 10]鎖起來砌左。
    • 也就是說脖咐,所謂“間隙”铺敌,其實根本就是由“這個間隙右邊的那個記錄”定義的。

update 的例子

  • 看過了 insert 和 delete 的加鎖例子屁擅,我們再來看一個 update 語句的案例
update的例子
  • 你可以自己分析一下偿凭,session A 的加鎖范圍是索引 c 上的 (5,10]、(10,15]派歌、(15,20]弯囊、(20,25]和 (25,supremum]。

注意:根據(jù) c>5 查到的第一個記錄是 c=10胶果,因此不會加 (0,5]這個 next-key lock匾嘱。

  • 之后 session B 的第一個 update 語句,要把 c=5 改成 c=1早抠,你可以理解為兩步:
    1. 插入 (c=1, id=5) 這個記錄霎烙;
    2. 刪除 (c=5, id=5) 這個記錄。
  • 按照我們上一節(jié)說的蕊连,索引 c 上 (5,10) 間隙是由這個間隙右邊的記錄吼过,也就是 c=10 定義的。所以通過這個操作咪奖,session A 的加鎖范圍變成了圖 7 所示的樣子:
session 修改后盗忱,session A的加鎖范圍
  • 好,接下來 session B 要執(zhí)行 update t set c = 5 where c = 1 這個語句了羊赵,一樣地可以拆成兩步:
    1. 插入 (c=5, id=5) 這個記錄趟佃;
    2. 刪除 (c=1, id=5) 這個記錄。
  • 第一步試圖在已經(jīng)加了間隙鎖的 (1,10) 中插入數(shù)據(jù)昧捷,所以就被堵住了闲昭。

小結(jié)

  • 再次跟你復(fù)習(xí)了加鎖規(guī)則。并且靡挥,重點說明了序矩,分析加鎖范圍時,一定要配合語句執(zhí)行邏輯來進(jìn)行跋破。
  • 每個想認(rèn)真了解 MySQL 原理的同學(xué)簸淀,應(yīng)該都要能夠做到:通過 explain 的結(jié)果,就能夠腦補出一個 SQL 語句的執(zhí)行流程毒返。達(dá)到這樣的程度租幕,才算是對索引組織表、索引拧簸、鎖的概念有了比較清晰的認(rèn)識劲绪。你同樣也可以用這個方法,來驗證自己對這些知識點的掌握程度。
  • 在分析這些加鎖規(guī)則的過程中贾富,這里順便跟你介紹了怎么看 show engine innodb status 輸出結(jié)果中的事務(wù)信息和死鎖信息歉眷,希望這些內(nèi)容對你以后分析現(xiàn)場能有所幫助。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末颤枪,一起剝皮案震驚了整個濱河市姥芥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汇鞭,老刑警劉巖凉唐,帶你破解...
    沈念sama閱讀 221,888評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異霍骄,居然都是意外死亡台囱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評論 3 399
  • 文/潘曉璐 我一進(jìn)店門读整,熙熙樓的掌柜王于貴愁眉苦臉地迎上來簿训,“玉大人,你說我怎么就攤上這事米间∏科罚” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評論 0 360
  • 文/不壞的土叔 我叫張陵屈糊,是天一觀的道長的榛。 經(jīng)常有香客問我,道長逻锐,這世上最難降的妖魔是什么夫晌? 我笑而不...
    開封第一講書人閱讀 59,726評論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮昧诱,結(jié)果婚禮上晓淀,老公的妹妹穿的比我還像新娘。我一直安慰自己盏档,他們只是感情好凶掰,可當(dāng)我...
    茶點故事閱讀 68,729評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蜈亩,像睡著了一般懦窘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勺拣,一...
    開封第一講書人閱讀 52,337評論 1 310
  • 那天奶赠,我揣著相機與錄音,去河邊找鬼药有。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愤惰。 我是一名探鬼主播苇经,決...
    沈念sama閱讀 40,902評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宦言!你這毒婦竟也來了扇单?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評論 0 276
  • 序言:老撾萬榮一對情侶失蹤奠旺,失蹤者是張志新(化名)和其女友劉穎蜘澜,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體响疚,經(jīng)...
    沈念sama閱讀 46,349評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡鄙信,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,439評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了忿晕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片装诡。...
    茶點故事閱讀 40,567評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖践盼,靈堂內(nèi)的尸體忽然破棺而出鸦采,到底是詐尸還是另有隱情,我是刑警寧澤咕幻,帶...
    沈念sama閱讀 36,242評論 5 350
  • 正文 年R本政府宣布渔伯,位于F島的核電站,受9級特大地震影響肄程,放射性物質(zhì)發(fā)生泄漏咱旱。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,933評論 3 334
  • 文/蒙蒙 一绷耍、第九天 我趴在偏房一處隱蔽的房頂上張望吐限。 院中可真熱鬧,春花似錦褂始、人聲如沸诸典。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽狐粱。三九已至,卻和暖如春胆数,著一層夾襖步出監(jiān)牢的瞬間肌蜻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評論 1 272
  • 我被黑心中介騙來泰國打工必尼, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蒋搜,地道東北人篡撵。 一個月前我還...
    沈念sama閱讀 48,995評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像豆挽,于是被迫代替她去往敵國和親育谬。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,585評論 2 359

推薦閱讀更多精彩內(nèi)容