MySQL是目前世界上最流行的數(shù)據(jù)庫(kù),InnoDB是MySQL最流行的存儲(chǔ)引擎,它在大數(shù)據(jù)量高并發(fā)量的業(yè)務(wù)場(chǎng)景下,有著非常良好的性能表現(xiàn),之所以如此,是和InnoDB的鎖機(jī)制相關(guān)。(2)共享/排它鎖(Shared and Exclusive Locks);
(3)意向鎖(Intention Locks);(4)插入意向鎖(Insert Intention Locks);(5)記錄鎖(Record Locks);
(6)間隙鎖(Gap Locks);
(7)臨鍵鎖(Next-key Locks);
今天和大家來逐一介紹。文章較長(zhǎng),案例較多,請(qǐng)大家提前收藏,點(diǎn)贊,轉(zhuǎn)發(fā),再看。
MySQL,InnoDB,默認(rèn)的隔離級(jí)別(RR),假設(shè)有數(shù)據(jù)表:t(id AUTO_INCREMENT, name);
數(shù)據(jù)表中有數(shù)據(jù):1, shenjian
2, zhangsan
3, lisi
insert into t(name) values(xxx);
insert into t(name) values(ooo);
問:事務(wù)B會(huì)不會(huì)被阻塞?InnoDB在RR隔離級(jí)別下,嘗試解決幻讀問題,上面這個(gè)案例中:(1)事務(wù)A先執(zhí)行insert,會(huì)得到一條(4, xxx)的記錄,由于是自增列,故不用顯示指定id為4,InnoDB會(huì)自動(dòng)增長(zhǎng),注意此時(shí)事務(wù)并未提交;
(2)事務(wù)B后執(zhí)行insert,假設(shè)不會(huì)被阻塞,那會(huì)得到一條(5, ooo)的記錄;insert into t(name) values(xxoo);
select * from t where id>3;
4, xxx
6, xxoo
畫外音:不可能查詢到5的記錄,在RR的隔離級(jí)別下,不可能讀取到還未提交事務(wù)生成的數(shù)據(jù)。這對(duì)于事務(wù)A來說,就很奇怪了,AUTO_INCREMENT的列,連續(xù)插入了兩條記錄,一條是4,接下來一條變成了6,就像莫名其妙的幻影。自增鎖是一種特殊的表級(jí)別鎖(table-level lock),專門針對(duì)事務(wù)插入AUTO_INCREMENT類型的列。最簡(jiǎn)單的情況,如果一個(gè)事務(wù)正在往表中插入記錄,所有其他事務(wù)的插入必須等待,以便第一個(gè)事務(wù)插入的行,是連續(xù)的主鍵值。An AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns. In the simplest case, if one transaction is inserting values into the table, any other transactions must wait to do their own inserts into that table, so that rows inserted by the first transaction receive consecutive primary key values.
與此同時(shí),InnoDB提供了innodb_autoinc_lock_mode配置,可以調(diào)節(jié)與改變?cè)撴i的模式與行為。上面的案例,假設(shè)不是自增列,又會(huì)是什么樣的情形呢?t(id unique PK, name);
數(shù)據(jù)表中有數(shù)據(jù):10, shenjian
20, zhangsan
30, lisi
事務(wù)A先執(zhí)行,在10與20兩條記錄中插入了一行,還未提交:insert into t values(11, xxx);
事務(wù)B后執(zhí)行,也在10與20兩條記錄中插入了一行:insert into t values(12, ooo);
(2)事務(wù)B會(huì)不會(huì)被阻塞呢?第二種,共享/排它鎖(Shared and Exclusive Locks)
《InnoDB并發(fā)如此高,原因竟然在這?》一文介紹了通用的共享/排它鎖,在InnoDB里當(dāng)然也實(shí)現(xiàn)了標(biāo)準(zhǔn)的行級(jí)鎖(row-level locking),共享/排它鎖:(1)事務(wù)拿到某一行記錄的共享S鎖,才可以讀取這一行;(2)事務(wù)拿到某一行記錄的排它X鎖,才可以修改或者刪除這一行;

