MySQL 核心模塊揭秘 | 19 期 | 鎖模塊里有什么?什么樣?

InnoDB 中管理表鎖和行鎖的鎖模塊,也就是傳說中的鎖子系統,在內存里是什么樣的?

作者:操盛春,愛可生技術專家,公眾號『一樹一溪』作者,專注于研究 MySQL 和 OceanBase 源碼。

愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯系小編并注明來源。

本文基于 MySQL 8.0.32 源碼,存儲引擎為 InnoDB。

1. 引言

前面三篇文章,我們分別介紹了 InnoDB 表鎖、行鎖,以及它們的鎖結構。

表鎖結構和行鎖結構是鎖模塊的基礎組成部分,它們就像一塊磚,哪里需要哪里搬。

然而,要蓋房子,光有磚不行,還得有鋼筋、水泥等材料,這些材料就由鎖模塊結構提供。

鎖模塊結構只有一個對象(lock_sys),在 InnoDB 中是全局唯一的。

2. 鎖模塊結構

鎖模塊結構類型為 lock_sys_t,去掉注釋以及兩個無關緊要的屬性之后,簡化如下:

struct lock_sys_t {locksys::Latches latches;hash_table_t *rec_hash;hash_table_t *prdt_hash;hash_table_t *prdt_page_hash;Lock_mutex wait_mutex;srv_slot_t *waiting_threads;srv_slot_t *last_slot;bool rollback_complete;std::chrono::steady_clock::duration n_lock_max_wait_time;os_event_t timeout_event;
}

單從屬性數量上看,鎖模塊結構并不復雜,甚至可以說比較簡單。

其實,鎖模塊的復雜性,不在于表鎖結構、行鎖結構,也不在于鎖模塊結構,而是在于各個事務、各種加鎖場景相互交錯導致的錯綜復雜的加鎖結果。

例如,一個事務等待獲得另一個事務持有的鎖,雖然會出現或長或短的等待鏈,但也不算太壞的情況。更壞的情況是出現了環形的等待鏈,也就是出現了死鎖。

如果出現死鎖,我們又需要被動復現死鎖,以解釋形成死鎖的原因,那簡直頭大了。

為了不滑入復雜的深淵,我們就此打住,先來介紹鎖模塊結構的屬性。

鎖模塊結構中有三個類型為 hash_table_t 的屬性,分別是 rec_hashprdt_hashprdt_page_hash

其中,prdt_hash、prdt_page_hash 由謂詞鎖使用。我們并不打算介紹謂詞鎖,忽略這兩個屬性,也就順理成章了。

n_lock_max_wait_time 屬性的值是 MySQL 本次啟動以來,行鎖的最長等待時間。通過以下命令可以查詢到這個屬性的值:

show status like 'innodb_row_lock_time_max';+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| Innodb_row_lock_time_max | 50157 |
+--------------------------+-------+

rollback_complete 屬性,用于 MySQL 啟動過程中,標識從 undo 日志中恢復出來的、需要回滾的事務是否已全部回滾完成。

如果 rollback_complete = false,說明從 undo 日志中恢復出來的、需要回滾的事務還沒有全部回滾完成,InnoDB 會遍歷讀寫事務鏈表(trx_sys->rw_trx_list),釋放這些事務加的表鎖和行鎖。

這些事務全部回滾完成之后,rollback_complete 會被修改為 true。

前面介紹了鎖模塊結構中兩個比較簡單的屬性,剩下的其它屬性,我們分為幾個小節一一介紹。

2.1 誰來管理行鎖結構?

上一篇文章,我們介紹過,事務對多條記錄加行鎖,滿足條件時,可以共用一個行鎖結構。

雖然共用能減少行鎖結構的數量,但是,同一時刻,InnoDB 中可能還是有很多行鎖結構。

這么多行鎖結構,要怎么組織,用到時才能方便、快速的找到呢?

這就需要用到鎖模塊結構的 rec_hash 屬性了。

