首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

InnoDB 行鎖源碼深度解析 (一):鎖結構、關鍵流程與同步機制

     編輯:MySQL 綜合教學

前言

InnoDB 是 MySQL 最主流的儲存引擎,支援事務與行級鎖是其核心優勢。本文基於 InnoDB 源碼 (以 5.7/8.0 為參考),深度剖析行鎖的資料結構、加鎖/解鎖流程、等待機制與同步原語,為效能調優與死鎖分析打下理論基礎。

一、行鎖系統總體架構

1. 核心資料結構


// 鎖系統全域管理
struct lock_sys_t {
    hash_table_t* rec_hash;       // 行鎖雜湊表,key = (space_id, page_no)
    srv_slot_t* waiting_threads;  // 等待執行緒槽位陣列
    ib_mutex_t mutex;             // 保護鎖系統的互斥鎖
};

// 行鎖對象 (每個索引頁一個 lock_rec_t)
struct lock_rec_t {
    ulint space;                  // 表空間 ID
    ulint page_no;                // 頁號
    ulint n_bits;                 // 頁面記錄數 (每記錄 1 bit)
    byte bitmap[1 + n_bits/8];    // 位圖,bit=1 表示該 heap_no 加鎖
};

// 事務鎖鏈表節點
struct lock_t {
    trx_t* trx;                   // 所屬事務
    enum lock_type {TABLE, REC} type;
    union {
        lock_table_t table;       // 表鎖
        lock_rec_t rec;           // 行鎖
    } un_member;
    enum lock_mode {S, X, IS, IX, AUTO_INC} mode;
    bool wait;                    // 是否等待中
    UT_LIST_NODE_T(lock_t) trx_locks;  // 掛在事務的鎖鏈表
    UT_LIST_NODE_T(lock_t) hash;       // 掛在雜湊桶鏈表
};

2. 雜湊表設計

  • Key: (space_id << 32) | page_no → 定位索引頁。
  • Value: lock_t* 鏈表,同一頁面所有鎖對象。
  • 查找複雜度 O(1),支援高併發加鎖/檢查。

二、關鍵流程詳解

1. 建立行鎖 lock_rec_create

  1. 計算頁面記錄數 n_bits = page_dir_get_n_heap(page) + MARGIN。
  2. 位圖大小 1 + n_bits/8 bytes。
  3. 從事務鎖堆 trx->lock.lock_heap 分配 lock_t 記憶體 (含位圖)。
  4. 初始化位圖,設定 heap_no 對應 bit = 1。
  5. 插入全域雜湊表 lock_sys->rec_hash。
  6. 插入事務鎖鏈表 trx->lock.trx_locks。

2. 檢查記錄是否已加鎖 lock_rec_has_expl

  1. 根據 (space_id, page_no) 查雜湊表獲取 lock_t* 鏈表。
  2. 遍歷鏈表,檢查鎖模式 (S/X) 是否衝突。
  3. 若存在,檢查位圖 bitmap[heap_no/8] & (1 << (heap_no%8))。
  4. 返回是否持有更強/相容鎖。

3. 加鎖主流程 lock_rec_lock_slow


lock_rec_lock_slow(...)
  -> 查雜湊表找頁面鎖鏈表
  -> 遍歷鎖對象:
       若當前事務已持有更強/相容鎖 (lock_rec_has_expl) → 直接設 bit 返回成功
       若發現衝突鎖 (其他事務持有不相容模式):
            建立 LOCK_WAIT 模式 lock_t
            lock_rec_enqueue_waiting() 掛入等待隊列
            返回 DB_LOCK_WAIT
       無衝突 → 設 bit,加入隊列,返回成功

4. 鎖等待與執行緒掛起 lock_wait_suspend_thread

  1. 從全域 lock_sys->waiting_threads 陣列分配一個 srv_slot_t (大小 = 最大併發執行緒數)。
  2. 初始化槽位內的 os_event_t (條件變數)。
  3. 呼叫 os_event_wait_low() → pthread_cond_wait() 掛起執行緒。
  4. 調用堆疊:pthread_cond_wait → os_cond_wait → os_event_wait_low → lock_wait_suspend_thread → row_mysql_handle_errors。