(1)多個(gè)事務(wù)可以拿到一把S鎖,讀讀可以并行;(2)而只有一個(gè)事務(wù)可以拿到X鎖,寫寫/讀寫必須互斥;InnoDB支持多粒度鎖(multiple granularity locking),它允許行級(jí)鎖與表級(jí)鎖共存,實(shí)際應(yīng)用中,InnoDB使用的是意向鎖。意向鎖是指,未來的某個(gè)時(shí)刻,事務(wù)可能要加共享/排它鎖了,先提前聲明一個(gè)意向。(1)首先,意向鎖,是一個(gè)表級(jí)別的鎖(table-level locking);- 意向共享鎖(intention shared lock, IS),它預(yù)示著,事務(wù)有意向?qū)Ρ碇械?span style="color: rgb(255, 76, 0);">某些行加共享S鎖
- 意向排它鎖(intention exclusive lock, IX),它預(yù)示著,事務(wù)有意向?qū)Ρ碇械?span style="color: rgb(255, 76, 0);">某些行加排它X鎖
select ... lock in share mode,要設(shè)置IS鎖;select ... for update,要設(shè)置IX鎖;(3)意向鎖協(xié)議(intention locking protocol)并不復(fù)雜:- 事務(wù)要獲得某些行的S鎖,必須先獲得表的IS鎖
- 事務(wù)要獲得某些行的X鎖,必須先獲得表的IX鎖
(4)由于意向鎖僅僅表明意向,它其實(shí)是比較弱的鎖,意向鎖之間并不相互互斥,而是可以并行,其兼容互斥表如下:
(5)額,既然意向鎖之間都相互兼容,那其意義在哪里呢?它會(huì)與共享鎖/排它鎖互斥,其兼容互斥表如下:
畫外音:排它鎖是很強(qiáng)的鎖,不與其他類型的鎖兼容。這也很好理解,修改和刪除某一行的時(shí)候,必須獲得強(qiáng)鎖,禁止這一行上的其他并發(fā),以保障數(shù)據(jù)的一致性。
第四種,插入意向鎖(Insert Intention Locks)對(duì)已有數(shù)據(jù)行的修改與刪除,必須加強(qiáng)互斥鎖X鎖,那對(duì)于數(shù)據(jù)的插入,是否還需要加這么強(qiáng)的鎖,來實(shí)施互斥呢?插入意向鎖,孕育而生。插入意向鎖,是間隙鎖(Gap Locks)的一種(所以,也是實(shí)施在索引上的),它是專門針對(duì)insert操作的。畫外音:有點(diǎn)尷尬,間隙鎖下文才會(huì)介紹,暫且理解為,它是一種實(shí)施在索引上,鎖定索引某個(gè)區(qū)間范圍的鎖。多個(gè)事務(wù),在同一個(gè)索引,同一個(gè)范圍區(qū)間插入記錄時(shí),如果插入的位置不沖突,不會(huì)阻塞彼此。Insert Intention Lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap.
t(id unique PK, name);
數(shù)據(jù)表中有數(shù)據(jù):10, shenjian
20, zhangsan
30, lisi
事務(wù)A先執(zhí)行,在10與20兩條記錄中插入了一行,還未提交:insert into t values(11, xxx);
事務(wù)B后執(zhí)行,也在10與20兩條記錄中插入了一行:insert into t values(12, ooo);
(2)事務(wù)B會(huì)不會(huì)被阻塞呢?回答:雖然事務(wù)隔離級(jí)別是RR,雖然是同一個(gè)索引,雖然是同一個(gè)區(qū)間,但插入的記錄并不沖突,故這里:(1)InnoDB使用共享鎖,可以提高讀讀并發(fā);(2)為了保證數(shù)據(jù)強(qiáng)一致,InnoDB使用強(qiáng)互斥鎖,保證同一行記錄修改與刪除的串行性;(3)InnoDB使用插入意向鎖,可以提高插入并發(fā);假設(shè)不是插入并發(fā),而是讀寫并發(fā),又會(huì)是什么樣的結(jié)果呢?MySQL,InnoDB,默認(rèn)的隔離級(jí)別(RR)。t(id unique PK, name);
數(shù)據(jù)表中有數(shù)據(jù):10, shenjian
20, zhangsan
30, lisi
事務(wù)A先執(zhí)行,查詢了一些記錄,還未提交:select * from t where id>10;
事務(wù)B后執(zhí)行,在10與20兩條記錄中插入了一行:insert into t values(11, xxx);
(2)事務(wù)B會(huì)不會(huì)被阻塞呢?InnoDB的細(xì)粒度鎖,是實(shí)現(xiàn)在索引記錄上的,如果查詢沒有命中索引,也將退化為表鎖。InnoDB的索引有兩類索引,聚集索引(Clustered Index)與普通索引(Secondary Index)。InnoDB的每一個(gè)表都會(huì)有聚集索引:(2)如果表沒有定義PK,則第一個(gè)非空unique列是聚集索引;(3)否則,InnoDB會(huì)創(chuàng)建一個(gè)隱藏的row-id作為聚集索引;索引的結(jié)構(gòu)是B+樹,這里不展開B+樹的細(xì)節(jié),說幾個(gè)結(jié)論:(1)在索引結(jié)構(gòu)中,非葉子節(jié)點(diǎn)存儲(chǔ)key,葉子節(jié)點(diǎn)存儲(chǔ)value;(2)聚集索引,葉子節(jié)點(diǎn)存儲(chǔ)行記錄(row);畫外音:所以,InnoDB索引和記錄是存儲(chǔ)在一起的,而MyISAM的索引和記錄是分開存儲(chǔ)的。
(3)普通索引,葉子節(jié)點(diǎn)存儲(chǔ)了PK的值;所以,InnoDB的普通索引,如果未滿足索引覆蓋,實(shí)際上會(huì)掃描兩遍:索引結(jié)構(gòu),InnoDB/MyISAM的索引結(jié)構(gòu),如果大家感興趣,未來撰文詳述。舉個(gè)例子,假設(shè)有InnoDB表:t(id PK, name KEY, sex, flag);
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B
(1)第一幅圖,id PK的聚集索引,葉子存儲(chǔ)了所有的行記錄;(2)第二幅圖,name上的普通索引,葉子存儲(chǔ)了PK的值;select * from t where name=’shenjian’;
(1)會(huì)先在name普通索引上查詢到PK=1;(2)再在聚集索引上查詢到(1,shenjian, m, A)的行記錄;有了上面的鋪墊,下文繼續(xù)介紹InnoDB剩下三種鎖:為了方便講述,如無特殊說明,后文中,默認(rèn)的事務(wù)隔離級(jí)別為可重復(fù)讀(Repeated Read, RR)。select * from t where id=1 for update;
它會(huì)在id=1的索引記錄上加鎖,以阻止其他事務(wù)插入,更新,刪除id=1的這一行。select * from t where id=1;
間隙鎖,它封鎖索引記錄中的間隔,或者第一條索引記錄之前的范圍,又或者最后一條索引記錄之后的范圍。t(id PK, name KEY, sex, flag);
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B
select * from t
where id between 8 and 15
for update;
會(huì)封鎖區(qū)間,以阻止其他事務(wù)id=10的記錄插入。如果能夠插入成功,頭一個(gè)事務(wù)執(zhí)行相同的SQL語(yǔ)句,會(huì)發(fā)現(xiàn)結(jié)果集多出了一條記錄,即幻影數(shù)據(jù)。間隙鎖的主要目的,就是為了防止其他事務(wù)在間隔中插入數(shù)據(jù),以導(dǎo)致“不可重復(fù)讀”。如果把事務(wù)的隔離級(jí)別降級(jí)為讀提交(Read Committed, RC),間隙鎖則會(huì)自動(dòng)失效。臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖范圍,既包含索引記錄,又包含索引區(qū)間。更具體的,臨鍵鎖會(huì)封鎖索引記錄本身,以及索引記錄之前的區(qū)間。如果一個(gè)會(huì)話占有了索引記錄R的共享/排他鎖,其他會(huì)話不能立刻在R之前的區(qū)間插入新的索引記錄。If one session has a shared or exclusive lock on record R in an index, another session cannot insert a new index record in the gap immediately before R in the index order.
t(id PK, name KEY, sex, flag);
1, shenjian, m, A
3, zhangsan, m, A
5, lisi, m, A
9, wangwu, f, B
(-infinity, 1]
(1, 3]
(3, 5]
(5, 9]
(9, +infinity)
臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務(wù)的隔離級(jí)別降級(jí)為RC,臨鍵鎖則也會(huì)失效。畫外音:關(guān)于事務(wù)的隔離級(jí)別,以及幻讀,之前的文章一直沒有展開說明,如果大家感興趣,后文詳述。【總結(jié)】
(1)自增鎖(Auto-inc Locks):表級(jí)鎖,專門針對(duì)事務(wù)插入AUTO_INC的列,如果插入位置沖突,多個(gè)事務(wù)會(huì)阻塞,以保證數(shù)據(jù)一致性;(2)共享/排它鎖(Shared and Exclusive Locks):行級(jí)鎖,S鎖與X鎖,強(qiáng)鎖;(3)意向鎖(Intention Locks):表級(jí)鎖,IS鎖與IX鎖,弱鎖,僅僅表明意向;(4)插入意向鎖(Insert Intention Locks):針對(duì)insert的,如果插入位置不沖突,多個(gè)事務(wù)不會(huì)阻塞,以提高插入并發(fā);(5)記錄鎖(Record Locks):索引記錄上加鎖,對(duì)索引記錄實(shí)施互斥,以保證數(shù)據(jù)一致性;(6)間隙鎖(Gap Locks):封鎖索引記錄中間的間隔,在RR下有效,防止間隔中被其他事務(wù)插入;(7)臨鍵鎖(Next-key Locks):封鎖索引記錄,以及索引記錄中間的間隔,在RR下有效,防止幻讀;InnoDB的鎖,與索引類型,事務(wù)的隔離級(jí)別相關(guān),更多更復(fù)雜更有趣的案例,后續(xù)和大家介紹。關(guān)注,不迷路
寫一篇好文,可能要15天,謝轉(zhuǎn),謝贊,謝在看。閱讀原文,學(xué)習(xí)更多架構(gòu)知識(shí)。