InnoDB,select為啥會(huì)阻塞insert?
MySQL的InnoDB的細(xì)粒度行鎖,是它最吸引人的特性之一。
但是,如《InnoDB,5項(xiàng)***實(shí)踐》所述,如果查詢(xún)沒(méi)有***索引,也將退化為表鎖。
InnoDB的細(xì)粒度鎖,是實(shí)現(xiàn)在索引記錄上的。
一、InnoDB的索引
InnoDB的索引有兩類(lèi)索引,聚集索引(Clustered Index)與普通索引(Secondary Index)。
InnoDB的每一個(gè)表都會(huì)有聚集索引:
- 如果表定義了PK,則PK就是聚集索引;
- 如果表沒(méi)有定義PK,則***個(gè)非空unique列是聚集索引;
- 否則,InnoDB會(huì)創(chuàng)建一個(gè)隱藏的row-id作為聚集索引;
為了方便說(shuō)明,后文都將以PK說(shuō)明。
索引的結(jié)構(gòu)是B+樹(shù),這里不展開(kāi)B+樹(shù)的細(xì)節(jié),說(shuō)幾個(gè)結(jié)論:
(1)在索引結(jié)構(gòu)中,非葉子節(jié)點(diǎn)存儲(chǔ)key,葉子節(jié)點(diǎn)存儲(chǔ)value;
(2)聚集索引,葉子節(jié)點(diǎn)存儲(chǔ)行記錄(row);
畫(huà)外音:所以,InnoDB索引和記錄是存儲(chǔ)在一起的,而MyISAM的索引和記錄是分開(kāi)存儲(chǔ)的。
(3)普通索引,葉子節(jié)點(diǎn)存儲(chǔ)了PK的值;
畫(huà)外音:所以,InnoDB的普通索引,實(shí)際上會(huì)掃描兩遍:
- ***遍,由普通索引找到PK;
- 第二遍,由PK找到行記錄;
索引結(jié)構(gòu),InnoDB/MyISAM的索引結(jié)構(gòu),如果大家感興趣,未來(lái)撰文詳述。
舉個(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
可以看到:
- ***幅圖,id PK的聚集索引,葉子存儲(chǔ)了所有的行記錄;
- 第二幅圖,name上的普通索引,葉子存儲(chǔ)了PK的值;
對(duì)于:
- select * from t where name=’shenjian
- 會(huì)先在name普通索引上查詢(xún)到PK=1;
- 再在聚集索引衫查詢(xún)到(1,shenjian, m, A)的行記錄;
下文簡(jiǎn)單介紹InnoDB七種鎖中的剩下三種:
- 記錄鎖(Record Locks)
- 間隙鎖(Gap Locks)
- 臨鍵鎖(Next-Key Locks)
為了方便講述,如無(wú)特殊說(shuō)明,后文中,默認(rèn)的事務(wù)隔離級(jí)別為可重復(fù)讀(Repeated Read, RR)。
二、記錄鎖(Record Locks)
記錄鎖,它封鎖索引記錄,例如:
- select * from t where id=1 for update
它會(huì)在id=1的索引記錄上加鎖,以阻止其他事務(wù)插入,更新,刪除id=1的這一行。
需要說(shuō)明的是:
- select * from t where id=1
則是快照讀(SnapShot Read),它并不加鎖,具體在《InnoDB為什么并發(fā)高,讀取快?》中做了詳細(xì)闡述。
三、間隙鎖(Gap Locks)
間隙鎖,它封鎖索引記錄中的間隔,或者***條索引記錄之前的范圍,又或者***一條索引記錄之后的范圍。
依然是上面的例子,InnoDB,RR:
- t(id PK, name KEY, sex, flag)
表中有四條記錄:
- 1, shenjian, m, A
- 3, zhangsan, m, A
- 5, lisi, m, A
- 9, wangwu, f, B
這個(gè)SQL語(yǔ)句
- select * from t
- where id between 8 and 15
- for update
會(huì)封鎖區(qū)間,以阻止其他事務(wù)id=10的記錄插入。
畫(huà)外音:為什么要阻止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)失效。
四、臨鍵鎖(Next-Key Locks)
臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖范圍,既包含索引記錄,又包含索引區(qū)間。
更具體的,臨鍵鎖會(huì)封鎖索引記錄本身,以及索引記錄之前的區(qū)間。
如果一個(gè)會(huì)話(huà)占有了索引記錄R的共享/排他鎖,其他會(huì)話(huà)不能立刻在R之前的區(qū)間插入新的索引記錄。
畫(huà)外音:原文是說(shuō)
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. |
依然是上面的例子,InnoDB,RR:
- t(id PK, name KEY, sex, flag)
表中有四條記錄:
- 1, shenjian, m, A
- 3, zhangsan, m, A
- 5, lisi, m, A
- 9, wangwu, f, B
PK上潛在的臨鍵鎖為:
- (-infinity, 1]
- (1, 3]
- (3, 5]
- (5, 9]
- (9, +infinity]
臨鍵鎖的主要目的,也是為了避免幻讀(Phantom Read)。如果把事務(wù)的隔離級(jí)別降級(jí)為RC,臨鍵鎖則也會(huì)失效。
畫(huà)外音:關(guān)于事務(wù)的隔離級(jí)別,以及幻讀,之前的文章一直沒(méi)有展開(kāi)說(shuō)明,如果大家感興趣,后文詳述。
今天的內(nèi)容,主要對(duì)InnoDB的索引,以及三種鎖的概念做了介紹。場(chǎng)景與例子,也都是最簡(jiǎn)單的場(chǎng)景與最簡(jiǎn)單的例子。
InnoDB的鎖,與索引類(lèi)型,事務(wù)的隔離級(jí)別相關(guān),更多更復(fù)雜更有趣的案例,后續(xù)和大家介紹。
五、總結(jié)
- InnoDB的索引與行記錄存儲(chǔ)在一起,這一點(diǎn)和MyISAM不一樣;
- InnoDB的聚集索引存儲(chǔ)行記錄,普通索引存儲(chǔ)PK,所以普通索引要查詢(xún)兩次;
- 記錄鎖鎖定索引記錄;
- 間隙鎖鎖定間隔,防止間隔中被其他事務(wù)插入;
- 臨鍵鎖鎖定索引記錄+間隔,防止幻讀;
【本文為51CTO專(zhuān)欄作者“58沈劍”原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)聯(lián)系原作者】