rec_hash 屬性是個哈希表,它的類型為 hash_table_t,創建鎖模塊對象(lock_sys)之后分配內存:

void lock_sys_create(ulint n_cells)
{...// 創建鎖模塊對象,分配內存lock_sys = static_cast<lock_sys_t *>(ut::zalloc_withkey(...));...// 創建哈希表(rec_hash),分配內存lock_sys->rec_hash =ut::new_<hash_table_t>(n_cells);...
}

lock_sys_create() 由 srv_start() 調用:

dberr_t srv_start(bool create_new_db) {...lock_sys_create(srv_lock_table_size);...
}

變量 srv_lock_table_size 在 innodb_init_params() 中賦值,它的值會傳遞給 lock_sys_create() 的參數 n_cells。

static int innodb_init_params() {...srv_lock_table_size = 5 * (srv_buf_pool_size / UNIV_PAGE_SIZE);...
}

srv_buf_pool_size 是 buffer pool 的大小,UNIV_PAGE_SIZE 是一個數據頁的大小,它們的單位都是字節。

以 buffer pool 大小為 128M、數據頁大小為 16K 為例,變量 srv_lock_table_size 的值計算如下:

// 128M = 134217728 字節
// 16K  = 16384 字節
srv_lock_table_size = 5 * (134217728 / 16384) = 40960

變量 srv_lock_table_size 的值(40960)最終會傳遞給 lock_sys_create() 的參數 n_cells。用 40960 替換 n_cells 之后如下:

void lock_sys_create(ulint n_cells)
{...lock_sys->rec_hash = ut::new_<hash_table_t>(40960);...
}

以上代碼說明 buffer pool 大小為 128M,數據頁大小為 16K 時,鎖模塊結構的 rec_hash 屬性有 40960 個格子。

每個格子都有編號,從 0 開始,一直到 40959。

這些格子并不是用來存儲行鎖結構,而是用來管理行鎖結構,它們的作用相當于線頭,找到了線頭就能牽出一根線。

創建行鎖結構之后,會先根據行鎖結構中那些記錄所屬數據頁的頁號和表空間 ID,計算得到哈希值,再根據哈希值計算得到格子的編號。

多個行鎖結構可能計算得到相同的哈希值,從而得到相同的編號,對應到同一個格子,這些行鎖結構通過各自的 hash 屬性形成一個行鎖結構鏈表。如果我們把這個鏈表看成一根線,這個格子就是這根線的線頭。

計算出格子編號之后,行鎖結構會插入到格子對應的行鎖結構鏈表的最前面。

想要找到某個行鎖結構,也需要根據同樣的規則,計算得到格子編號,再根據編號找到格子,最后遍歷這個格子對應的行鎖結構鏈表,以找到目標行鎖結構。

2.2 誰來保護表鎖和行鎖結構?

前面我們介紹了 rec_hash 是個哈希表,分為很多格子,每個格子管理一個行鎖結構鏈表。同一個鏈表的所有行鎖結構,計算得到的哈希值相同。

事務加行鎖時,會優先考慮共用已有的行鎖結構,這就要先找到一個可以共用的行鎖結構。

首先,需要找到 rec_hash 的某個格子。

然后,遍歷這個格子對應的行鎖結構鏈表,并根據共用條件,判斷某個行鎖結構是否可以共用。

事務加行鎖時,如果生成了新的行鎖結構,需要找到 rec_hash 的某個格子,把行鎖結構插入到這個格子對應的行鎖結構鏈表的最前面。

事務提交或回滾時,釋放所有行鎖,需要找到每個鎖結構在哪個格子對應的行鎖結構鏈表中,并從鏈表中刪除這個行鎖結構。

事務加表鎖時,會遍歷這個表對象的 locks 鏈表,以判斷可以立即獲得表鎖,還是需要進入等待狀態。

事務提交或回滾時,釋放所有表鎖,需要從每個表對象的 locks 鏈表中刪除這個表鎖結構。

