鎖定讀(locking read)、更新(UPDATE)或删除(DELETE)通常會在SQL語句處理過程中掃描的每個索引記錄上設定記錄鎖。語句中是否存在排除行的WHERE條件并不重要。InnoDB不記得确切的WHERE條件,而隻知道哪個索引範圍被掃描。這些鎖通常是next-key鎖,它還會阻止在記錄之前插入“間隙”。然而,間隙鎖(gap lock)可以被顯式禁用,這會導緻不使用next-key鎖。
如果在檢索中使用了二級索引,并且要設定的索引記錄鎖是排它的,則InnoDB也會檢索相應的聚集索引記錄并在它們上設定鎖。
執行SQL語句時,如果沒有找到可用的索引,MySQL必須掃描整個表來處理該語句,這樣的話表的每一行都會被鎖定,進而阻塞其他使用者對表的所有插入。是以,建立良好的索引非常重要,這樣可以避免掃描許多不必要的行。
InnoDB設定特定類型的鎖,如下所示:
- SELECT ... FROM 是一緻讀,讀取資料庫快照,除非将事務隔離級别設定為SERIALIZABLE,否則不設定鎖。對于SERIALIZABLE級别,檢索會在遇到的索引記錄上設定共享的next-key鎖。但是,對于使用唯一索引來搜尋唯一行的語句,隻需要一個索引記錄鎖。
- 對于 SELECT ... FOR UPDATE 或者 SELECT ... LOCK IN SHARE MODE ,對掃描的行加鎖,并對不符合結果集中包含條件的行(例如,如果它們不滿足WHERE子句中給出的條件)釋放鎖。
- SELECT ... LOCK IN SHARE MODE 在所有遇到的索引記錄上設定共享的next-key鎖。但是,對于使用唯一索引來搜尋唯一行的語句,隻需要一個索引記錄鎖。
- SELECT ... FOR UPDATE 在搜尋遇到的每個記錄上設定排它的next-key鎖。但是,對于使用唯一索引來搜尋唯一行的語句,僅需要索引記錄鎖定。
- UPDATE ... WHERE ... 在搜尋遇到的每個記錄上設定排它的next-key鎖。但是,對于使用唯一索引來搜尋唯一行的語句,僅需要索引記錄鎖定。
- DELETE FROM ... WHERE ... 在搜尋遇到的每個記錄上設定排它的next-key鎖。但是,對于使用唯一索引來搜尋唯一行的語句,僅需要索引記錄鎖定。
- INSERT 在插入的行上設定排他鎖。該鎖是索引記錄鎖,不是next-key鎖(即沒有間隙鎖),并且不會阻止其他會話插入到插入行之前的間隙中。
舉個例子,假設有一張表t1,結構如下:
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
再假設,有三個會話操作順序如下:
Session 1:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 2:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
Session 3:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
ROLLBACK;
首先Session1獲得i=1這一行的排它鎖,接下來Session2和Session3由于主鍵重複隻能請求擷取該行的共享鎖,由于行上已經有排它鎖,是以Session2和Session3請求的共享鎖不能被立即授予。再接着,Session1復原,行上的排它鎖被釋放,于是Session2和Session3在該行上都持有共享鎖,此時,死鎖發生了,由于對方持有的共享鎖,任何一方都不能獲得該行的排它鎖。
下面這組操作也是類似:
START TRANSACTION;
DELETE FROM t1 WHERE i = 1;
START TRANSACTION;
INSERT INTO t1 VALUES(1);
START TRANSACTION;
INSERT INTO t1 VALUES(1);
COMMIT;
和前面的情況類似,Session1送出以後,Session2和Session3請求該行上的共享鎖被立即授予,此時它們再請求擷取排它鎖時就出現死鎖了,因為共享鎖被另一個事務持有。
補充: 聚集索引與輔助索引
clustered index (譯:聚集索引、聚簇索引)
secondary index (譯:二級索引、輔助索引)
每個InnoDB表都有一個特殊的索引,稱為聚集索引,用于存儲行資料。通常,聚集索引與主鍵是同義詞。為了從查詢、插入和其他資料庫操作中獲得最佳性能,必須了解InnoDB如何使用聚集索引來優化每個表的最常見的查找和DML操作。
Typically, the clustered index is synonymous with the primary key.
通常,“clustered index” 和 “primary key” 是一個意思。
- 當你在表上定義一個PRIMARY KEY時,InnoDB将它用作聚集索引。為建立的每個表定義一個主鍵。如果沒有邏輯唯一的非空列或列集,請添加一個新的自動遞增(auto-increment)列,其值将自動填充。
- 如果你沒有為你的表定義一個PRIMARY KEY,則MySQL會在所有鍵列都不為NULL的情況下找到第一個唯一索引,并且InnoDB使用它作為聚集索引。
- 如果表沒有主鍵或合适的唯一索引,InnoDB會在包含行ID值的合成列上内部生成一個名為GEN_CLUST_INDEX的隐藏聚集索引
通過聚集索引通路行非常快,因為索引搜尋直接指向包含所有行資料的頁。如果表很大,聚集索引體系結構通常節省磁盤I/O操作。
除了聚集索引之外的所有索引都稱為二級索引。在InnoDB中,二級索引中的每條記錄都包含該行的主鍵列,以及為二級索引指定的列。InnoDB使用此主鍵值在聚集索引中搜尋行。
如果主鍵很長,則輔助索引将使用更多空間,是以具有主鍵較短是比較有利的。
With the exception of spatial indexes, InnoDB indexes are B-tree data structures. Index records are stored in the leaf pages of their B-tree or R-tree data structure. The default size of an index page is 16KB. Supported sizes are 64KB, 32KB, 16KB (default), 8KB, and 4KB.