日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

Innodb锁系统 Insert/Delete 锁处理及死锁示例分析

發布時間:2024/1/17 windows 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Innodb锁系统 Insert/Delete 锁处理及死锁示例分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

A.INSERT

插入操作在函數btr_cur_optimistic_insert->btr_cur_ins_lock_and_undo->lock_rec_insert_check_and_lock這里進行鎖的判斷,我們簡單的看看這個函數的流程:

1.首先先看看欲插入記錄之后的數據上有沒有鎖,

? ?next_rec = page_rec_get_next_const(rec);

? ?next_rec_heap_no = page_rec_get_heap_no(next_rec);

? ?lock = lock_rec_get_first(block, next_rec_heap_no);

如果lock為空的話,對于非聚集索引,還需要更新page上的最大事務ID。

實際上這里是比較松散的檢查,大并發插入的時候,可以大大的降低創建鎖開銷。

那么其他事務如何發現這些新插入的記錄呢(重復插入同一條記錄顯然應該被阻塞),這里會有個判斷,其他事務去看看

新插入記錄的事務是否還是活躍的,如果還是活躍的,那么就為這個事務主動增加一個鎖記錄(所謂的隱式鎖就是么有鎖。。。。),這個判斷是在檢查是否存在沖突鍵的時候進行的(row_ins_duplicate_error_in_clust->row_ins_set_shared_rec_lock->lock_clust_rec_read_check_and_lock->lock_rec_convert_impl_to_expl

row_ins_set_shared_rec_lock的目的是為了向記錄上加一個LOCK_REC_NOT_GAP的LOCK_S鎖,也就是非GAP的記錄S鎖,如果發現記錄上有X鎖(隱式鎖轉換為LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP),顯然是需要等待的(返回DB_LOCK_WAIT)

?

?

這里設置inherit為FALSE,然后返回DB_SUCCESS;

至于inherit的作用,稍后再議!

?

2.如果lock不為空,這意味著插入記錄的下一個記錄上存在鎖,設置inherit為TRUE.

檢查下一個記錄上的鎖是否和LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION相互沖突

? ? if (lock_rec_other_has_conflicting(

? ? ? ? ? ? LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION,

? ? ? ? ? ? block, next_rec_heap_no, trx)) {

?

? ? ? ? /* Note that we may get DB_SUCCESS also here! */

? ? ? ? err = lock_rec_enqueue_waiting(LOCK_X | LOCK_GAP

? ? ? ? ? ? ? ? ? ? ? ? ? ?| LOCK_INSERT_INTENTION,

? ? ? ? ? ? ? ? ? ? ? ? ? ?block, next_rec_heap_no,

? ? ? ? ? ? ? ? ? ? ? ? ? ?index, the);

?

如果有別的事務在下一個記錄上存在顯式的鎖請求,并且和鎖模式( LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION) 沖突,那么

這時候當前事務就需要等待。

?

如果別的事務持有一個GAP類型的鎖以等待插入,我們認為這個鎖和當前插入不沖突。

如何判定鎖之間是否沖突,在上一篇博客(http://mysqllover.com/?p=425)已經介紹過,不再贅述.

?

當檢查到存在沖突的事務,我們就將一個鎖模式為LOCK_X | LOCK_GAP|LOCK_X | LOCK_GAP 加入到請求隊列中(調用函數lock_rec_enqueue_waiting),這里也會負責去檢查死鎖。

?

注意在加入等待隊列的時候可能會返回DB_SUCCESS,例如死鎖發生,但選擇另外一個事務為犧牲者。

?

我們上面提到變量inherit,在存在下一個記錄鎖時會設置為TRUE,在上層函數btr_cur_optimistic_insert,會據此進行判斷:

? ? if (!(flags & BTR_NO_LOCKING_FLAG) && inherit) {

? ? ? ? lock_update_insert(block, *rec);

? ? } ?

注意當我們執行到這部分邏輯時err為DB_SUCCESS,表示鎖檢查已經通過了。

BTR_NO_LOCKING_FLAG表示不做記錄鎖檢查

對于optimistic_insert, flags值為0

對于pessimistic_insert,flags值為BTR_NO_UNDO_LOG_FLAG | BTR_NO_LOCKING_FLAG | BTR_KEEP_SYS_FLAG


因此對于樂觀更新(無需修改BTREE結構),當inherit被設置為TRUE時,總會調用lock_update_insert

根據注釋,lock_update_insert用于繼承下一條記錄的GAP鎖,流程如下

1.首先獲取插入的記錄的heap no和下一條記錄的heap no

? ? ? ? receiver_heap_no = rec_get_heap_no_new(rec);

? ? ? ? donator_heap_no = rec_get_heap_no_new(

? ? ? ? ? ? page_rec_get_next_low(rec, TRUE));

?

其中receiver_heap_no是當前記錄,donator_heap_no是下一條記錄

?

2.調用lock_rec_inherit_to_gap_if_gap_lock函數,將donator_heap_no上所有非INSERT INTENTION且非LOCK_REC_NOT_GAP的記錄鎖

轉移給receiver_heap_no

遍歷donator_heap_no上的所有記錄鎖,繼承鎖的判定條件如下:

? ? ? ? if (!lock_rec_get_insert_intention(lock)

? ? ? ? ? ? && (heap_no == PAGE_HEAP_NO_SUPREMUM

? ? ? ? ? ? || !lock_rec_get_rec_not_gap(lock))) {

?

? ? ? ? ? ? lock_rec_add_to_queue(LOCK_REC | LOCK_GAP

? ? ? ? ? ? ? ? ? ? ? ? ? | lock_get_mode(lock),

? ? ? ? ? ? ? ? ? ? ? ? ? block, heir_heap_no,

? ? ? ? ? ? ? ? ? ? ? ? ? lock->index, lock->trx);

? ? ? ? }

?

注意這里有對SUPREMUM記錄的特殊處理。

也就是說,成功插入了一條記錄,其他持有該記錄的下一條記錄上鎖的事務也會持有新插入記錄上的GAP鎖。

?

說起INSERT,就不得不提到一個有趣的死鎖案例。也就是bug#43210(http://bugs.mysql.com/bug.php?id=43210)

DROP TABLE t1;

CREATE TABLE `t1` (

? `a` int(11) NOT NULL,

? `b` int(11) DEFAULT NULL,

? PRIMARY KEY (`a`),

? KEY `b` (`b`)

) ENGINE=InnoDB;

insert into t1 values (1,19),(8,12);

?

Session 1:

set autocommit = 0;

insert into t1 values (6,12);

?

Session 2:

set autocommit = 0;

insert into t1 values (6,12); ?//阻塞住,同時將session1的鎖轉換為顯示鎖。等待記錄上的S鎖 (查找dup key)

/****

session 1上的轉為顯式鎖:lock_mode X locks rec but not gap

session 2等待的鎖:lock mode S locks rec but not gap waiting

***/

Session 3:

set autocommit = 0;

insert into t1 values (6,12); ?//阻塞住,和session2 同樣等待S鎖,lock mode S locks rec but not gap waiting

?

Session 1:

ROLLBACK;

?

Session 2:

執行插入成功

這時候Session 2持有的鎖為主鍵記錄上的:

lock mode S locks rec but not gap

lock mode S locks gap before rec

lock_mode X locks gap before rec insert intention

?

Session3:

被選為犧牲者,回滾掉。

?

很容易重現,當session 1回滾時,session2和session3提示死鎖發生。

?

這里的關鍵是當ROLLBACK時,實際上是在做一次delete操作,backtrace如下:

trx_general_rollback_for_mysql->….->row_undo->row_undo_ins->row_undo_ins_remove_clust_rec->btr_cur_optimistic_delete->lock_update_delete->lock_rec_inherit_to_gap

?

我們來跟蹤一下創建鎖的軌跡

s1的事務0x7fdfd80265b8

s2的事務0x7fdfe0007c68

s3的事務0x7fdff00213f8

?

s1 , type_mode=1059 ? ? //s2為s1轉換隱式鎖為顯式鎖,

s2, ?type_mode=1282 ? ?//檢查重復鍵,需要加共享鎖,被s1 block住,等待S鎖

s3, ?type_mode=1282 ? ?// 被s1 block住,等待S鎖

?

s1, type_mode=547 ? ? ? //s1回滾,刪除記錄,lock_update_delete鎖繼承,

s2, type_mode=546 ? ? ? ?//創建s鎖 ?LOCK_GAP | LOCK_REC | LOCK_S

s3, type_mode=546 ? ? ? ?//創建s鎖 ? LOCK_GAP | LOCK_REC | LOCK_S


s2, type_mode=2819 ? // LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION

s3, type_mode=2819 ? // ?LOCK_X | LOCK_GAP | LOCK_INSERT_INTENTION

?

看看show engine innodb status打印的死鎖信息:

insert into t1 values (6,12)

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 137 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1` trx id FE3BFA70 lock_mode X locks gap before rec insert intention waiting

*** (2) TRANSACTION:

TRANSACTION FE3BFA6F, ACTIVE 143 sec inserting, thread declared inside InnoDB 1

mysql tables in use 1, locked 1

4 lock struct(s), heap size 1248, 2 row lock(s)

MySQL thread id 791, OS thread handle 0x7fe2d4ea1700, query id 2613 localhost root update

insert into t1 values (6,12)

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 137 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1` trx id FE3BFA6F lock mode S locks gap before rec

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 137 page no 3 n bits 72 index `PRIMARY` of table `test`.`t1` trx id FE3BFA6F lock_mode X locks gap before rec insert intention waiting

*** WE ROLL BACK TRANSACTION (2)

?

從上面的分析,我們可以很容易理解死鎖為何發生。s1插入記錄,s2插入同一條記錄,主鍵沖突,s2將s1的隱式鎖轉為顯式鎖,同時s2向隊列中加入一個s鎖請求;

s3同樣也加入一個s鎖請求;

當s1回滾后,s2和s3獲得s鎖,但隨后s2和s3又先后請求插入意向鎖,因此鎖隊列為:

s2(S GAP)<—s3(S GAP)<—s2(插入意向鎖)<–s3(插入意向鎖) ? s3,s2,s3形成死鎖。

B.DELETE

Innodb的delete操作實際上只是做標記刪除,而不是真正的刪除記錄;真正的刪除是由Purge線程來完成的。

DELETE操作的記錄加鎖,是在查找記錄時完成的。這一點,我們在上一節已經提到了。

上面我們有提到,對插入一條記錄做回滾時,實際上是通過undo來做delete操作。這時候有一個lock_update_insert操作,我們來看看這個函數干了什么:

1.首先獲取將被移除的記錄HEAP NO和下一條記錄的HEAP NO

? ? ? ? heap_no = rec_get_heap_no_new(rec);

? ? ? ? next_heap_no = rec_get_heap_no_new(page

? ? ? ? ? ? ? ? ? ? ? ? ? ?+ rec_get_next_offs(rec,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?TRUE));

2.然后獲取kernel mutex鎖,執行:

將被刪除記錄上的GAP鎖轉移到下一條記錄上:

lock_rec_inherit_to_gap(block, block, next_heap_no, heap_no);

遍歷heao_no上的鎖對象,滿足如下條件時為下一個記錄上的事務創建新的鎖對象:

? ? ? ? if (!lock_rec_get_insert_intention(lock)

? ? ? ? ? ? && !((srv_locks_unsafe_for_binlog

? ? ? ? ? ? ? || lock->trx->isolation_level

? ? ? ? ? ? ? <= TRX_ISO_READ_COMMITTED)

? ? ? ? ? ? ?&& lock_get_mode(lock) == LOCK_X)) {

?

? ? ? ? ? ? lock_rec_add_to_queue(LOCK_REC | LOCK_GAP

? ? ? ? ? ? ? ? ? ? ? ? ? | lock_get_mode(lock),

? ? ? ? ? ? ? ? ? ? ? ? ? heir_block, heir_heap_no,

? ? ? ? ? ? ? ? ? ? ? ? ? lock->index, lock->trx);

? ? ? ? }

?

?

條件1:鎖對象不是插入意向鎖(INSERT INTENTION LOCK)

條件2:srv_locks_unsafe_for_binlog被設置為FALSE且隔離級別大于READ COMMITTED, 或者鎖類型為LOCK_S ? ??

?

和lock_update_insert類似,這里也會創建新的GAP鎖對象

?

當完成鎖表更新操作后,重置鎖bit并釋放等待的事務lock_rec_reset_and_release_wait(block, heap_no):

>>正在等待當前記錄鎖的(lock_get_wait(lock)),取消等待(lock_rec_cancel(lock))

>>已經獲得當前記錄鎖的,重置對應bit位(lock_rec_reset_nth_bit(lock, heap_no);)

?

lock_update_delete主要在INSERT回滾及Purge線程中被調用到。

?

在查找數據時,DELETE會給記錄加鎖,在進行標記刪除時,也會調用到鎖檢查函數:

聚集索引:

row_upd->row_upd_clust_step->row_upd_del_mark_clust_rec->btr_cur_del_mark_set_clust_rec->lock_clust_rec_modify_check_and_lock

這個backtrace,會從lock_clust_rec_modify_check_and_lock直接返回DB_SUCCESS,因為函數btr_cur_del_mark_set_clust_rec的參數flags總是

值為BTR_NO_LOCKING_FLAG

用戶線程不做調用,但在btr_cur_upd_lock_and_undo則會繼續走lock_clust_rec_modify_check_and_lock的流程。

?

二級索引:

row_upd->row_upd_sec_step->row_upd_sec_index_entry->btr_cur_del_mark_set_sec_rec->lock_sec_rec_modify_check_and_lock

用戶線程里lock_sec_rec_modify_check_and_lock的flags參數為0,而在row_undo_mod_del_unmark_sec_and_undo_update、row_undo_mod_del_mark_or_remove_sec_low函數里則設置為BTR_NO_LOCKING_FLAG,表示不做檢查。

?

lock_sec_rec_modify_check_and_lock用于檢查是否有其他事務阻止當前修改一條二級索引記錄(delete mark or delete unmark),

?

如果開始修改二級索引,則表示我們已經成功修改了聚集索引,因此不應該有其他事務在該記錄上的隱式鎖,也不應該有其他活躍事務修改了二級索引記錄。該函數會調用:

? ? err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP,

? ? ? ? ? ? ? ? block, heap_no, index, the);

第一個函數為TRUE,則當無需等待時,不會創建新的鎖對象。

?

如果err返回值為DB_SUCCESS或者DB_SUCCESS_LOCKED_REC,就更新當前二級索引Page上的最大事務ID。

如果當前存在和LOCK_X|LOCK_REC_NOT_GAP相沖突的鎖對象,則可能需要等待。

?

回到在之前博文提到的死鎖,信息如下:

*** (1) TRANSACTION:

TRANSACTION 1E7D49CDD, ACTIVE 69 sec fetching rows

mysql tables in use 1, locked 1

LOCK WAIT 4 lock struct(s), heap size 1248, 4 row lock(s), undo log entries 1

MySQL thread id 1385867, OS thread handle 0x7fcebd956700, query id 837909262 10.246.145.78 im updating

delete??? from??????? msg ? ?WHERE???? target_id = ‘Y25oaHVwYW7mmZbmmZblpKnkvb8=’????? and???????? gmt_modified <= ’2012-12-14 15:07:14′

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS?space id 203 page no 475912 n bits 88?index `PRIMARY` of table `im`.`msg` trx id 1E7D49CDD lock_mode X locks rec but not gap waiting

*** (2) TRANSACTION:

TRANSACTION 1E7CE0399, ACTIVE 1222 sec fetching rows, thread declared inside InnoDB 272

mysql tables in use 1, locked 1

1346429 lock struct(s), heap size 119896504, 11973543 row lock(s), undo log entries 1

MySQL thread id 1090268, OS thread handle 0x7fcebf48c700, query id 837483530 10.246.145.78 im updating

delete??? from??????? msg ? ?WHERE???? target_id = ‘Y25oaHVwYW7niLHkuZ3kuYU5OQ==’????? and???????? gmt_modified <= ’2012-12-14 14:13:28′

*** (2) HOLDS THE LOCK(S):

RECORD LOCKS space id 203 page no 475912 n bits 88 index `PRIMARY` of table `im`.`msg` trx id 1E7CE0399 lock_mode X

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 203 page no 1611099?n bits 88?index `PRIMARY` of table `im`.`msg` trx id 1E7CE0399 lock_mode X waiting

?

表結構為:

CREATE?TABLE?`msg`?(

??`id`?bigint(20)?NOT?NULL?AUTO_INCREMENT,

??`target_id`?varchar(100)?COLLATE?utf8_bin?NOT?NULL?,

? ? ? ?……

? ? ? ?……

??`flag`?tinyint(4)?NOT?NULL ,

??`gmt_create`?datetime?NOT?NULL,

??`gmt_modified`?datetime?NOT?NULL,

? `datablob`?blob,

??`nickname`?varchar(64)?COLLATE?utf8_bin?DEFAULT?NULL ,

??`source`?tinyint(4)?DEFAULT?NULL ,

??PRIMARY?KEY?(`id`),

??KEY?`idx_o_tid`?(`target_id`,`gmt_modified`,`source`,`flag`)

)?ENGINE=InnoDB?

首先我們從死鎖信息里來看,發生死鎖的是兩個delete語句,

delete??? from??????? offmsg_0007??? WHERE???? target_id = ‘Y25oaHVwYW7mmZbmmZblpKnkvb8=’????? and???????? gmt_modified <= ’2012-12-14 15:07:14′

delete??? from??????? offmsg_0007??? WHERE???? target_id = ‘Y25oaHVwYW7niLHkuZ3kuYU5OQ==’????? and???????? gmt_modified <= ’2012-12-14 14:13:28′

?

?

我們再看看這個表上的索引,一個主鍵索引(target_id),一個二級索引(`target_id`,`gmt_modified`,`source`,`flag`)

根據前綴索引的原則,理論上我們應該可以通過二級索引來查找數據,從上一節的分析,我們知道,如果根據二級索引查找數據:

>>二級索引上加X 鎖,記錄及GAP

>>聚集索引上加記錄X鎖

?

我們再看死鎖信息:

第一條SQL等待聚集索引Page 475912上的lock_mode X locks rec but not gap, 這說明該鎖請求等待是走二級索引的

第二條SQL持有聚集索引Page 475912上的lock_mode X鎖,等待聚集索引Page 1611099上的 lock_mode X

因此我們大致可以認為第二條SQL總是在請求聚集索引上的LOCK_ORDINARY類型的鎖,簡單的gdb我們可以知道走聚集索引做范圍刪除,鎖模式值為3,也就是LOCK_X

?

因此,可以推測delete操作走錯了索引,導致出現資源的互相占用。從而死鎖;至于為什么走錯索引,這就是優化器的問題了,暫不明;

?

C.釋放鎖

在事務提交或回滾時,會釋放記錄鎖,調用函數為lock_release_off_kernel

函數的邏輯很簡單,遍歷trx->trx_locks。

對于記錄鎖,調用lock_rec_dequeue_from_page(lock)

–>從lock_sys中刪除

–>檢查lock所在page上的等待的鎖對象是否能被grant(lock_grant),如果可以,則喚醒等待的事務。

對于表鎖,調用lock_table_dequeue(lock)

轉載自:?[MySQL學習] Innodb鎖系統(4) Insert/Delete 鎖處理及死鎖示例分析

創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎

總結

以上是生活随笔為你收集整理的Innodb锁系统 Insert/Delete 锁处理及死锁示例分析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。