多個事務執行上面這些操作,可能會同時讀寫 rec_hash 中某個格子對應的行鎖結構鏈表,也可能同時讀寫某個表對象的 locks 鏈表。

為了避免并發操作同時讀寫同一個行鎖結構鏈表、或者同時讀寫同一個表對象的 locks 鏈表出現沖突,需要有個什么東西,來限制同一時刻只有一個事務讀寫某個行鎖結構鏈表、或者某個表對象的 locks 鏈表。

于是,就有了鎖模塊結構的 latches 屬性,它的類型為 locksys::Latches。

class Latches {private:...Unique_sharded_rw_lock global_latch;Page_shards page_shards;Table_shards table_shards;...
}

latches 也是一個對象,有三個屬性,分別為 global_latchpage_shardstable_shards

事務提交或回滾時,釋放所有行鎖和表鎖會用到 global_latch。

事務加行鎖時,會用到 page_shards。

事務加表鎖時,會用到 table_shards。

page_shards、table_shards 的類型分為 Page_shardsTable_shards,定義如下:

static constexpr size_t SHARDS_COUNT = 512;class Page_shards {...Padded_mutex mutexes[SHARDS_COUNT];...
}class Table_shards {...Padded_mutex mutexes[SHARDS_COUNT];...
}

Page_shards 的 mutexes 屬性是個數組,有 512 個元素。

有新的行鎖結構需要加入某個行鎖結構鏈表,或者需要遍歷某個行鎖結構鏈表以找到目標行鎖結構時,會根據行鎖結構中那些記錄所屬數據頁的頁號和表空間 ID,計算得到哈希值,再根據哈希值計算得到數組下標,到 mutexes 數組中拿到下標對應的互斥量,就可以保護需要讀寫的行鎖結構鏈表了。

Table_shards 的 mutexes 屬性也是個數組,同樣有 512 個元素。

某個表對象的 locks 鏈表需要保護時,會直接用表 ID 對 512 取模(table_id % 512),得到的結果作為數組下標,到 mutexes 數組中拿到下標對應的互斥量,就可以保護這個表對象的 locks 鏈表了。

2.3 鎖等待了怎么辦?

鎖模塊結構中,有三個屬性和鎖等待相關,分別是 wait_mutexwaiting_threadslast_slot,它們的初始化代碼如下:

void lock_sys_create(ulint n_cells)
{ulint lock_sys_sz;// 鎖模塊結構占用的內存大小// 加上 waiting_threads 指向的內存區域的大小// 因為這兩部分要一起分配內存lock_sys_sz = sizeof(*lock_sys) + srv_max_n_threads * sizeof(srv_slot_t);...void *ptr = &lock_sys[1];lock_sys->waiting_threads = static_cast<srv_slot_t *>(ptr);// 初始化時// last_slot 和 waiting_threads 指向同一個位置lock_sys->last_slot = lock_sys->waiting_threads;mutex_create(LATCH_ID_LOCK_SYS_WAIT, &lock_sys->wait_mutex);...
}

waiting_threads 屬性是個指針,它指向一片內存區域,這片內存區域分為 srv_max_n_threads 個 slot,每個 slot 存放一個 srv_slot_t 對象。

srv_max_n_threads 在 innodb_init_params() 中賦值,硬編碼為 102400。

也就是說,waiting_threads 屬性指向的內存區域,最多可以存放 102400 個 srv_slot_t 對象。

如果某個事務不能立即獲得鎖(表鎖或行鎖),就會在這片內存區域中找到一個空閑的 slot,構造一個包含該事務以及鎖信息的 srv_slot_t 對象放入這個 slot,并標記這個 slot 為已使用狀態。

last_slot 屬性也是個指針,初始化時,和 waiting_threads 屬性指向相同的內存地址。

隨著不斷有事務進入鎖等待狀態、以及處于鎖等待狀態的事務獲得鎖,last_slot 會不斷變化。

不過,不管怎么變化,last_slot 始終遵循一個原則,就是它指向的那個 slot,以及之后的所有 slot 都處于空閑狀態。

為什么需要 last_slot?

因為后臺線程檢查鎖等待是否超時,會從后往前遍歷 waiting_threads 屬性指向的內存區域。

如果沒有 last_slot,每次遍歷都需要從最后一個 slot 開始,到第一個 slot 為止,檢查每個 slot 對應的鎖等待是否超時。

然而,通常情況下,waiting_threads 屬性指向的內存區域中的 102400 個 slot,其中大部分都是空閑的。

空閑 slot 沒有被正在等待鎖的事務占用,實際上不需要檢查鎖等待是否超時。

如果沒有 last_slot,每次檢查鎖等待是否超時,都要遍歷所有 slot,顯然很浪費時間。

為了提升檢查鎖等待超時的效率,只需要遍歷已使用狀態的 slot 就可以了,這就需要有個東西來標識哪個范圍內的 slot 是已使用狀態,于是,就有了 last_slot。

有一點需要說明,如果某個事務曾經進入過鎖等待狀態,占用了某個 slot。某一輪檢查鎖等待超時之前,這個事務獲得了鎖,又會把它占用的那個 slot 重置為空閑狀態。

所以,last_slot 之前的那些 slot,并不全部是已使用狀態,也有一些是空閑的,但是這個數量應該不會很多,遍歷這些少量的空閑 slot,也不會浪費太多時間。

介紹完 waiting_threads、last_slot,終于輪到 wait_mutex 屬性了。

從屬性名上看,wait_mutex 屬性顯然是個互斥量。

多個事務同時讀寫 last_slot 屬性,可能造成沖突,這就需要有個東西來保證同一時刻只有一個線程讀寫 last_slot 屬性,于是就有了 wait_mutex

2.4 那就發個鎖等待通知

事務想要加鎖(表鎖或行鎖),如果發生了鎖等待,新出現的鎖等待,和原來那些鎖等待攪和在一起,有可能會出現死鎖。

為了及時發現死鎖,事務進入鎖等待狀態之前,會觸一個事件,通知后臺線程出現了鎖等待。

這個事件就保存在鎖模塊結構的 timeout_event 屬性中。

監聽 timeout_event 事件的后臺線程收到通知之后,就會開始檢查是否發生了死鎖。如果檢查發現了死鎖,就及時解決。

3. 總結

鎖模塊結構的 rec_hash 屬性是個哈希表,分為很多小格子,每個格子管理一個行鎖結構鏈表。

latches 屬性用于保證同一時刻只有一個線程讀寫 rec_hash 屬性的同一個格子對應的行鎖結構鏈表,以及同一時刻只有一個線程讀寫同一個表對象的 locks 鏈表。

waiting_threads 屬性指向一片分為 102400 個 slot 的內存區域,每個等待獲得鎖的事務會占用其中一個 slot。

last_slot 屬性用于減少檢查鎖等待超時需要遍歷的 slot 數量,提升效率。

wait_mutex 屬性用于保證同一時刻只有一個線程讀寫 last_sot 屬性。

timeout_event 屬性用于發生鎖等待時,通知后臺線程及時檢查是否出現了死鎖。

更多技術文章,請訪問:https://opensource.actionsky.com/

關于 SQLE

SQLE 是一款全方位的 SQL 質量管理平臺,覆蓋開發至生產環境的 SQL 審核和管理。支持主流的開源、商業、國產數據庫,為開發和運維提供流程自動化能力,提升上線效率,提高數據質量。

? Github:https://github.com/actiontech/sqle

📚 文檔:https://actiontech.github.io/sqle-docs/

💻 官網:https://opensource.actionsky.com/sqle/

👥 微信群:請添加小助手加入 ActionOpenSource

🔗 商業支持:https://www.actionsky.com/sqle

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/23011.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/23011.shtml
英文地址,請注明出處:http://en.pswp.cn/web/23011.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

LabVIEW開發EOL功能測試系統

