<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ## 前言 在MySQL中,DDL是不屬于事務范疇的,如果事務和DDL并行執行,操作相關聯的表的話,會出現各種意想不到問題,如[事務特性被破壞](http://mysql.taobao.org/monthly/2015/11/04/bug%E5%9C%B0%E5%9D%80)、[binlog順序錯亂](http://bugs.mysql.com/bug.php?id=989)等,為了解決類似這些問題,MySQL在5.5.3引入了MDL鎖(Metadata Locking),關于其設計思路可以參考這兩個worklog:[WL#3726](http://dev.mysql.com/worklog/task/?id=3726)?和?[WL#4284](http://dev.mysql.com/worklog/task/?id=4284)。本篇從代碼實現角度對MDL進行分析。 ## 重要數據結構 MDL 是在 MySQL server 層實現的一個模塊,通過對外接口和server層其它模塊進行交互,在sql/mdl.h和sql/mdl.cc中實現。 1. `enum_mdl_type`,枚舉類型,表示MDL鎖的類型,目前一共9種 ~~~ * MDL_INTENTION_EXCLUSIVE IX // 意向X鎖,只用于scope 鎖 * MDL_SHARED S // 只能讀metadata,當能讀寫數據,如檢查表是否存在時用這個鎖 * MDL_SHARED_HIGH_PRIO SH // 高優先級S鎖,可以搶占X鎖,只能讀metadata,不能讀寫數據,用于填充INFORMATION_SCHEMA,或者show create table時 * MDL_SHARED_READ SR // 可以讀表數據,select語句,lock table xxx read 都用這個 * MDL_SHARED_WRITE SW // 可以更新表數據,insert,update,delete,lock table xxx write, select for update, * MDL_SHARED_UPGRADABLE SU // 可升級鎖,可以升級為SNW或者X鎖,ALTER TABLE第一階段會用到 * MDL_SHARED_NO_WRITE SNW // 可升級鎖,其它線程能讀metadata,數據可讀不能讀,持鎖者可以讀寫,可以升級成X鎖,ALTER TABLE的第一階段 * MDL_SHARED_NO_READ_WRITE SNRW // 可升級鎖,其它線程能讀metadata,數據不能讀寫,持鎖者可以讀寫,可以升級成X鎖,LOCK TABLES xxx WRITE * MDL_EXCLUSIVE X // 排它鎖,禁止其它線程的所有請求,CREATE/DROP/RENAME TABLE ~~~ 2. `enum_mdl_duration`,枚舉類型,表示持有MDL鎖的時間 ~~~ * MDL_STATEMENT // 語句范圍的,語句結束自動釋放 * MDL_TRANSACTION // 事務范圍的,事務結束時自動釋放 * MDL_EXPLICIT // 顯式鎖,由lock tables xxx read 這種獲取,需要通過unlock tables釋放 ~~~ 3. `MDL_key`, 對MDL鎖的一個標識,是個三元組:namespace + db_name + table_name ~~~ * m_ptr // 字符串數組,三元組就存在這里 - enum_mdl_namespace // 內部定義的一個枚舉類型,表示加鎖對象的類型 * GLOBAL // 全局讀鎖,FLUSH TABLES WITH READ LOCK * SCHEMA // 數據庫鎖 * TABLE // 表鎖 * FUNCTION // 函數鎖 * PROCEDURE // 存儲過程 * TRIGGER // 觸發器 * EVENT // event事件 * COMMIT // 全局commit鎖,FLUSH TABLES WITH READ LOCK ~~~ 4. `MDL_request`, 線程的鎖請求,這個會發送給MDL子系統,包含加鎖對象(MDL_key)、加什么類型鎖(enum_mdl_type)、鎖持有時間(enum_mdl_duration)等信息 ~~~ * type // 類型是enum_mdl_type,表示鎖請求的類型 * duration // 類型是enum_mdl_duration,表示鎖的持有時間 * next_in_list // 當前線程中下一個MDL_request指針,和prev_in_list一起所有MDL_request串起來,形成雙向鏈表 * prev_in_list // 見上 * ticket // 加鎖成功后,MDL模塊會返回一個ticket * key // MDL_key ~~~ 5. `MDL_ticket`, MDL子系統內部對加鎖請求或已獲得鎖的表示,對MDL來說非常重要,同時是`MDL_wait_for_subgraph`的子類,線程的鎖等待圖就通過ticket構建出來。 ~~~ * next_in_context // 和prev_in_context一起構造在當前context下所有的ticket雙向鏈表 * prev_in_context // 見上 * next_in_lock // 和prev_in_lock一起構造當前MDL_lock的等待和持有ticket雙向鏈表 * prev_in_lock // 見上 - has_pending_conflicting_lock // 當前ticket的鎖類型是否和對應MDL鎖的等待隊列中的鎖沖突 - is_upgradable_or_exclusive // 是否是可以升級或者互斥鎖 - has_stronger_or_equal_type // 當前ticket對應的鎖和指定的鎖比較是否更強(如X比S強) - is_incompatible_when_granted // 是否能加鎖 - is_incompatible_when_waiting // 是否比等待隊列中的tciket類型優先級更高 - accept_visitor // 死鎖檢測用到 - get_deadlock_weight // 拿一個死鎖權重,死鎖檢測用 * m_type // 鎖類型 * m_duration // 持有時間,debug 模式下有效 * m_ctx // 指向所屬context * m_lock // 指向請求的鎖對象 ~~~ 6. `MDL_wait`,鎖等待實現,當拿不到鎖時就要進入等待,等待的結果也存在這里面 ~~~ - enum_wait_status // 鎖等待退出時的狀態 * EMPTY // 初始化值 * GRANTED // 加鎖成功,拿到鎖 * VICTIM // 等待的時候,死鎖檢測發現死鎖,當前線程選為victim,加鎖失敗 * TIMEOUT // 加鎖超時,加鎖失敗 * KILLED // 連接被kill,加鎖失敗 - timed_wait // 等待的實現,條件變量+超時 ~~~ 7. `MDL_context`,在MDL子系統中,對應一個線程,thd和MDL系統交互就通過這個類實現 ~~~ - try_acquire_lock // 嘗試加鎖,加鎖失敗就返回,沒有死鎖檢測 - acquire_lock // 加一個鎖,和上面的區別是多了死鎖檢測 - acquire_locks // 一次性加多個排它鎖,要么成功,要么全失敗 - upgrade_shared_lock // 升級共享鎖 - clone_ticket // clone 出一個 ticket - release_all_locks_for_name // 把當前線程對某個對象加的所有MDL鎖都釋放掉 - release_lock // 釋放單個鎖 - is_lock_owner // 是否持有某個對象的鎖 - has_lock // 線程是否否在savepoint之前持有指定的鎖 - has_locks // 當前線程是否持有鎖 - set_explicit_duration_for_all_locks // 鎖的時間范圍都置為顯式 - set_transaction_duration_for_all_locks // 鎖的時間范圍都置為事務 - set_lock_duration // 設置鎖的時間范圍 - release_statement_locks // 釋放所有語句時間范圍的鎖 - release_transactional_locks // 釋放所有事務時間范圍的鎖 - rollback_to_savepoint // MDL 鎖回滾到某個savepoint - get_deadlock_weight // 死鎖時拿一個權重值,以此來判斷對應線程是否要做為victim * m_wait // 鎖等待 * m_tickets // 指針數組,每個元素指向一個ticket鏈表,分別對應當前線程的語句范圍鎖、事務范圍鎖和顯式鎖 * m_owner // 指向thd的指針 * m_waiting_for // 當前線程正在等待的鎖 - find_ticket // 在當前線程ticket鏈表中查找一個ticket - release_locks_stored_before // 釋放ticket鏈表上在某個ticket之前所有ticket - find_deadlock // 檢測是否有死鎖 - visit_subgraph // 和死鎖檢測相關 ~~~ 8. `MDL_map`,MDL_key 到 MDL_lock 的一個映射,MDL模塊內部用,MDL系統所有鎖都放在這個Map里 ~~~ - init // 初始化 - destroy - find_or_insert // 查找對應的MDL_lock,沒有的話新建并插入 - remove // 移除MDL_lock * m_partitions // MDL_map 分區 * m_global_lock // 預先分配的全局讀鎖 * m_commit_lock // 預先分配的全局commit鎖 ~~~ 9. `MDL_map_partition`,為了提升MDL模塊的擴展性,把原本的一個MDL_map分成多個分區,每個分區就是一個?`MDL_map_partition` ~~~ - find_or_insert // 當前分區中查找對應的MDL_lock,沒有的話新建并插入 - remove // 在當前分區中移除MDL_lock - move_from_hash_to_lock_mutex // 鎖轉換,釋放對分區的加鎖(MDL_map_partition::m_mutex),獲取lock對象的鎖(MDL_lock::m_rwlock) * m_mutex // 對分區對象的一個保護鎖,修改當前分區要拿到這個鎖 * m_unused_locks_cache // 釋放掉的鎖對象的一個緩存,不用再新分配內存 ~~~ 10. `MDL_lock`,MDL鎖對象,對于一個key組合(三元組),整個系統只有一個鎖對象,不管請求的key是什么類型,什么時間范圍 ~~~ - Ticket_list // 一個內部嵌套類,用于表示當前MDL鎖相關的ticket列表,是個list - add_ticket // 增加 ticket - remove_ticket // 移除 ticket - is_empty // list 是不是空的 - clear_bit_if_not_in_list // 如果當前list中沒有某種類型的ticket,就把對應的位清掉 * m_list // 存放ticket的list * m_bitmap // 標識當前list中所有ticket類型對應bit位的bitmap,實例是個short類型 * key // 當前鎖對應的MDL_key * m_rwlock // 對MDL_lock鎖對象的保護鎖 - has_pending_conflicting_lock // 已經授權的ticket是否和等待隊列中的ticket不兼容 - can_grant_lock // 能否加鎖,先和等待隊列進行優先級比較,然后看和已授權的鎖是否兼容 - reschedule_waiters // 當持有當前鎖的ticket釋放或者降級時,會調用下,看等待隊列里是否有ticket此時可以獲取鎖 - remove_ticket // 從指定隊列中移出ticket - visit_subgraph // 死鎖檢測相關 - needs_notification // 是否需要通知其它線程,當前ticket的鎖情況 - notify_conflicting_locks // 通知其它線程,有一個高級的鎖請求 - hog_lock_types_bitmap // 標識哪種鎖是高級鎖 * m_granted // 已經獲得當前MDL鎖的ticket隊列 * m_waiting // 等待當前MDL鎖的ticket隊列 * m_hog_lock_count // 高級鎖可以連接拿得鎖的個數,超過這個數目就要給低級鎖讓路,防止低級鎖餓死 * m_ref_usage // 和下面2個變量一起,為了提高鎖的擴展性 * m_ref_release * m_is_destroyed * m_version // 用于判斷鎖對象是否被放入unsed隊列 * m_map_part // 當前MDL鎖所在的MDL_map 分區 ~~~ 11. `MDL_scoped_lock`,MDL_lock的一個子類,主要用于對schema加MDL鎖,全局讀鎖和全局commit鎖也是這種類型。 12. `MDL_object_lock`,MDL_lock的另一個子類,除了`MDL_scoped_lock`外,其它都用這個(table、fucntion等),只有?`MDL_object_lock`?可以緩存。 總結下,上面這些類中,`MDL_key`?和?`MDL_request`?都是POD,用來保存信息的;`MDL_context`是MDL子系統和線程交互的接口,一個對象對應一個線程;`MDL_map`、`MDL_map_partition`?和?`MDL_lock`?都是MDL子系統內部實現細節,對server層其它部分不可見;`MDL_ticket`?表示線程對`MDL_lock`持有的某種鎖。 MDL鎖可以從不同角度進行分類: 1. namespace,如GLOBAL、SCHEMA、TABLE等; 2. 鎖的持續時間,如transaction、顯式等; 3. 鎖的兼容性,如S、X、SH等; 4. 鎖的實現類,如scope,object等; 可以看作是MDL鎖的不同屬性,大家不要搞亂了 :-) ## 模塊初始化 整個MDL模塊的初始化是在mysqld啟動時進行的,初始化邏輯在?`MDL_map::init()`?中,做的事情也比較簡單: 1. 初始化兩個全局MDL鎖,global lock 和 commit lock,兩者都是類型都是`MDL_scoped_lock`; 2. 分配`metadata_locks_hash_instances`個map分區,為了解決MDL模塊[全局鎖競爭問題](http://bugs.mysql.com/bug.php?id=66473),在5.6.8對MDL鎖做了分區([commit](https://github.com/mysql/mysql-server/commit/38ed575f03e3b5cf01ae7aabbe3c16355793abbd)),通過`metadata_locks_hash_instances`配置指定用多少個分區,默認是8個。 ## 加鎖 加鎖就是server的線程(thd)向MDL模塊獲取對應鎖的ticket過程,加鎖成功標志是MDL模塊返回一個對應的ticket,大致邏輯如下: 1. 線程解析SQL語句,根據語義對每一個表對象設置`TABLE_LIST.mdl_request`,如對普通的select語句?`TABLE_lsit.mdl_request.type`?就是`MDL_SHARED_READ`,可以參考函數`st_select_lex::set_lock_for_tables()`; 2. 線程在打開每個表之前,會請求和這個表對應的MDL鎖,通過?`thd->mdl_context.acquire_lock()`?等接口將`mdl_request`請求發給MDL模塊; 3. MDL模塊根據請求類型和已有的鎖來判斷請求能否滿足,如果可以就返回一個ticket;如果不可以就等待,等待結果可以是成功(別的線程釋放了阻塞的MDL鎖)或者失敗(超時、連接被kill或者被死鎖檢測選為victim); 4. 線程根據MDL模塊的返回結果,決定繼續往下走還是報錯退出。 > 需要注意的是,MDL鎖并不是對表加鎖,而是在加表鎖前的一個預檢查,如果能拿到MDL鎖,下一步加相應的表鎖。 下面對MDL模塊中的主要加鎖方法進行介紹。 MDL_context::find_ticket 這是一個shortcut方法,加鎖的時候先檢查當前線程是否已持有對應key的MDL鎖,并且這個鎖的類型不比請求的低,那么就不需要經過MDL系統再分配一個ticket出來(這個比較復雜,代價較高),直接使用已有的ticket,或者clone一個。 舉個例子: ~~~ 1\. begin; 2\. insert into t1 values (1); 3\. insert into t1 values (2); ... ~~~ 在上面的語句序列中,執行語句3的時候就不需要再走一遍復雜的加鎖邏輯,因為語句2已經成功拿到t1表的ticket,類型都是MDL_SHARED_WRITE,并且MDL鎖時間范圍也一樣(transaction),這個時候直接用已有的ticket,甚至不用clone。 MDL_context::clone_ticket 經過檢測發現可以直接使用已有的ticket,比如上面的`MDL_context::find_ticket`發現了可以復用的ticket,但是鎖時間范圍不一致,為了確保已經有鎖釋放時,不影響現在請求的,就clone一個ticket。 ~~~ 1\. begin; 2\. insert into t1 values (1); 3\. handler t1 open; ... ~~~ 在上面的語句序列中,執行語句3的時候,發現有可以復用的ticket(語句2的ticket),但是handler需要的MDL鎖是顯式的,而語句2取得的ticket是事務時間范圍的,事務完成后就會釋放,為了避免handler的MDL鎖被提前釋放,因此單獨clone一個出來用。 MDL_context::try_acquire_lock_impl 無等待的加鎖,如果發現有沖突導致加鎖失敗,直接退出。會先調用`MDL_context::find_ticket`看是否有可以復用的ticket,有的話就返回成功,如果沒有就看能否加鎖,能加的話也返回成功,不能加也直接返回(同時返回一個ticket給調用者)。 MDL_context::acquire_lock 主加鎖函數,調試MDL鎖相關問題時,給這個函數加斷點比較有效。先調用`MDL_context::try_acquire_lock_impl`,如果加鎖失敗就進入等待加鎖邏輯: 1. 將`MDL_context::try_acquire_lock_impl`返回的ticket放進MDL_lock的等待隊列; 2. 觸發一次死鎖檢測(后面會詳細介紹); 3. 進入等待,這個時候如果我們`show processlist`就會看到”Waiting for table metadata lock”之類state。等待又分為2種: * 定時檢查等待: 如果當前請求的鎖是比較高級的(對于`MDL_object_lock`是比MDL_SHARED_NO_WRITE類型更高,對于`MDL_scoped_lock`是MDL_SHARED類型),就會每秒給其它持有當前鎖的線程(并且這些連接持有的鎖等級比較低)發信號,通知其釋放鎖,然后再檢查是否鎖已拿到; * 一直等待,直到超時; 4. 檢查步驟3的等待結果,可以是GRANTED(拿到鎖)、VICTIM(被死鎖檢測算法選為受害者)、TIMEOUT(加鎖超時)、KILLED(連接被kill)。拿到鎖返回成功,其它返回失敗。 > 鎖等待是靠`MDL_wait`這個類來實現的。 MDL_context::acquire_locks 一次性加多個排它MDL鎖,如果其中一個加鎖失敗,前面已經拿到的鎖也全部釋放。主要用在DDL中,比如`drop table test.t1`這個DDL會一次加3個鎖: * GLOBAL,MDL_INTENTION_EXCLUSIVE * test 庫, MDL_INTENTION_EXCLUSIVE * test.t1 表,MDL_EXCLUSIVE MDL_context::upgrade_shared_lock 鎖升級,從共享鎖升級到互斥鎖,實現方式是重新申請一個目標鎖,拿到新的ticket后替換老的ticket,用在alter table和create table場景中。 如`create table test.t1(id int) engine = innodb`,會先拿test.t1的MDL_SHARED共享鎖,檢查表是否存在,如果不存在就把鎖升級到MDL_EXCLUSIVE鎖,然后開始建表。 對于`alter table test.t1 add column name varchar(10), algorithm=copy;`,alter用copy到臨時的方式來做。整個過程中MDL順序是這樣的: 1. 剛開始打開表的時候,用的是 MDL_SHARED_UPGRADABLE 鎖; 2. 拷貝到臨時表過程中,需要升級到 MDL_SHARED_NO_WRITE 鎖,這個時候其它連接可以讀,不能更新; 3. 拷貝完在交換表的時候,需要升級到是MDL_EXCLUSIVE,這個時候是禁止讀寫的。 所以在用copy算法alter表過程中,會有2次鎖升級。 MDL_ticket::downgrade_lock 和`MDL_context::upgrade_shared_lock`對應的鎖降級,從互斥鎖降級到共享鎖,實現比較簡單,直接把鎖類型改為目標類型(不用重新申請)。 對于`alter table test.t1 add column name varchar(10), algorithm=inplace`,如果alter使用inplace算法的話,整個過程中MDL加鎖順序是這樣的: 1. 和copy算法一樣,剛開始打開表的時候,用的是 MDL_SHARED_UPGRADABLE 鎖; 2. 在prepare前,升級到MDL_EXCLUSIVE鎖; 3. 在prepare后,降級到MDL_SHARED_UPGRADABLE(其它線程可以讀寫)或者MDL_SHARED_NO_WRITE(其它線程只能讀不能寫),降級到哪種由表的引擎決定; 4. 在alter結束后,commit前,升級到MDL_EXCLUSIVE鎖,然后commit。 可以看到inplace有2次鎖升級,1次降級,不過在alter最耗時的階段是有可能降級到MDL_SHARED_UPGRADABLE的,對其它線程的影響小。 MDL_context::release_locks_stored_before 釋放線程指定ticket鏈表上某個ticket之前的所有ticket,每個context有3個ticket鏈表(statement、transaction和explicit),分別對應當前線程持有的不同時間范圍的MDL鎖。而ticket在鏈表中的順序和時間順序是相反的,后插入的ticket放在鏈表開頭,因此本函數的作用就是把某個時間點之后的ticket都釋放掉,回滾MDL鎖。有幾個指釋放MDL鎖的函數都是基于此實現: 1. `MDL_context::rollback_to_savepoint`,把存檔點之后的所有MDL鎖都釋放掉; 2. `MDL_context::release_transactional_locks`,釋放所有transaction和statement時間范圍的MDL鎖; 3. `MDL_context::release_statement_locks()`,釋放所有statement時間范圍的MDL鎖。 ## 死鎖檢測 MDL模塊作為一個集中的資源,收到不同線程發來的鎖請求,而有的鎖是互斥的,不能同時滿足,在這種情況下就會等待,如果線程在此之前已經拿到某些鎖的話,就會形成持有-等待的狀態;而又不可能要求所有線程按某一固定順序請求鎖,這樣就會形成等待循環,也就是死鎖,如下圖所示: ![](https://box.kancloud.cn/2015-11-17_564aa53b544df.png) 圖1\. 死鎖 線程T1持有M1,然后請求M2,但M2被線程T2持有,并且和T1的請求類型互斥,同時T2請求M1,和T1拿到的鎖互斥,形成死鎖。 在介紹MDL的死鎖檢測之前,先介紹下MDL鎖的兼容矩陣。每種類型的鎖各有2個兼容矩陣,granted matrix 和 waiting matrix,前者表示鎖的兼容性,后者表示鎖的優先級(優先級就是和等待隊列的鎖相比,當前鎖是否能夠進行加鎖嘗試,當前鎖優先級高則可以,低則需進等待隊列)。 矩陣中 ‘+’ 表示兼容,’-‘ 表示不兼容,’0’ 表示不可能存在的場景。 `MDL_scoped_lock`,支持IX,S和X鎖(關于鎖的縮寫可以看第一節)。 1. granted matrix ~~~ | Type of active | Request | scoped lock | type | IS(*) IX S X | ---------+------------------+ IS | + + + + | IX | + + - - | S | + - + - | X | + - - - | ~~~ 2. waiting matrix ~~~ | Pending | Request | scoped lock | type | IS(*) IX S X | ---------+-----------------+ IS | + + + + | IX | + + - - | S | + + + - | X | + + + + | ~~~ IS鎖雖然列了出來,但是代碼里并沒有實現這個鎖,因為IS和所有的鎖類型都兼容(也可以理解為每次鎖請求都默認會額外有一個IS鎖)。 `MDL_object_lock`,支持S、SH、SR、SW、SU、SNW、SNRW 和 X鎖。 1. granted matrix ~~~ Request | Granted requests for lock | type | S SH SR SW SU SNW SNRW X | ----------+----------------------------------+ S | + + + + + + + - | SH | + + + + + + + - | SR | + + + + + + - - | SW | + + + + + - - - | SU | + + + + - - - - | SNW | + + + - - - - - | SNRW | + + - - - - - - | X | - - - - - - - - | SU -> X | - - - - 0 0 0 0 | SNW -> X | - - - 0 0 0 0 0 | SNRW -> X | - - 0 0 0 0 0 0 | ~~~ 關于’0’的情況說明下,比如對于SU鎖來說其和自身是不兼容的,不可能有2個線程對同一個對象都持有SU鎖,所以就不存在當一個線程進行鎖升級時,另一個線程持有SU。其它’0’的情況類似。 2. waiting matrix ~~~ Request | Pending requests for lock | type | S SH SR SW SU SNW SNRW X | ----------+---------------------------------+ S | + + + + + + + - | SH | + + + + + + + + | SR | + + + + + + - - | SW | + + + + + - - - | SU | + + + + + + + - | SNW | + + + + + + + - | SNRW | + + + + + + + - | X | + + + + + + + + | SU -> X | + + + + + + + + | SNW -> X | + + + + + + + + | SNRW -> X | + + + + + + + + | ~~~ 注意 SH 比 X 鎖的優先級還高,正是其高優先級(high priority)的體現。 在MDL系統中,資源關系是這樣的: 1. 線程和鎖的關系通過ticket建立; 2. 每個線程有3個ticket鏈表,分別對應當前持有的statement鎖、transaction鎖和顯式鎖,放在?`MDL_context::m_tickets`中;對于當前線程正在等待的鎖只有一個,用`MDL_context::m_waiting_for`表示; 3. 每個MDL鎖有2個ticket鏈表,分別對應已經獲得鎖的線程(`MDL_lock::m_granted`)和等待鎖的線程(`MDL_lock::m_waiting`); 4. 線程的ticket鏈表和MDL鎖的ticket鏈表一起構成了MDL系統的等待關系圖,死鎖檢測就是搜索這張圖,看是否有環路。 為了描述的簡潔,我們將線程和MDL鎖的ticket鏈表都簡化為1個,如下圖2矩陣的,橫線表示線程的鏈表,縱向表示MDL鎖的鏈表,有色彩的交點表示一個ticket,橘黃色表示連接已經拿到鎖,青色表示正在等待的鎖,圖中MDL上鎖的類型不兼容,形成持有等待回路——死鎖。 ![](https://box.kancloud.cn/2015-11-17_564aa53b83f45.png) 圖2\. MDL死鎖 下面介紹下死鎖檢測中的函數。 MDL_context::find_deadlock 這個是死鎖檢測的入口,線程在`MDL_context::acquire_lock`嘗試拿鎖失敗,進入等待之前,會調用這個函數進行一次死鎖檢測。 函數會行循環檢測,直到發現沒有死鎖(每輪檢測會去掉等待圖中一條邊,但不保證能解決死鎖,所以需要循環),或者當前線程被選為victim才退出。 MDL_context::visit_subgraph 看當前線程是否有鎖等待`MDL_context::m_waiting_for`,有的話就沿著ticket搜下去,沒有就退出。 MDL_ticket::accept_visitor 這個方法看起來沒有什么實際內容,只是簡單調用`MDL_lock::visit_subgraph`,其實可以看作是搜索視角的轉換,從?`MDL_context`?經過?`MDL_ticket`?進入到?`MDL_lock`,代碼邏輯顯得比較清晰。 MDL_lock::visit_subgraph 這個是死鎖檢測核心邏輯: 1. 先給搜索深度加1,然后判斷是否超過最大搜索深度(MAX_SEARCH_DEPTH= 32),超過就無條件認為有死鎖,退出; 2. 遍歷當前鎖的ticket鏈表,看ticket對應的線程是否和死鎖檢測的發起線程是同一個,如果是則說明有回路,退出(相當于做了一層的廣度搜索); 3. 從頭開始遍歷當前鎖的ticket鏈表,對每個ticket對應的線程,遞歸調用`MDL_context::visit_subgraph`(深度搜索)。 整個死鎖檢測邏輯是一個加了深度限制的深搜,中間同時多了一層廣搜。 Deadlock_detection_visitor?是死鎖檢測中重要的輔助類,主要負責: 1. 記錄死鎖檢測的起始線程; 2. 記錄被選做victim的線程; 3. 在檢測到死鎖,深搜一層層退出的時候,會依次檢查回路上各線程的死鎖權重,選擇權重最小的做為最終的victim(權重由鎖的類型決定)。 ## global read lock 相信FTWRL(FLUSH TABLES WITH READ LOCK)這個命令很多人都用過,比如備份時為了獲取SQL線程執行位點或binlog位點,這個命令的目的是阻止新的更新進來和已有事務的提交。就這個命令主要靠MDL鎖來實現,這里用到了2個MDL鎖,namespace分別為`MDL_key::GLOBAL`和`MDL_key::COMMIT`,這2個鎖在整個MDL系統中都是全局唯一的,都是`MDL_scoped_lock`類型。 執行 FTWRL 的線程會請求這2個鎖的MDL_SHARED鎖,并且是顯式的。在所有更新數據的代碼路徑里,除了必須的鎖外,還會額外請求`MDL_key::GLOBAL`鎖的MDL_INTENTION_EXCLUSIVE鎖;在事務提交前,會先請求`MDL_key::COMMIT`鎖的MDL_INTENTION_EXCLUSIVE鎖。對于scope鎖來說,IX鎖和S鎖是不兼容的(參考granted matrix),所以更新和事務提交都被FTWRL擋到了。 Percona Server 實現的相對于 FTWRL 輕量級的backup鎖也是基于MDL實現的,其對MDL_key的 namespace 額外擴展了2個,`MDL_key::BACKUP`和`MDL_key::BINLOG`,對應的2個鎖也是全局唯一的,感興趣的同學可以了解下[backup locks](https://www.percona.com/doc/percona-server/5.6/management/backup_locks.html)。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看