天天看點

MySQL · 引擎特性 · InnoDB index lock前世今生

innodb并發過程中使用兩類鎖進行同步。

1. 事務鎖

維護在不同的isolation level下資料庫的atomicity和consistency兩大基本特性。

innodb定義了如下的lock mode:

2. 記憶體鎖

為了維護記憶體結構的一緻性,比如dictionary cache、sync array、trx system等結構。

innodb并沒有直接使用glibc提供的庫,而是自己封裝了兩類:

1. 一類是mutex,實作記憶體結構的串行化通路

2. 一類是rw lock,實作讀寫阻塞,讀讀并發的通路的讀寫鎖

讀者如果有興趣,可以直接翻閱innodb的代碼,這裡我們主要介紹index lock所使用的rw lock。

innodb預設使用b-tree結構來儲存資料,如下圖所示的b-tree結構:

MySQL · 引擎特性 · InnoDB index lock前世今生

這個b-tree一共有兩類節點,一類是node(branch) block,一類是leaf block,對于記憶體中的每一個block,都有一個rw lock與之相對應,用于保護block内部結構的一緻性,阻塞并發修改。每一個index在記憶體中保持着一個index字典對象,即<code>dict_index_t</code>,并對應着一個index lock,同樣屬于rw lock類型,用于保護b-tree的平衡樹結構。

是以,innodb為每一個index,維護兩種rw lock:

1. index級别的,用于保護b-tree結構不被破壞

2. block級别的,用于保護block内部結構不被破壞

很明顯,rw lock 鎖保護的對象的級别越高,沖突的可能性就越大,并發的瓶頸也就越容易出現。

我們先來看rw lock的模型,rw lock一共使用兩類lock mode,即s鎖和x鎖,其相容性矩陣是:

按照lock mode,資料庫對b-tree操作區分幾種類型:

根據這些不同的操作類型,我們下面來分析一下加鎖的過程。

如果sql通過索引進行掃描,其latch mode為<code>btr_search_leaf</code>:

首先是hold住index lock的rw_s_latch,然後通過<code>btr_cur_search_to_nth_level</code>進行b-tree查詢leaf節點的過程。當cursor定位到leaf節點上之後,在leaf page節點上,添加rw_s_latch鎖,即s鎖,然後通過save_point的mtr釋放index lock的s鎖。在掃描的過程中,因為持有index的rw_s_latch,是以節點的掃描比如root、branch這樣的node block,并不持有任何mode的rw lock。直到latch住leaf節點後,就釋放掉 index 的鎖,這樣盡可能的減少阻塞,剩下就是leaf節點的掃描過程,隻持有leaf page的鎖。 掃描完資料,就釋放leaf page的s鎖。

場景2和場景1在持有index lock的過程中,是相同的,都是在search的過程中,持有rw_s_latch,一旦定位到leaf page,就釋放掉index 的s鎖,升序和降序的掃描過程中,會沿着leaf page之間的雙連結清單進行掃描,因為是雙向連結清單,是以可以完成asc和desc的掃描。但這裡要注意的是,innodb先持有下一個page的lock,然後再釋放目前持有page的lock,這樣就有可能造成死鎖,是以innodb不管目前是asc還是desc的掃描,都會先持有左leaf page的lock,然後再持有下一個leaf page的鎖,最後釋放prev page的lock,這樣做到加鎖的順序一緻,避免死鎖。

innodb在插入記錄的過程中,分了兩個步驟,樂觀插入和悲觀插入:

1. 樂觀,就是目前leaf page的剩餘空間滿足記錄的插入需要;

2. 悲觀,就是需要split b-tree,增加leaf page來完成新記錄的插入。

先看樂觀插入:

場景1和場景2都持有leaf page的rw_s_latch,但在插入的過程中,操作類型是btr_modify_leaf,需要持有leaf page的rw_x_latch, 在search的過程中,和場景1、2相同,都是持有index的rw_s_latch lock,一旦定位結束,釋放index lock。

悲觀插入,需要split b-tree,是以首先會持有index lock,mode為rw_x_latch,并x lock三個leaf page,即prev,current,next三個leaf page,然後修改branch節點的記錄,指向leaf節點,修改完成後,才能釋放index lock。

在split的過程中,無法進行search操作(因為正在修改branch節點),但如果其他線程已經在讀取leaf page,并不會受影響。

在online ddl的過程中,比如add index,因為是新添加的index,并不會産生并發通路的問題。

比如加字段的過程,其并發問題,由server層的mdl鎖和innodb層的事務鎖來完成其同步。

我們來看上面提到的6個場景,對我們日常使用innodb的過程中,影響最大的就是場景4,即split的過程中,會嚴重的影響并發,因為index 的x lock,導緻任何的b-tree掃描都産生了阻塞。有解嗎?

通常我們碰到lock導緻的并發問題的時候,第一個想到的就是降低鎖對象的粒度,粒度越小,共享區域也就越小,沖突的幾率也就越小,并發就能夠提高。

根據這個原則,我們回過頭來看這個問題,因為index lock 保護了整個b-tree的結構,但我們對某一個branch節點進行split的時候,我們僅僅修改了這個branch節點,是以我們可以把鎖的粒度降低到某些要修改的branch節點上,這樣就可以不影響其他branch節點的掃描和通路。

mysql官方對index lock進行了優化,在split的過程中,盡可能的減少沖突,減少并發的瓶頸。

對于innodb的rw lock增加第三種lock mode,即sx鎖,其相容性矩陣如下:

這裡仍然保留了index lock,考慮一下兩個存在沖突的場景,還是否阻塞:

1. btr_search_leaf和btr_modify_leaf

對于掃描leaf節點和修改leaf節點的場景:

和之前的差别是在search的過程中,對使用到的branch節點,加上s鎖,用于同步branch節點的修改。同樣,當定位到leaf節點後,就可以把index lock和branch lock全部釋放掉了,後面leaf節點之間的移動,同樣不需要index lock和branch lock。

2. btr_modify_tree

對于修改index b-tree結構的場景:

注意:因為有index sx鎖,是以不允許并發的修改b-tree操作,是以,隻需要x latch要修改的branch即可。

和之前的差别就是index lock從x鎖變成了sx鎖,這樣并不影響search的過程,增加了更改過程中branch節點的x鎖。

這樣修改後,index lock在并發的過程中,修改b-tree和search b-tree沒有了并發沖突問題,在split的過程中,隻有search和modify到同一個branch節點,才會産生阻塞,對于我們正常的使用資料庫過程中(大部分都是通過index進行讀寫),可以顯著的提升并發能力。