深入理解InnoDB -- 鎖篇

鎖是實(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)景下的加鎖邏輯

插入

  1. 首先對(duì)表加上IX鎖

  2. 唯一索引沖突檢查:如果唯一索引上存在相同項(xiàng),進(jìn)行S鎖當(dāng)前讀工秩,讀到數(shù)據(jù)則唯一索引沖突尸饺,返回異常,否則檢查通過助币。

  3. 判斷插入位置是否存在Gap鎖或Next-Key鎖浪听,沒有的話直接插入,有的話等待鎖釋放眉菱,并產(chǎn)生插入意向鎖迹栓。

  4. 對(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è)方案:

  1. 超時(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í)間奥裸。

  2. 等待圖(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依然保留。

轉(zhuǎn)載自:

深入理解InnoDB -- 鎖篇

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末羹应,一起剝皮案震驚了整個(gè)濱河市揽碘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌园匹,老刑警劉巖雳刺,帶你破解...
    沈念sama閱讀 222,000評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異裸违,居然都是意外死亡掖桦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門供汛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來枪汪,“玉大人,你說我怎么就攤上這事怔昨∪妇茫” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵趁舀,是天一觀的道長(zhǎng)赖捌。 經(jīng)常有香客問我,道長(zhǎng)矮烹,這世上最難降的妖魔是什么越庇? 我笑而不...
    開封第一講書人閱讀 59,782評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮奉狈,結(jié)果婚禮上卤唉,老公的妹妹穿的比我還像新娘。我一直安慰自己嘹吨,他們只是感情好搬味,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,798評(píng)論 6 397
  • 文/花漫 我一把揭開白布境氢。 她就那樣靜靜地躺著,像睡著了一般碰纬。 火紅的嫁衣襯著肌膚如雪萍聊。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評(píng)論 1 310
  • 那天悦析,我揣著相機(jī)與錄音寿桨,去河邊找鬼。 笑死强戴,一個(gè)胖子當(dāng)著我的面吹牛亭螟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播骑歹,決...
    沈念sama閱讀 40,952評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼预烙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了道媚?” 一聲冷哼從身側(cè)響起扁掸,我...
    開封第一講書人閱讀 39,852評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎最域,沒想到半個(gè)月后谴分,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡镀脂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,483評(píng)論 3 341
  • 正文 我和宋清朗相戀三年牺蹄,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片薄翅。...
    茶點(diǎn)故事閱讀 40,615評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡沙兰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出翘魄,到底是詐尸還是另有隱情僧凰,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評(píng)論 5 350
  • 正文 年R本政府宣布熟丸,位于F島的核電站,受9級(jí)特大地震影響伪节,放射性物質(zhì)發(fā)生泄漏光羞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,979評(píng)論 3 334
  • 文/蒙蒙 一怀大、第九天 我趴在偏房一處隱蔽的房頂上張望纱兑。 院中可真熱鬧,春花似錦化借、人聲如沸潜慎。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽铐炫。三九已至垒手,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間倒信,已是汗流浹背科贬。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評(píng)論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鳖悠,地道東北人榜掌。 一個(gè)月前我還...
    沈念sama閱讀 49,041評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像乘综,于是被迫代替她去往敵國和親憎账。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,630評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容