InnoDB 是 MySQL 最主流的儲存引擎,支援事務與行級鎖是其核心優勢。本文基於 InnoDB 源碼 (以 5.7/8.0 為參考),深度剖析行鎖的資料結構、加鎖/解鎖流程、等待機制與同步原語,為效能調優與死鎖分析打下理論基礎。
// 鎖系統全域管理
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; // 掛在雜湊桶鏈表
};
(space_id << 32) | page_no → 定位索引頁。lock_t* 鏈表,同一頁面所有鎖對象。lock_rec_createn_bits = page_dir_get_n_heap(page) + MARGIN。1 + n_bits/8 bytes。trx->lock.lock_heap 分配 lock_t 記憶體 (含位圖)。heap_no 對應 bit = 1。lock_sys->rec_hash。trx->lock.trx_locks。lock_rec_has_expl(space_id, page_no) 查雜湊表獲取 lock_t* 鏈表。bitmap[heap_no/8] & (1 << (heap_no%8))。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,加入隊列,返回成功
lock_wait_suspend_threadlock_sys->waiting_threads 陣列分配一個 srv_slot_t (大小 = 最大併發執行緒數)。os_event_t (條件變數)。os_event_wait_low() → pthread_cond_wait() 掛起執行緒。pthread_cond_wait → os_cond_wait → os_event_wait_low → lock_wait_suspend_thread → row_mysql_handle_errors。lock_trx_release_lockstrx->lock.trx_locks 釋放所有鎖。lock_rec_dequeue_from_page:清除位圖 bit,若位圖全 0 從雜湊表移除 lock_t。lock_rec_get_first_waiter。lock_grant 授權:lock_t->trx 找到等待事務 → trx->lock.wait_thr 找到執行緒 → thr->slot->event 找到條件變數。os_event_set() → pthread_cond_broadcast() 喚醒所有等待執行緒。lock_t 含位圖:sizeof(lock_t) + 1 + n_bits/8 bytes。lock_t 總計 ~100 bytes。lock_t。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_funclock_word,成功即獲取。pthread_mutex_trylock(&os_fast_mutex) 保護 lock_word。sync_wait_array cell,設 waiters=1,呼叫 os_event_wait_low() 掛起。mutex_exit_funclock_word (原子操作或 os_fast_mutex 保護)。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。
SELECT ... FOR UPDATE → 建立 lock_t (X),位圖 bit5=1。FOR UPDATE → 發現 T1 持有 X 鎖衝突 → 建立 lock_t (LOCK_WAIT),掛入等待隊列,執行緒掛起。COMMIT → 釋放鎖 → 遍歷等待隊列 → 喚醒 T2 執行緒 → T2 獲取鎖繼續執行。SHOW ENGINE INNODB STATUS)、鎖等待熱點、調優 innodb_lock_wait_timeout、innodb_spin_wait_delay 等參數。