LabVIEW開發EOL功能測試系統 介紹了一種基于LabVIEW開發的EOL功能測試系統方案&#xff0c;涵蓋軟件架構、工作流程、模塊化設計、低耦合性、易于修改與維護、穩定性及硬件選型。系統通過高效的CAN通信實現對電機控制器的全面測試&#xff0c;確保運行可靠并支持未來的升級需求…

危機公關之負面信息優化技巧解析

當今時代&#xff0c;網絡發布信息沒有任何門檻&#xff0c;任何人可以通過互聯網發布信息&#xff0c;這使負面信息產生的可能性大大提高&#xff0c;企業形成危機的可能性也大大提高。針對網絡上的負面信息處理得當可能并不會對品牌造成傷害&#xff0c;處理不當就很可能給企…

QT之可拖動布局研究

1. 背景 最開始只用到了最基本的水平布局 、垂直布局。它的好處就是窗口整體縮放后&#xff0c;控件也自動等比例縮放。 但是比如水平布局之中的控件寬度比例、垂直布局之中的控件高度比例都是固定的。 平時也不怎么開發界面&#xff0c;最近有個需求&#xff0c;想界面上的…

Atlassian企業日技術分享:AI在ITSM中的創新實踐與應用、Jira服務管理平臺AI功能介紹

2024年5月17日&#xff0c;Atlassian中國合作伙伴企業日活動在上海成功舉辦。活動以“AI協同 創未來——如何利用人工智能提升團隊協作&#xff0c;加速產品交付”為主題&#xff0c;深入探討了AI技術在團隊協作與產品交付中的創新應用與實踐&#xff0c;吸引了眾多業內專家、企…

ros1中的server服務的創建與使用函數指針類型別名請求處理函數

ros1中的server服務的創建與使用函數指針類型別名請求處理函數 法一: #include "ros/ros.h" //自定義消息 #include "trilateration/trilateration_srvs.h"void handleDeletePosint(const trilateration::trilateration_srvs::Request& req, trilate…

深圳比創達電子EMC|EMC與EMI一站式解決方案:攻克電磁兼容難題

在當今這個科技日新月異、電子產品層出不窮的時代&#xff0c;電磁兼容&#xff08;EMC&#xff09;與電磁干擾&#xff08;EMI&#xff09;問題愈發凸顯其重要性。為了確保電子設備的正常運行&#xff0c;減少電磁干擾對環境和人體的影響&#xff0c;EMC與EMI一站式解決方案成…

【回眸】Linux內核(十)system()函數與popen()函數

前言 system()函數的作用是執行一個shell腳本或者shell指令 popen與system()函數類似,不同點是popen()函數可以獲取運行的shell腳本或者命令的輸出結果 system() 函數參數 #include <stdlib.h> int system(const char *comand) 參考示例代碼: #include <stdio.…

2023年全國消費品“增品種、提品質、創品牌”三品戰略發展成果報告

來源&#xff1a;賽迪&歐特歐 近期歷史回顧&#xff1a; 2023工業無線電磁環境白皮書——有色金屬制造行業.pdf 2024出海企業人才發展實踐指南.pdf 2024年全球電子商務市場.pdf 寶鋼低碳鋼鐵技術策劃及開發-鐘勇.pdf 2023-2024年度中國智能制造產業發展報告.pdf 2024精準醫…

【AI大模型】Function Calling

目錄 什么是Function Calling 示例 1&#xff1a;調用本地函數 Function Calling 的注意事項 支持 Function Calling 的國產大模型 百度文心大模型 MiniMax ChatGLM3-6B 訊飛星火 3.0 通義千問 幾條經驗總結 什么是Function Calling Function Calling 是一種函數調用機…

【C++ | 構造函數】類的構造函數詳解

&#x1f601;博客主頁&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客內容&#x1f911;&#xff1a;&#x1f36d;嵌入式開發、Linux、C語言、C、數據結構、音視頻&#x1f36d; ?發布時間?&#xff1a;2024-06-06 0…

HCIA-RS基礎-VLAN配置

