成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

MySQL 核心模塊揭秘

數據庫 MySQL
鎖模塊結構的 rec_hash 屬性是個哈希表,分為很多小格子,每個格子管理一個行鎖結構鏈表。latches 屬性用于保證同一時刻只有一個線程讀寫 rec_hash 屬性的同一個格子對應的行鎖結構鏈表,以及同一時刻只有一個線程讀寫同一個表對象的 locks 鏈表。

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_hash、prdt_hash、prdt_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_latch、page_shards、table_shards。

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

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

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

page_shards、table_shards 的類型分為 Page_shards、Table_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_mutex、waiting_threads、last_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 屬性用于發生鎖等待時,通知后臺線程及時檢查是否出現了死鎖。

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

責任編輯:武曉燕 來源: 愛可生開源社區
相關推薦

2024-04-03 08:20:53

MySQL核心模塊

2024-05-15 09:05:42

MySQL核心模塊

2024-08-28 08:50:11

MySQL核心模塊

2024-03-27 13:33:00

MySQLInnoDB事務

2024-08-07 14:58:00

MySQL釋放鎖核心模塊

2024-10-30 10:38:08

2024-10-16 11:11:51

隔離InnoDB死鎖

2024-05-29 10:17:01

2024-09-04 08:44:18

MySQL核心模塊

2025-02-26 08:26:38

2024-11-05 10:52:07

2010-01-26 14:04:02

2025-01-17 08:17:55

2009-07-21 09:06:14

開發團隊Windows 7

2022-07-12 10:38:25

分布式框架

2021-06-21 17:00:05

云計算Hologres云原生

2024-05-08 16:54:21

Python編程開發

2023-08-24 10:33:19

serviceexportsinfo類

2019-01-23 10:42:21

華為云

2013-07-08 16:24:13

軟件定義網絡SDN
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: www.国产精品| 欧美精品福利视频 | 日本免费视频在线观看 | 国产精品二区三区 | 精品国产18久久久久久二百 | 久久久久久综合 | 国产精品一区二区在线播放 | 日韩国产高清在线观看 | 久久不卡 | 午夜在线免费观看视频 | 亚州成人 | 欧美一区二区三区视频在线观看 | 综合五月婷 | 国产福利在线免费观看 | 精品视频在线播放 | 一区二区国产精品 | 久久久夜夜夜 | 亚洲人成人一区二区在线观看 | 国产成人99久久亚洲综合精品 | 亚洲一区二区三区四区视频 | 国产精品视频中文字幕 | 91精品久久久久 | 美女视频网站久久 | 亚洲精品视频网站在线观看 | 欧美一区二区三区久久精品 | 羞羞在线视频 | 精品免费国产视频 | 国产精品亚洲一区 | 久久久久久国产免费视网址 | 羞羞的视频在线 | 国产精品色婷婷久久58 | 久久久久久久久毛片 | 色婷婷激情综合 | 亚洲一区二区 | 日韩免费看视频 | 欧美v日韩| 麻豆精品国产91久久久久久 | 亚洲网站在线观看 | 久草在线视频中文 | 亚洲国产视频一区二区 | 在线欧美日韩 |