15 innodB的隱式鎖

一、知識準備之隱式鎖

參考:http://www.uml.org.cn/sjjm/201205302.asp

Innodb 實現了一個延遲加鎖的機制,來減少加鎖的數量,在代碼中稱爲隱式鎖(Implicit Lock)。隱式鎖中有個重要的元素,事務ID(trx_id)。隱式鎖的邏輯過程如下:

A. InnoDB的每條記錄中都一個隱含的trx_id字段,這個字段存在於簇索引的B+Tree中。

B. 在操作一條記錄前,首先根據記錄中的trx_id檢查該事務是否是活動的事務(未提交或回滾)。如果是活動的事務,首先將隱式鎖轉換爲顯式鎖(就是爲該事務添加一個鎖)。

C. 檢查是否有鎖衝突,如果有衝突,創建鎖,並設置爲waiting狀態。如果沒有衝突不加鎖,跳到E。

D. 等待加鎖成功,被喚醒,或者超時。

E. 寫數據,並將自己的trx_id寫入trx_id字段。Page Lock可以保證操作的正確性。


二、具體代碼

轉自:《InnoDB SMO & Page Extent & Lock & Latch》 by 何登成

InnoDB 的 insert 操作,對插入的記錄不加鎖,但是此時如果另一個線程進行當前讀,類似與以下的用例,session 2 會鎖等待 session 1,那麼這是如何實現的呢?

session 1:                			session 2:
set autocommit = ‘ off ’;
insert into c values (11, ’ aaa’);
					        select * from c where c1 = 11 lock in share mode;

下面是 session 2 的源碼跟蹤流程:

row_search_for_mysql();
sel_set_rec_lock();
…
	//  將記錄上的 implicit 鎖轉換爲 explicit 鎖
	lock_rec_convert_impl_to_expl();
		//  查詢當前記錄上是否存在 implicit 鎖
		// 1.  必須已經持有了 kernel mutex
		// 2.  獲取記錄上的 DB_TRX_ID 系統列,獲取事務 ID
		// 3.  根據事務 ID,判斷當前事務是否爲活躍事務
		// 4.  若爲活躍事務,則返回此活躍事務對象
		impl_trx = lock_clust_rec_some_has_impl(rec, index, offsets);
			ut_ad(mutex_own(&kernel_mutex));
			trx_id = row_get_rec_trx_id();
			trx_is_active(trx_id);
		//  判斷返回事務,是否含有 explicit 鎖;若有,直接返回;否則將
		// implicit 鎖轉化爲 explicit 鎖;由 session 2 完成 session 1 insert 記錄的加鎖
		lock_rec_has_expl(impl_trx);
		//  當前 session 1 不存在 explicit 鎖,因此直接創建一個鎖,鎖模式爲
		// LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP
		//  由於 insert 記錄上不可能有其他鎖,因此轉化直接成功,X 鎖加上
		lock_rec_add_to_queue();
//  完成 session 1  的 insert 操作的 implicit 到 explicit 鎖轉化之後,此時可以加 session 2
//  的 scan S 鎖,但是會等待 session 1 放鎖
lock_rec_lock();

備註:Insert 不加鎖,或者說是 Implicit Lock 的意義其實十分重大。從前面介紹的 Lock 結構中,我們可以分析出,其實 InnoDB 的一個鎖結構的開銷是比較大的。 或者說InnoDB 鎖一條記錄的開銷,與鎖一個頁面中所有記錄的開銷是一樣的。而 Insert 通過 Implicit 方式加鎖,極大的減輕了 Insert 時的鎖模塊開銷,對於 InnoDB 支持併發 Insert 操作,是一個極大的提升。


三、隱式鎖的重要問題——check Duplicate

轉自:《InnoDB SMO & Page Extent & Lock & Latch》 by 何登成

進一步參考: Innodb鎖系統 Insert/Delete 鎖處理及死鎖示例分析 http://fan0321.iteye.com/blog/1984364

這裏主要看聚集索引的check duplicate。check duplicate也就是保證在隱式鎖情況下,多個事務的insert是如何保證索引的unique的。

1、聚集索引check duplicate

ha_innobase::write_row();
	…
	row_ins_index_entry_low();
		//  做 search path,將 cursor 定位到第一個小於等於插入值的位置
		btr_cur_search_to_nth_level(PAGE_CUR_LE);
		// cursor 是 binary search 之後在葉節點定位的 insert 位置
		//  判斷 binary search 的結果,當前記錄與待 insert 記錄有幾個相同的列
		//  若相同列取值的列數量(cursor->low_match),超過當前索引的唯一鍵值數量,
		//  則可能存在唯一性鍵值衝突
		row_ins_duplicate_error_in_clust(cursor, entry, thr);
			n_unique = dict_index_get_n_uniques();
			if (cursor->low_match >= n_unique)
				//  對 cursor 對應的已有記錄加 S 鎖(可能會等待),保證記錄上的操作,包括:
				// Insert/Update/Delete 已經提交或者回滾
				// S 鎖已經可以保證其他事務的 insert 操作不能進行,因爲在真正
				// insert 操作進行時,會嘗試對 下一個record加 X 鎖,詳見下一章節分析
				row_ins_set_shared_rec_loc(LOCK_S);
					lock_clust_rec_read_check_and_lock();
						//  判斷 cursor 對應的記錄上是否存在 implicit 鎖(有活躍事務)
						//  若存在,則將 implicit 鎖轉化爲 explicit 鎖
						lock_rec_convert_impl_to_expl();
						lock_rec_lock();  //如果上面的隱式鎖轉化成功,此處加S鎖將會等待,直到活躍事務釋放鎖。
				// S 鎖加鎖完成之後,可以再次做判斷,最終決定是否存在 unique 衝突
				// 1.  判斷 insert 記錄與 cursor 對應的記錄取值是否相同
				// 2.  二級唯一鍵值鎖引,可以存在多個 NULL 值
				// 3.  最後判斷記錄的 delete_bit 狀態,判斷記錄是否被刪除提交
				row_ins_dupl_err_with_rec();
					cmp_dtuple_rec_with_match();
					return !rec_get_deleted_flag();

注意:S鎖加鎖成功之時,活躍事務應當提交或回滾並釋放鎖;但不管是提交還是回滾,cursor指向的record仍然存在,可能會有delete標誌(發生回滾)。

2、輔助索引check duplicate

a、聚簇索引 primary key 是唯一的;非聚簇唯一索引,其索引也是唯一的。
b、若 insert 記錄與聚簇索引項完全相同,並且聚簇索引項爲刪除項,則直接將其刪除標記設置爲 0,並在刪除項上做 update。
c、若 insert 記錄與非聚簇唯一索引項鍵值完全相同, 並且非聚簇索引項爲刪除項, 此時並不一定修改項狀態,還需要判斷兩者對應的 primary key 是否相同,若 primary key 也相同,則重用項;否則,插入新項。
d、聚簇索引中,相同 primary key 取值的項,最多隻有一項,不可能存在多項。
e、非聚簇唯一索引,索引鍵值相同的項可能有多項,但是這些項,其 primary  key 是不同的;而且,這些項,只有一個是有效項,其他項都爲已刪除的提交項。
f、聚簇索引的唯一性檢查,只需要檢測 insert 對應的記錄即可,因爲只有一項;非聚簇唯一索引的唯一性檢測,需要向後檢查多條鍵值相同記錄

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章