目錄 前言創建拓撲創建VLAN查看創建的VLAN配置trunk口并放行VLAN配置access接口查看所有vlan基本信息測試網絡連通性命令合集 前言 VLAN定義&#xff1a;VLAN是一種將局域網內的設備從邏輯上劃分成一個個網段&#xff0c;從而實現虛擬工作組的新興數據交換技術。VLAN優點&…

設計模式-單例模式(創建型)

創建型-單例模式 了解單例 單例模式是一種創建型設計模式&#xff0c;它提供了一種創建對象的最佳方式;它必須保證&#xff1a; 單例類只能有一個實例化對象&#xff1b;單例類必須創建自己的唯一實例&#xff1b;單例類必須給其他對象提供實例&#xff1b; 另外&#xff1a;…

【面試筆記】嵌入式軟件工程師,汽車電子軟件相關

文章目錄 1. C語言基礎1.1 const1.2 static1.3 回調函數的用法1.4 宏定義1.5 編譯、鏈接過程1.6 堆與棧的區別&#xff1f;1.7 簡單的字符串算法題&#xff0c;C語言實現1.7.1 給定一個字符串&#xff0c;按順序篩選出不重復的字符組成字符串&#xff0c;輸出該字符串1.7.2 給定…

Python3 迭代器和生成器

前言 本文主要介紹Python中的迭代器和生成器&#xff0c;主要內容包括 迭代器概述、生成器簡介。 文章目錄 前言一、迭代器簡介二、生成器簡介 一、迭代器簡介 在 Python 中&#xff0c;迭代器(iterator)是一個實現了迭代器協議&#xff08;Iterator Protocol&#xff09;的…

opencv進階 ——(十一)基于RMBG實現生活照生成寸照

實現步驟 1、檢測人臉&#xff0c;可以使用opencv自帶的級聯分類器或者dlib實現人臉檢測 2、放大人臉范圍&#xff0c;調整到正常寸照尺寸 3、基于RMGB算法得到人像掩碼 4、生成尺寸相同的純色背景與當前人像進行ALPHA融合即可 alpha融合實現 void alphaBlend(cv::Mat&…

1 機器人軟件開發學習所需通用技術棧(一)

機器人軟件工程師技術路線&#xff08;如有缺失&#xff0c;歡迎補充&#xff09; 1. 機器人軟件開發工程師技術路線 1.1 基礎知識 C/C編程&#xff1a;掌握C/C語言基礎&#xff0c;包括數據結構、算法、內存管理等。操作系統&#xff1a;了解Linux或Windows等操作系統的基本…

android 13 aosp 預置so庫

展訊對應的main.mk配置 device/sprd/qogirn**/ums***/product/***_native/main.mk $(call inherit-product-if-exists, vendor/***/build.mk)vendor/***/build.mk PRODUCT_PACKAGES \libtestvendor///Android.bp cc_prebuilt_library_shared{name:"libtest",srcs:…

2.1 初識Windows程序

Windows程序設計是一種面向對象的編程。Windows操作系統以數據結構的形式定義了大量預定義的對象作為操作系統的數據類型。Windows動態鏈接庫提供了各種各樣的API接口函數供Windows應用程序調用。一個Windows應用程序是運行在Windows操作系統之上的。這些API接口函數的調用所實…

1349:【例4-10】最優布線問題

【解題思路】 最小生成樹模板題&#xff0c;求最小生成樹所有邊權加和。 該題輸入的是鄰接矩陣&#xff0c;因此使用鄰接矩陣解決該問題。當然也可以保存為鄰接表。 【參考代碼】 //示例代碼 Prim算法 #include <iostream> #include <cstring> #include <cs…

【Vue】路由的基本使用

文章目錄 一、固定5個固定的步驟二、代碼示例三、兩個核心步驟四、完整代碼 vue-router插件作用 修改地址欄路徑時&#xff0c;切換顯示匹配的組件 說明 Vue 官方的一個路由插件&#xff0c;是一個第三方包 官網 https://v3.router.vuejs.org/zh/ VueRouter的使用&#xff0…