5. 解鎖與喚醒 lock_trx_release_locks

  1. 事務 Commit/Rollback 時遍歷 trx->lock.trx_locks 釋放所有鎖。
  2. 釋放行鎖 lock_rec_dequeue_from_page:清除位圖 bit,若位圖全 0 從雜湊表移除 lock_t。
  3. 檢查該頁面等待隊列 lock_rec_get_first_waiter。
  4. 若有等待者,呼叫 lock_grant 授權:
  5. 從 lock_t->trx 找到等待事務 → trx->lock.wait_thr 找到執行緒 → thr->slot->event 找到條件變數。
  6. 呼叫 os_event_set() → pthread_cond_broadcast() 喚醒所有等待執行緒。

三、行鎖記憶體開銷與重用優化

1. 開銷估算

  • 每個 lock_t 含位圖:sizeof(lock_t) + 1 + n_bits/8 bytes。
  • 典型頁面 200 條記錄 → 位圖 ~25 bytes,lock_t 總計 ~100 bytes。
  • 若單筆記錄獨佔一把鎖 → 100 bytes/行;若全頁記錄共用一把鎖 (同事務、同模式) → 0.5 bytes/行。

2. 鎖重用機制

  • 同事務、同頁面、同模式:直接設位圖 bit,不創建新 lock_t。
  • 同事務、同記錄、現有鎖強於請求鎖 (X > S):直接返回成功,不創建新鎖。
  • 此機制大幅減少鎖對象數量,降低記憶體壓力與遍歷開銷。

四、InnoDB 同步原語融合實現

InnoDB 未直接使用 pthread_mutex,而是融合 自旋鎖 + 快速互斥量 + 條件變數 實現 ib_mutex_t,達成高併發低延遲。

資料結構


struct ib_mutex_t {
    os_event_t event;           // 條件變數
    volatile lock_word_t lock_word; // 原子鎖字 (自旋用)
    os_fast_mutex_t os_fast_mutex;  // 非原子平台回退用
    ulint waiters;              // 是否有等待者
};

獲取鎖 mutex_enter_func

  1. 自旋階段:原子 CAS 檢查 lock_word,成功即獲取。
  2. 非原子平台:pthread_mutex_trylock(&os_fast_mutex) 保護 lock_word。
  3. 獲取失敗:分配 sync_wait_array cell,設 waiters=1,呼叫 os_event_wait_low() 掛起。
  4. 被喚醒後跳回步驟 1 重試。

釋放鎖 mutex_exit_func

  1. 重置 lock_word (原子操作或 os_fast_mutex 保護)。
  2. 若 waiters 為真,呼叫 mutex_signal_object() → pthread_cond_broadcast() 喚醒等待者。

五、等待事件 os_event_t 實現


struct os_event_t {
    os_cond_t cond_var;         // pthread_cond_t
    ibool is_set;               // true=不阻塞
    os_fast_mutex_t os_mutex;   // 保護 cond_var
};

// os_event_set: 設 is_set=true → pthread_cond_broadcast
// os_event_wait: 若 is_set=false → pthread_cond_wait 阻塞

六、實戰案例還原

表 t,id=1 位於頁面 (space=1, page_no=20),heap_no=5。

  1. 事務 T1 SELECT ... FOR UPDATE → 建立 lock_t (X),位圖 bit5=1。
  2. 事務 T2 同樣 FOR UPDATE → 發現 T1 持有 X 鎖衝突 → 建立 lock_t (LOCK_WAIT),掛入等待隊列,執行緒掛起。
  3. T1 COMMIT → 釋放鎖 → 遍歷等待隊列 → 喚醒 T2 執行緒 → T2 獲取鎖繼續執行。

總結

  • 行鎖以 位圖 高效表示,按 頁面 組織,雜湊表 索引。
  • 鎖等待採用 條件變數 掛起執行緒,解鎖時 廣播喚醒。
  • 同步原語融合 自旋 + 條件變數,適應不同硬體平台。
  • 理解源碼流程有助於分析死鎖 (SHOW ENGINE INNODB STATUS)、鎖等待熱點、調優 innodb_lock_wait_timeout、innodb_spin_wait_delay 等參數。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved