共計 6024 個字符,預計需要花費 16 分鐘才能閱讀完成。
這篇“Mysql 鎖的內部實現機制是什么”文章的知識點大部分人都不太理解,所以丸趣 TV 小編給大家總結了以下內容,內容詳細,步驟清晰,具有一定的借鑒價值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“Mysql 鎖的內部實現機制是什么”文章吧。
注:所列舉代碼皆出自 Mysql-5.6
雖然現在關系型數據庫越來越相似,但其背后的實現機制可能大相徑庭。實際使用方面,因為 SQL 語法規范的存在使得我們熟悉多種關系型數據庫并非難事,但是有多少種數據庫可能就有多少種鎖的實現方法。
Microsoft Sql Server2005 之前只提供頁鎖,直到 2005 版本才開始支持樂觀并發、悲觀并發,樂觀模式下允許實現行級別鎖,在 Sql Server 的設計中鎖是一種稀缺資源,鎖的數量越多,開銷就越大,為了避免因為鎖的數量快速攀升導致性能斷崖式下跌,其支持一種稱為鎖升級的機制,一旦行鎖升級為頁鎖,并發性能就又回到原點。
事實上,即使在同一個數據庫,不同的執行引擎對鎖這一功能的詮釋依然是百家爭鳴。對于 MyISAM 而言僅僅支持表鎖,并發讀取尚可,并發修改可就捉襟見肘了。Innodb 則和 Oracle 非常相似,提供非鎖定一致性讀取、行鎖支持,與 Sql Server 明顯不同的是隨著鎖總數的上升,Innodb 僅僅只需要付出一點點代價。
行鎖結構
Innodb 支持行鎖,且對于鎖的描述并不會存在特別大的開銷。因此不需要鎖升級這一機制作為大量鎖導致性能下降之后的搶救措施。
摘自 lock0priv.h 文件,Innodb 對于行鎖的定義如下:
/** Record lock for a page */
struct lock_rec_t {
/* space id */
ulint space;
/* page number */
ulint page_no;
/**
* number of bits in the lock bitmap;
* NOTE: the lock bitmap is placed immediately after the lock struct
*/
ulint n_bits;
};
不難看出雖然并發控制可以細化到行級別,但是鎖以頁的粒度組織管理。Innodb 的設計中通過 space id、page number 兩個必要條件就可以確定唯一一個數據頁,n_bits 表示描述該頁行鎖信息需要多少 bit 位。
同一數據頁中每條記錄都分配唯一的連續的遞增序號:heap_no,若要知道某一行記錄是否上鎖,則只需要判斷位圖 heap_no 位置的數字是否為一即可。由于 lock bitmap 根據數據頁的記錄數量進行內存空間分配的,因此沒有顯式定義,且該頁記錄可能還會繼續增加,因此預留了 LOCK_PAGE_BITMAP_MARGIN 大小的空間。
/**
* Safety margin when creating a new record lock: this many extra records
* can be inserted to the page without need to create a lock with
* a bigger bitmap
*/
#define LOCK_PAGE_BITMAP_MARGIN 64
假設 space id = 20,page number = 100 的數據頁目前有 160 條記錄,heap_no 為 2、3、4 的記錄已經被鎖,則對應的 lock_rec_t 結構與數據頁應該被這樣刻畫:
注:
內存中的 lock bitmap 應該是線性分布的,圖中所示二維結構是為了方便描述
bitmap 與 lock_rec_t 結構是一塊連續內存,圖中引用關系也是繪圖需要
可以看到該頁對應的 bitmap 第二三四位置全部置一,描述一個數據頁行鎖所消耗內存從感官上相當有限,那具體占用多少呢?我們可以計算一下:
160 / 8 + 8 + 1 = 29byte。
160 條記錄對應 160bit
+ 8 是因為需要預留出 64bit
+ 1 是因為源碼中還預留了 1 字節
這里還額外 +1,應該是為了避免因為整除導致的結果數值偏小的問題。假如是 161 條記錄如果不 + 1 則計算出來的 20byte 不夠描述所有記錄的鎖信息(不動用預留位)。
摘自 lock0priv.h 文件:
/* lock_rec_create 函數代碼片段 */
n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN;
n_bytes = 1 + n_bits / 8;
/* 注意這里是分配的連續內存 */
lock = static_cast lock_t* ( mem_heap_alloc(trx- lock.lock_heap, sizeof(lock_t) + n_bytes)
* Gets the number of records in the heap.
* @return number of user records
*/
UNIV_INLINE ulint page_dir_get_n_heap(const page_t* page)
return(page_header_get_field(page, PAGE_N_HEAP) 0x7fff);
}
表鎖結構
Innodb 還支持表鎖,表鎖可分為兩大類: 意向鎖,自增鎖其數據結構定義如下:
摘自 lock0priv.h 文件
struct lock_table_t {
/* database table in dictionary cache */
dict_table_t* table;
/* list of locks on the same table */
UT_LIST_NODE_T(lock_t) locks;
};
摘自 ut0lst.h 文件
struct ut_list_node {
/* pointer to the previous node, NULL if start of list */
TYPE* prev;
/* pointer to next node, NULL if end of list */
TYPE* next;
#define UT_LIST_NODE_T(TYPE) ut_list_node TYPE
事務中鎖的描述
上述 lock_rec_t、lock_table_t 結構只是單獨的定義,鎖產生于事務之中,因此每個事務對應的行鎖、表鎖會有一個相應的鎖的結構,其定義如下:
摘自 lock0priv.h 文件
/** Lock struct; protected by lock_sys- mutex */
struct lock_t {
/* transaction owning the lock */
trx_t* trx;
/* list of the locks of the transaction */
UT_LIST_NODE_T(lock_t) trx_locks;
/**
* lock type, mode, LOCK_GAP or LOCK_REC_NOT_GAP,
* LOCK_INSERT_INTENTION, wait flag, ORed
*/
ulint type_mode;
/* hash chain node for a record lock */
hash_node_t hash;
/*! index for a record lock */
dict_index_t* index;
/* lock details */
union {
/* table lock */
lock_table_t tab_lock;
/* record lock */
lock_rec_t rec_lock;
} un_member;
};
lock_t 是根據每個事務每個頁 (或表) 來定義的,但是一個事務往往涉及到多個頁,因此需要鏈表 trx_locks 串聯起一個事務相關的所有鎖信息。除了需要根據事務查詢到所有鎖信息,實際場景還要求系統必須能夠快速高效的檢測出某個行記錄是否已經上鎖。因此必須有一個全局變量支持對行記錄進行鎖信息的查詢。Innodb 選擇了哈希表,其定義如下:
摘自 lock0lock.h 文件
/** The lock system struct */
struct lock_sys_t {
/* Mutex protecting the locks */
ib_mutex_t mutex;
/* 就是這里: hash table of the record locks */
hash_table_t* rec_hash;
/* Mutex protecting the next two fields */
ib_mutex_t wait_mutex;
/**
* Array of user threads suspended while waiting forlocks within InnoDB,
* protected by the lock_sys- wait_mutex
*/
srv_slot_t* waiting_threads;
/*
* highest slot ever used in the waiting_threads array,
* protected by lock_sys- wait_mutex
*/
srv_slot_t* last_slot;
/**
* TRUE if rollback of all recovered transactions is complete.
* Protected by lock_sys- mutex
*/
ibool rollback_complete;
/* Max wait time */
ulint n_lock_max_wait_time;
/**
* Set to the event that is created in the lock wait monitor thread.
* A value of 0 means the thread is not active
*/
os_event_t timeout_event;
/* True if the timeout thread is running */
bool timeout_thread_active;
};
函數 lock_sys_create 在 database start 之際負責初始化 lock_sys_t 結構。rec_hash 的 hash slot 數量由 srv_lock_table_size 變量決定。rec_hash 哈希表的 key 值通過頁的 space id,page number 計算得出。
摘自 lock0lock.ic、ut0rnd.ic 文件
/**
* Calculates the fold value of a page file address: used in inserting or
* searching for a lock in the hash table.
*
* @return folded value
*/
UNIV_INLINE ulint lock_rec_fold(ulint space, ulint page_no)
return(ut_fold_ulint_pair(space, page_no));
*/
UNIV_INLINE ulint ut_fold_ulint_pair(ulint n1, ulint n2)
return (
( (((n1 ^ n2 ^ UT_HASH_RANDOM_MASK2) 8) + n1)
^ UT_HASH_RANDOM_MASK
)
+ n2
);
}
這將意味著無法提供一個手段使得我們可以直接得知某一行是否上鎖。而是應該先通過其所在的頁得到 space id、page number 通過 lock_rec_fold 函數得出 key 值而后經過 hash 查詢得到 lock_rec_t,而后根據 heap_no 掃描 bit map,最終確定鎖信息。lock_rec_get_first 函數實現了上述邏輯:
這里返回的其實是 lock_t 對象,摘自 lock0lock.cc 文件
/**
* Gets the first explicit lock request on a record.
*
* @param block : block containing the record
* @param heap_no : heap number of the record
*
* @return first lock, NULL if none exists
*/
UNIV_INLINE lock_t* lock_rec_get_first(const buf_block_t* block, ulint heap_no)
lock_t* lock;
ut_ad(lock_mutex_own());
for (lock = lock_rec_get_first_on_page(block); lock;
lock = lock_rec_get_next_on_page(lock)
) { if (lock_rec_get_nth_bit(lock, heap_no)) {
break;
}
}
return(lock);
}
鎖維護以頁的粒度,不是一個最高效直接的方式,明顯的時間換空間,這種設計使得鎖的開銷很小。某一事務對任一行上鎖的開銷都是一樣的,鎖數量的上升也不會帶來額外的內存消耗。
每個事務都對應一個 trx_t 的內存對象,其中保存著該事務鎖信息鏈表和正在等待的鎖信息。因此存在如下兩種途徑對鎖進行查詢:
根據事務: 通過 trx_t 對象的 trx_locks 鏈表,再通過 lock_t 對象中的 trx_locks 遍歷可得某事務持有、等待的所有鎖信息。
根據記錄: 根據記錄所在的頁,通過 space id、page number 在 lock_sys_t 結構中定位到 lock_t 對象,掃描 bitmap 找到 heap_no 對應的 bit 位。
上述各種數據結構,對其整理關系如下圖所示:
注:
lock_sys_t 中的 slot 顏色與 lock_t 顏色相同則表明 lock_sys_t slot 持有 lock_t
指針信息,實在是沒法連線,不然圖很混亂
以上就是關于“Mysql 鎖的內部實現機制是什么”這篇文章的內容,相信大家都有了一定的了解,希望丸趣 TV 小編分享的內容對大家有幫助,若想了解更多相關的知識內容,請關注丸趣 TV 行業資訊頻道。