1. 程式人生 > >InnoDB,select為啥會阻塞insert?

InnoDB,select為啥會阻塞insert?

本文取自微信公眾號——《架構師之路》

MySQL的InnoDB的細粒度行鎖,是它最吸引人的特性之一。

但是,如《InnoDB,5項最佳實踐》所述,如果查詢沒有命中索引,也將退化為表鎖。

InnoDB的細粒度鎖,是實現在索引記錄上的。

一,InnoDB的索引 InnoDB的索引有兩類索引,聚集索引(Clustered Index)與普通索引(Secondary Index)。

InnoDB的每一個表都會有聚集索引: (1)如果表定義了PK,則PK就是聚集索引; (2)如果表沒有定義PK,則第一個非空unique列是聚集索引; (3)否則,InnoDB會建立一個隱藏的row-id作為聚集索引; 為了方便說明,後文都將以PK說明。

索引的結構是B+樹,這裡不展開B+樹的細節,說幾個結論: (1)在索引結構中,非葉子節點儲存key,葉子節點儲存value; (2)聚集索引,葉子節點儲存行記錄(row); 畫外音:所以,InnoDB索引和記錄是儲存在一起的,而MyISAM的索引和記錄是分開儲存的。

(3)普通索引,葉子節點儲存了PK的值; 畫外音: 所以,InnoDB的普通索引,實際上會掃描兩遍: 第一遍,由普通索引找到PK; 第二遍,由PK找到行記錄; 索引結構,InnoDB/MyISAM的索引結構,如果大家感興趣,未來撰文詳述。

舉個例子,假設有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的聚集索引,葉子儲存了所有的行記錄; (2)第二幅圖,name上的普通索引,葉子儲存了PK的值;

對於: select * from t where name=’shenjian’; (1)會先在name普通索引上查詢到PK=1; (2)再在聚集索引衫查詢到(1,shenjian, m, A)的行記錄;

下文簡單介紹InnoDB七種鎖中的剩下三種: 記錄鎖(Record Locks) 間隙鎖(Gap Locks) 臨鍵鎖(Next-Key Locks) 為了方便講述,如無特殊說明,後文中,預設的事務隔離級別為可重複讀(Repeated Read, RR)。

二、記錄鎖(Record Locks) 記錄鎖,它封鎖索引記錄,例如: select * from t where id=1 for update;

它會在id=1的索引記錄上加鎖,以阻止其他事務插入,更新,刪除id=1的這一行。

需要說明的是: select * from t where id=1; 則是快照讀(SnapShot Read),它並不加鎖,具體在《InnoDB為什麼併發高,讀取快?》中做了詳細闡述。

三、間隙鎖(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

這個SQL語句 select * from t where id between 8 and 15 for update; 會封鎖區間,以阻止其他事務id=10的記錄插入。 畫外音: 為什麼要阻止id=10的記錄插入? 如果能夠插入成功,頭一個事務執行相同的SQL語句,會發現結果集多出了一條記錄,即幻影資料。

間隙鎖的主要目的,就是為了防止其他事務在間隔中插入資料,以導致“不可重複讀”。

如果把事務的隔離級別降級為讀提交(Read Committed, RC),間隙鎖則會自動失效。

四、臨鍵鎖(Next-Key Locks) 臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖範圍,既包含索引記錄,又包含索引區間。

更具體的,臨鍵鎖會封鎖索引記錄本身,以及索引記錄之前的區間。

如果一個會話佔有了索引記錄R的共享/排他鎖,其他會話不能立刻在R之前的區間插入新的索引記錄。 畫外音:原文是說 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)。如果把事務的隔離級別降級為RC,臨鍵鎖則也會失效。 畫外音:關於事務的隔離級別,以及幻讀,之前的文章一直沒有展開說明,如果大家感興趣,後文詳述。

今天的內容,主要對InnoDB的索引,以及三種鎖的概念做了介紹。場景與例子,也都是最簡單的場景與最簡單的例子。

InnoDB的鎖,與索引型別,事務的隔離級別相關,更多更復雜更有趣的案例,後續和大家介紹。

五、總結 (1)InnoDB的索引與行記錄儲存在一起,這一點和MyISAM不一樣; (2)InnoDB的聚集索引儲存行記錄,普通索引儲存PK,所以普通索引要查詢兩次; (3)記錄鎖鎖定索引記錄; (4)間隙鎖鎖定間隔,防止間隔中被其他事務插入; (5)臨鍵鎖鎖定索引記錄+間隔,防止幻讀;