鎖是實(shí)現(xiàn)事務(wù)隔離性最廣泛使用的技術(shù)。本文主要分享InnoDB中鎖的設(shè)計(jì)與實(shí)現(xiàn)费彼。
鎖的定義
下面列舉innodb支持的鎖婆咸。
行級(jí)鎖
- 共享鎖:S鎖骇两,允許事務(wù)讀一行數(shù)據(jù)
- 排他鎖:X鎖胞此,允許事務(wù)刪除或更新一行數(shù)據(jù)
X | S | |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
X鎖與任何的鎖都不兼容,而S鎖僅和S鎖兼容。
注意:行鎖實(shí)際上是索引記錄鎖,對(duì)索引記錄的鎖定舅列。即使表沒有建立索引肌割,InnoDB也會(huì)創(chuàng)建一個(gè)隱藏的聚簇索引,并使用此索引進(jìn)行記錄鎖定帐要。
意圖鎖
意圖鎖定是表級(jí)鎖定把敞,標(biāo)識(shí)事務(wù)稍后對(duì)表中的行做哪種類型的鎖定(共享或獨(dú)占)
意向共享鎖(IS):事務(wù)想要獲得一張表中某幾行的共享鎖
意向排他鎖(IX):事務(wù)想要獲得一張表中某幾行的排他鎖
意圖鎖遵循如下協(xié)議:
在事務(wù)獲取表中某行的共享鎖之前,它必須首先在表上獲取IS鎖或更強(qiáng)的鎖宠叼。
在事務(wù)獲取表中某行的獨(dú)占鎖之前先巴,它必須首先在表上獲取IX鎖其爵。
IS | IX | S | X | |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
X鎖與任何的鎖都不兼容冒冬,S鎖與IX鎖不兼容,其他情況都是兼容的摩渺。
注意:意向鎖只會(huì)阻塞表級(jí)別的鎖(如LOCK TABLES請(qǐng)求的表鎖)简烤,并不會(huì)阻塞行級(jí)鎖(如行級(jí)X鎖)。
間隙鎖(Gap Lock)
行鎖的以下三種算法
Record Lock:?jiǎn)蝹€(gè)行記錄上的鎖
Gap Lock:間隙鎖摇幻,鎖定一個(gè)范圍横侦,但不包含記錄本身
Next-Key Lock:鎖定記錄以及記錄前一個(gè)間隙
插入意向鎖(Insert Intention Lock)
插入意圖鎖是在行插入之前通過INSERT操作設(shè)置的一種特殊間隙鎖。
注意:多個(gè)事務(wù)插入同一個(gè)間隙的不同位置绰姻,他們并不會(huì)沖突枉侧。假設(shè)存在索引記錄,其值分別為4和7狂芋。單獨(dú)的事務(wù)分別嘗試插入值5和6榨馁,在獲得插入行的排他鎖之前,每個(gè)事務(wù)都使用插入意圖鎖來鎖定4和7之間的間隙帜矾, 但他們不會(huì)互相阻塞翼虫。
同樣,不同事務(wù)請(qǐng)求同一個(gè)間隙的Gap鎖并不會(huì)阻塞屡萤,但如果一個(gè)事務(wù)請(qǐng)求了Gap鎖珍剑,另一個(gè)事務(wù)再請(qǐng)求插入意向鎖,則會(huì)阻塞死陆。
Gap鎖和Next-Key鎖只存在RR隔離級(jí)別下招拙,RC隔離級(jí)別下并不使用這些鎖。
Gap鎖意義是什么措译?是為了解決幻讀問題别凤。
什么是幻讀問題?一個(gè)事務(wù)中同一個(gè)SQL多次執(zhí)行瞳遍,結(jié)果集不同闻妓,就是多了一些記錄。這違反了事務(wù)的隔離性掠械,即當(dāng)前事務(wù)能夠看到其他事務(wù)的結(jié)果由缆。
Gap鎖的目的就是解決這個(gè)問題注祖。它阻塞插入意向鎖,阻止不適當(dāng)?shù)挠涗洸迦刖Γ苊饣米x問題是晨。
文章下面說到的加Gap鎖或Next-Key鎖的場(chǎng)景,大家思考一下通過這些鎖是否可以解決幻讀問題舔箭,就知道為什么要加Gap鎖和Next-Key鎖了罩缴。
自增鎖
自增鎖是事務(wù)插入到具有AUTO_INCREMENT列的表時(shí)的一種特殊表級(jí)鎖。當(dāng)一個(gè)事務(wù)將值插入表時(shí)层扶,必須獲取自增鎖箫章,以便獲取自增列的值。
innodb_autoinc_lock_mode參數(shù)可以控制 auto-increment 鎖定的算法镜会。有興趣的同學(xué)可以深入了解檬寂。
表鎖
innodb還支持表鎖,LOCK TABLES … WRITE可以獲取指定表的X鎖戳表。LOCK TABLES … READ可以獲取指定表的S鎖桶至。
鎖的實(shí)現(xiàn)
行鎖在InnoDB中的數(shù)據(jù)結(jié)構(gòu)如下
typedef struct lock_rec_struct lock_rec_tstruct lock_rec_struct{ ulint space; /*space id*/ ulint page_no; /*page number*/ unint n_bits; /*number of bits in the lock bitmap*/}
InnoDB中根據(jù)頁的組織形式進(jìn)行鎖管理,并使用位圖記錄鎖信息匾旭。
n_bits變量表示位圖占用的字節(jié)數(shù)镣屹,它后面緊跟著一個(gè)bitmap,bitmap占用的字節(jié)為:1 + (nbits-1)/8价涝,bitmap中的每一位標(biāo)識(shí)對(duì)應(yīng)的行記錄是否加鎖女蜈。
因此,lock_rec_struct占用的實(shí)際存儲(chǔ)空間為:sizeof(lock_rec_struct) + 1 + (nbits-1)/8飒泻。
思考:如何鎖定一個(gè)間隙呢鞭光?
InnoDB通過在間隙的下一個(gè)記錄添加Gap鎖實(shí)現(xiàn)鎖定一個(gè)間隙
表級(jí)鎖的數(shù)據(jù)結(jié)構(gòu)(用于表的意向鎖和自增鎖)
typedef struct lock_table_struct lock_table_t;struct lock_table_struct { dict_table_t* table; /*database table in dictionary cache*/ UT_LIST_NODE_T(lock_t) locks; /*list of locks on the same table*/}
而事務(wù)中關(guān)聯(lián)如下鎖結(jié)構(gòu)
typedef struct lock_struct lock_t;struct lock_struct{ trx_t* trx; /* transaction owning the lock */ UT_LIST_NODE_T(lock_t) trx_locks; /* list of the locks of the transaction */ ulint type_mode; /* lock type, mode, gap flag, and wait flag, ORed */ hash_node_t hash; /* hash chain node for a record lock */ dict_index_t* index; /* index for a record lock */ union { lock_table_t tab_lock; /* table lock */ lock_rec_t rec_lock; /* record lock */ } un_member;};
index變量指向一個(gè)索引,行鎖本質(zhì)是索引記錄鎖泞遗。
lock_struct是根據(jù)一個(gè)事務(wù)的每個(gè)頁(或每個(gè)表)進(jìn)行定義的惰许。但一個(gè)事務(wù)可能在不同頁上有多個(gè)行鎖,trx_locks變量將一個(gè)事務(wù)所有的鎖信息進(jìn)行鏈接史辙,這樣就可以快速查詢一個(gè)事務(wù)所有鎖信息汹买。
UT_LIST_NODE_T定義如下,典型的鏈表結(jié)構(gòu)
#define UT_LIST_NODE_T(TYPE)struct { TYPE * prev; /* pointer to the previous node,NULL if start of list */ TYPE * next; /* pointer to next node, NULL if end of list */}
lock_struct中type_mode變量是一個(gè)無符號(hào)的32位整型聊倔,從低位排列晦毙,第1字節(jié)為lock_mode,定義如下耙蔑;
/* Basic lock modes */enum lock_mode { LOCK_IS = 0, /* intention shared */ LOCK_IX, /* intention exclusive */ LOCK_S, /* shared */ LOCK_X, /* exclusive */ LOCK_AUTO_INC, /* locks the auto-inc counter of a table in an exclusive mode */ LOCK_NONE, /* this is used elsewhere to note consistent read */ LOCK_NUM = LOCK_NONE, /* number of lock modes */ LOCK_NONE_UNSET = 255};
第2字節(jié)為lock_type见妒,目前只用前兩位,大小為 16 和 32 甸陌,表示 LOCK_TABLE 和 LOCK_REC须揣,
#define LOCK_TABLE 16 #define LOCK_REC 32
剩下的高位 bit 表示行鎖的類型record_lock_type
#define LOCK_WAIT 256 /* 表示正在等待鎖 */#define LOCK_ORDINARY 0 /* 表示 Next-Key Lock 盐股,鎖住記錄本身和記錄之前的 Gap*/#define LOCK_GAP 512 /* 表示鎖住記錄之前 Gap(不鎖記錄本身) */#define LOCK_REC_NOT_GAP 1024 /* 表示鎖住記錄本身,不鎖記錄前面的 gap */#define LOCK_INSERT_INTENTION 2048 /* 插入意向鎖 */#define LOCK_CONV_BY_OTHER 4096 /* 表示鎖是由其它事務(wù)創(chuàng)建的(比如隱式鎖轉(zhuǎn)換) */
另外耻卡,除了查詢某個(gè)事務(wù)所有鎖信息疯汁,系統(tǒng)還需要查詢某個(gè)具體記錄的鎖信息。如記錄id=3是否有鎖卵酪?
而InnoDB使用哈希表映射行數(shù)據(jù)和鎖信息
struct lock_sys_struct{ hash_table_t* rec_hash;}
每次新建一個(gè)鎖對(duì)象幌蚊,都要插入到lock_sys->rec_hash中。lock_sys_struct中的key通過頁的space和page_no計(jì)算得到溃卡,而value則是鎖對(duì)象lock_rec_struct溢豆。
因此若需查詢某一行記錄是否有鎖,首先根據(jù)行所在頁進(jìn)行哈希查詢塑煎,然后根據(jù)查詢得到的lock_rec_struct沫换,查找lock bitmap,最終得到該行記錄是否有鎖最铁。
可以看出,根據(jù)頁進(jìn)行對(duì)行鎖的查詢并不是高效設(shè)計(jì)垮兑,但這種方式的資源開銷非常小冷尉。某一事務(wù)對(duì)一個(gè)頁任意行加鎖開銷都是一樣的(不管鎖住多少行)。因此也不需要支持鎖升級(jí)的功能系枪。
如果根據(jù)每一行記錄進(jìn)行鎖信息管理雀哨,所需的開銷會(huì)非常巨大。當(dāng)一個(gè)事務(wù)占用太多的鎖資源時(shí)私爷,需要進(jìn)行鎖升級(jí)雾棺,將行鎖升級(jí)為更粗粒度的鎖,如頁鎖或表鎖衬浑。
而現(xiàn)在InnoDB設(shè)計(jì)的方案并不需要鎖升級(jí)捌浩。
加鎖操作
下面列舉幾種常見場(chǎng)景下的加鎖邏輯
插入
首先對(duì)表加上IX鎖
唯一索引沖突檢查:如果唯一索引上存在相同項(xiàng),進(jìn)行S鎖當(dāng)前讀工秩,讀到數(shù)據(jù)則唯一索引沖突尸饺,返回異常,否則檢查通過助币。
判斷插入位置是否存在Gap鎖或Next-Key鎖浪听,沒有的話直接插入,有的話等待鎖釋放眉菱,并產(chǎn)生插入意向鎖迹栓。
對(duì)插入記錄的所有索引項(xiàng)加X鎖
為了降低鎖的開銷,innodb采用了延遲加鎖機(jī)制俭缓,即隱式鎖(implicit lock)克伊。
當(dāng)有事務(wù)對(duì)某條記錄進(jìn)行修改時(shí)叉抡,需要先判斷該行記錄是否有隱式鎖(原記錄的事務(wù)id是否是活動(dòng)的事務(wù)),如果有則為其真正創(chuàng)建鎖并等待(隱式鎖轉(zhuǎn)換為顯示鎖)答毫,否則直接更新數(shù)據(jù)并寫入自己的事務(wù)id(可以理解為加了隱式鎖)褥民。
二級(jí)索引雖然存儲(chǔ)上沒有記錄事務(wù)id,但同樣可以存在隱式鎖洗搂,只不過判斷邏輯復(fù)雜一些消返。有興趣的同學(xué)可以深入了解。
插入操作第3步添加的插入意向鎖和第4步添加的X鎖都是先添加隱式鎖(就是沒有加鎖)耘拇,當(dāng)發(fā)生鎖沖突時(shí)撵颊,再轉(zhuǎn)化為顯示鎖。
一致性鎖定讀惫叛,修改
一致性非鎖定讀:如果讀取的行正在執(zhí)行DELETE或UPDATE操作倡勇,讀取操作不等待行上鎖的釋放,而去讀行的一個(gè)快照數(shù)據(jù)嘉涌。在之前事務(wù)篇已經(jīng)分享過相關(guān)內(nèi)容妻熊。
這里看一下一致性鎖定讀(就是當(dāng)前讀)和修改操作的加鎖邏輯
(1) 查詢命中結(jié)果
SELECT … FROM … LOCK IN SHARE MODE(S鎖),SELECT … FROM … FOR UPDATE(X鎖)仑最,UPDATE … WHERE … (X鎖)語句在掃描命中的索引記錄上加上next key鎖扔役。如果是唯一索引,只需要在相應(yīng)記錄上加index record lock警医。
在輔助索引記錄上加鎖的語句亿胸,首先對(duì)輔助索引記錄加next key鎖,然后還要對(duì)聚集索引記錄進(jìn)行加鎖record lock
在輔助索引記錄上加鎖的語句预皇,可能還需要對(duì)下一個(gè)記錄進(jìn)行加Gap鎖侈玄,解決幻讀問題。
(2) 查詢未命中結(jié)果
如果sql查詢沒有命中結(jié)果吟温,則對(duì)命中的間隙加Gap鎖序仙。
(3) 查詢未使用索引
如果sql沒有使用索引,只能走聚簇索引溯街,對(duì)表中的記錄進(jìn)行全表掃描诱桂。
在RC隔離級(jí)別下會(huì)給所有記錄加Record鎖,在RR隔離級(jí)別下呈昔,對(duì)所有記錄加Next-Key鎖挥等。
刪除
刪除操作需要和更新操作一樣加鎖,并且當(dāng)purge真正刪除記錄操作完成后堤尾,如果刪除記錄上有Gap鎖肝劲,則由下一個(gè)記錄繼承該鎖,同時(shí)釋放并重置刪除記錄上等待鎖的信息。
死鎖
死鎖是指兩個(gè)或兩個(gè)以上的事務(wù)在執(zhí)行過程中辞槐,因爭(zhēng)奪鎖資源而造成的一種相互等待的現(xiàn)象掷漱。若無外力作用,他們都將無法推進(jìn)下去榄檬。
解決死鎖常用的兩個(gè)方案:
超時(shí)機(jī)制卜范,即兩個(gè)事務(wù)互相等待時(shí),當(dāng)一個(gè)等待時(shí)間超過設(shè)置的某一閥值時(shí)鹿榜,其中一個(gè)事務(wù)回滾海雪,另一個(gè)事務(wù)繼續(xù)執(zhí)行。
MySQL4.0版本開始舱殿,提供innodb_lock_wait_time用于設(shè)置等待超時(shí)時(shí)間奥裸。等待圖(wait-for graph)
InnoDB通過鎖的信息鏈表和事務(wù)等待鏈表,判斷是否存在等待回路沪袭。如有湾宙,則存在死鎖。
每次加鎖操作需要等待時(shí)都判斷是否產(chǎn)生死鎖冈绊,若有則回滾事務(wù)侠鳄。
實(shí)例分析
MySQL 8提供了performance_schema.data_locks可以很清晰地看到鎖信息。
下面的data_locks信息都是通過如下sql查詢
select ENGINE_TRANSACTION_ID,OBJECT_NAME,INDEX_NAME,LOCK_TYPE,LOCK_MODE,LOCK_STATUS,LOCK_DATA from performance_schema.data_locks;
創(chuàng)建測(cè)試表和測(cè)試數(shù)據(jù)
CREATE TABLE `lock_test` ( `a` int(10) unsigned NOT NULL, `b` int(1) unsigned NOT NULL , `c` int(1) unsigned NOT NULL , PRIMARY KEY (`a`), KEY `key2` (b) USING BTREE) ENGINE=InnoDB DEFAULT CHARSET=utf8;insert into lock_test values(10, 10, 10);insert into lock_test values(20, 20, 20);insert into lock_test values(30, 30, 30);insert into lock_test values(40, 40, 40);insert into lock_test values(50, 50, 50);
測(cè)試場(chǎng)景焚碌,通過非唯一索引更新數(shù)據(jù)
begin;update lock_test set b = 0 where b = 30;data_locks信息如下:+-----------------------+---------------+-------------+------------+-----------+---------------+-------------+-----------+| ENGINE_TRANSACTION_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |+-----------------------+---------------+-------------+------------+-----------+---------------+-------------+-----------+| 5242 | ttt | lock_test | NULL | TABLE | IX | GRANTED | NULL || 5242 | ttt | lock_test | key2 | RECORD | X | GRANTED | 30, 30 || 5242 | ttt | lock_test | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 30 || 5242 | ttt | lock_test | key2 | RECORD | X,GAP | GRANTED | 40, 40 |+-----------------------+---------------+-------------+------------+-----------+---------------+-------------+-----------+
事務(wù)在key2索引的b=30記錄添加了Next-Key鎖畦攘。(LOCK_MODE為X,代表Next-Key鎖)
PRIMARY索引的a=30記錄也加了Record鎖十电。
事務(wù)還在key2索引的(30,40)區(qū)間加了Gap鎖,所以在(30,40)之間插入數(shù)據(jù)會(huì)被阻塞叹螟。
這是如果刪除a=40記錄鹃骂,data_locks信息如下
+-----------------------+---------------+-------------+------------+-----------+---------------+-------------+-----------+| ENGINE_TRANSACTION_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |+-----------------------+---------------+-------------+------------+-----------+---------------+-------------+-----------+| 5249 | ttt | lock_test | NULL | TABLE | IX | GRANTED | NULL || 5249 | ttt | lock_test | key2 | RECORD | X | GRANTED | 40, 40 || 5249 | ttt | lock_test | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 40 || 5249 | ttt | lock_test | key2 | RECORD | X,GAP | GRANTED | 50, 50 || 5242 | ttt | lock_test | NULL | TABLE | IX | GRANTED | NULL || 5242 | ttt | lock_test | key2 | RECORD | X | GRANTED | 30, 30 || 5242 | ttt | lock_test | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 30 || 5242 | ttt | lock_test | key2 | RECORD | X,GAP | GRANTED | 40, 40 |+-----------------------+---------------+-------------+------------+-----------+---------------+-------------+-----------+
可以到a=40記錄被刪除后,添加了(30,50)的Gap鎖罢绽,替換原來的(30,40)的Gap鎖
再看一下查詢未命中結(jié)果的場(chǎng)景
update lock_test set a = 0 where a = 15data_locks信息如下:+-----------------------+-------------+------------+-----------+-----------+-------------+-----------+| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |+-----------------------+-------------+------------+-----------+-----------+-------------+-----------+| 5644 | lock_test | NULL | TABLE | IX | GRANTED | NULL || 5644 | lock_test | PRIMARY | RECORD | X,GAP | GRANTED | 20 |+-----------------------+-------------+------------+-----------+-----------+-------------+-----------+
可以看到在命中的(10,20)區(qū)間上加了Gap鎖畏线。
注意:update操作也會(huì)產(chǎn)生INSERT_INTENTION鎖
+-------------------------------------------------+-------------------------------------------------+| T1 | T2 |+-------------------------------------------------+-------------------------------------------------+| begin; | || select * from lock_test where b= 20 for update; | |+-------------------------------------------------+-------------------------------------------------+| | begin; || | update lock_test set a = 60 where b = 30;(阻塞) |+-------------------------------------------------+-------------------------------------------------+data_locks信息如下:+-----------------------+-------------+------------+-----------+------------------------+-------------+-----------+| ENGINE_TRANSACTION_ID | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |+-----------------------+-------------+------------+-----------+------------------------+-------------+-----------+| 5684 | lock_test | NULL | TABLE | IX | GRANTED | NULL || 5684 | lock_test | key2 | RECORD | X | GRANTED | 30, 30 || 5684 | lock_test | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 30 || 5684 | lock_test | key2 | RECORD | X,GAP | GRANTED | 40, 40 || 5684 | lock_test | key2 | RECORD | X,GAP,INSERT_INTENTION | WAITING | 30, 30 || 5673 | lock_test | NULL | TABLE | IX | GRANTED | NULL || 5673 | lock_test | key2 | RECORD | X | GRANTED | 20, 20 || 5673 | lock_test | PRIMARY | RECORD | X,REC_NOT_GAP | GRANTED | 20 || 5673 | lock_test | key2 | RECORD | X,GAP | GRANTED | 30, 30 |+-----------------------+-------------+------------+-----------+------------------------+-------------+-----------+事務(wù)2的update操作產(chǎn)生了X,GAP,INSERT_INTENTION鎖,并且被阻塞良价。
下面看一些死鎖場(chǎng)景寝殴。
(1) Duplicate key導(dǎo)致死鎖
+-----+------------------------+-----------------------------------------+-----------------------+| | T1 | T2 | T3 |+-----+------------------------+-----------------------------------------+-----------------------+| (1) | begin; | | || | insert into lock_test | | || | values(25, 25, 25); | | |+-----+------------------------+-----------------------------------------+-----------------------+| (2) | | begin; | || | | insert into lock_test | || | | values(25, 25, 25); | |+-----+------------------------+-----------------------------------------+-----------------------+| | | | begin; || | | | insert into lock_test || | | | values(25, 25, 25); |+-----+------------------------+-----------------------------------------+-----------------------+| (3) | rollback; | Deadlock found when trying to get lock; | Query OK |+-----+------------------------+-----------------------------------------+-----------------------+
(1) 事務(wù)T1插入a=25記錄
(2) 事務(wù)T2、T3也開始插入a=25記錄明垢,由于發(fā)生唯一鍵沖突蚣常,T2,T3需要執(zhí)行S鎖當(dāng)前讀(LOCK_S | LOCK_REC_NOT_GAP)
這時(shí)T1隱式鎖轉(zhuǎn)化為顯示鎖 (LOCK_X | LOCK_REC_NOT_GAP),導(dǎo)致T2,T3阻塞
(3) T1回退
這時(shí)痊银,T2和T3都要請(qǐng)求索引id=25上的排他記錄鎖(LOCK_X | LOCK_REC_NOT_GAP)抵蚊。
由于X鎖與S鎖互斥,T2和T3都等待對(duì)方釋放S鎖。
于是贞绳,死鎖便產(chǎn)生了谷醉。
(2) GAP與Insert Intention沖突導(dǎo)致死鎖
+-----+--------------------------------------------------+--------------------------------------------------+| | T1 | T2 |+-----+--------------------------------------------------+--------------------------------------------------+| (1) | begin; | || | select * from lock_test where b = 20 for update; | |+-----+--------------------------------------------------+--------------------------------------------------+| | | begin; || | | select * from lock_test where b = 30 for update; |+-----+--------------------------------------------------+--------------------------------------------------+| (2) | insert into lock_test values(25, 25, 25); | |+-----+--------------------------------------------------+--------------------------------------------------+| | | insert into lock_test values(26, 26, 26); |+-----+--------------------------------------------------+--------------------------------------------------+| | | Deadlock found when trying to get lock; |+-----+--------------------------------------------------+--------------------------------------------------+
(1)
T1事務(wù)GAP鎖鎖住區(qū)間(20,30)
T2事務(wù)Next-Key鎖鎖住區(qū)間(20,30]
(2)
T1事務(wù)插入操作,需要在間隙(20,30)添加插入意向鎖冈闭,這時(shí)等待T2事務(wù)Next-Key鎖釋放
T2事務(wù)插入操作俱尼,需要在間隙(20,30)添加插入意向鎖,這時(shí)等待T1事務(wù)GAP鎖釋放
這時(shí)死鎖產(chǎn)生了萎攒。
另外遇八,show engine innodb status可以獲取最近一次的死鎖日志。
MySQL8之前躺酒,可以通過INFORMATION_SCHEMA下INNODB_TRX,INNODB_LOCKS,INNODB_LOCK_WAITS查看事務(wù)和鎖信息押蚤。
INNODB_TRX在MySQL8依然保留。