一、死鎖示例
考慮下面一個MySQL死鎖的示例:
有如下一張表:
CREATE TABLE `test` (
`id` int(20) NOT NULL,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
表中有如下數據:
-
mysql> SELECT * FROM test;
-
+----+------+
-
| id | name |
-
+----+------+
-
| 1 | 1 |
-
| 5 | 5 |
-
| 10 | 10 |
-
| 15 | 15 |
-
| 20 | 20 |
-
| 25 | 25 |
-
+----+------+
-
6 rows in set (0.00 sec)
當數據庫的隔離級別為Repeatable Read或Serializable時,我們來看這樣的兩個並發事務(場景一):
session1 |
session2 |
begin; |
|
|
begin; |
select * from test where id = 12 for update; |
|
|
select * from test where id = 13 for update; |
insert into test(id, name) values(12, "test1"); |
|
鎖等待中 |
insert into test(id, name) values(13, "test2"); |
鎖等待解除 |
死鎖,session 2的事務被回滾 |
上面兩個並發事務一定會發生死鎖(這里之所以限定RR和Serializable兩個隔離級別,是因為只有這兩個級別下才會有間隙鎖/臨鍵鎖,而這是導致死鎖的根本原因,后面會詳細分析)。
我們再來看另外一個並發場景(場景二):
session1 |
session2 |
begin; |
|
|
begin; |
select * from test where id = 12 for update; |
|
|
select * from test where id = 16 for update; |
insert into test(id, name) values(12, "test1"); |
|
commit; |
insert into test(id, name) values(16, "test2"); |
|
commit; |
在這個並發場景下,兩個事務均能成功提交,而不會有死鎖。
在上面的示例中,我們發現,select ... for update雖然可以用於解決數據庫的並發操作,但在實際項目中卻不建議使用,原因是當查詢條件對應的記錄不存在時,很容易造成死鎖。而造成死鎖的原因和MySQL的鎖機制有關。本文將詳細介紹常見的七種鎖機制,了解了這些鎖機制之后就能理解造成場景一死鎖的根本原因以及場景一和場景二差異的原因。
二、MySQL的八種鎖
-
行鎖(Record Locks)
-
間隙鎖(Gap Locks)
-
臨鍵鎖(Next-key Locks)
-
共享鎖/排他鎖(Shared and Exclusive Locks)
-
意向共享鎖/意向排他鎖(Intention Shared and Exclusive Locks)
-
插入意向鎖(Insert Intention Locks)
-
自增鎖(Auto-inc Locks)
實際上,MySQL官網中還提到了一種預測鎖,這種鎖主要用於存儲了空間數據的空間索引,本文暫不討論。
1、行鎖
這MySQL的官方文檔中有以下描述:
A record lock is a lock on an index record. Record locks always lock index records, even if a table is defined with no indexes. For such cases, InnoDB creates a hidden clustered index and uses this index for record locking.
這句話說明行鎖一定是作用在索引上的。
2、間隙鎖
在MySQL的官方文檔中有以下描述:
A gap lock is a lock on a gap between index records, or a lock on the gap before the first or after the last index record。
這句話表明間隙鎖一定是開區間,比如(3,5)或者。在MySQL官網上還有一段非常關鍵的描述:
Gap locks in InnoDB are “purely inhibitive”, which means that their only purpose is to prevent other transactions from inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.
這段話表明間隙鎖在本質上是不區分共享間隙鎖或互斥間隙鎖的,而且間隙鎖是不互斥的,即兩個事務可以同時持有包含共同間隙的間隙鎖。這里的共同間隙包括兩種場景:其一是兩個間隙鎖的間隙區間完全一樣;其二是一個間隙鎖包含的間隙區間是另一個間隙鎖包含間隙區間的子集。間隙鎖本質上是用於阻止其他事務在該間隙內插入新記錄,而自身事務是允許在該間隙內插入數據的。也就是說間隙鎖的應用場景包括並發讀取、並發更新、並發刪除和並發插入。
在MySQL官網上關於間隙鎖還有一段重要描述:
Gap locking can be disabled explicitly. This occurs if you change the transaction isolation level to READ COMMITTED. Under these circumstances, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.
這段話表明,在RU和RC兩種隔離級別下,即使你使用select ... in share mode或select ... for update,也無法防止幻讀(讀后寫的場景)。因為這兩種隔離級別下只會有行鎖,而不會有間隙鎖。這也是為什么示例中要規定隔離級別為RR的原因。
3、臨鍵鎖
在MySQL的官方文檔中有以下描述:
A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.
這句話表明臨鍵鎖是行鎖+間隙鎖,即臨鍵鎖是是一個左開右閉的區間,比如(3,5]。
在MySQL的官方文檔中還有以下重要描述:
By default, InnoDB operates in REPEATABLE READ transaction isolation level. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows.
個人覺得這段話描述得不夠好,很容易引起誤解。這里更正如下:InnoDB的默認事務隔離級別是RR,在這種級別下,如果你使用select ... in share mode或者select ... for update語句,那么InnoDB會使用臨鍵鎖,因而可以防止幻讀;但即使你的隔離級別是RR,如果你這是使用普通的select語句,那么InnoDB將是快照讀,不會使用任何鎖,因而還是無法防止幻讀。
4、共享鎖/排他鎖
在MySQL的官方文檔中有以下描述:
InnoDB implements standard row-level locking where there are two types of locks, shared (S) locks and exclusive (X) locks。
-
A shared (S) lock permits the transaction that holds the lock to read a row.
-
An exclusive (X) lock permits the transaction that holds the lock to update or delete a row.
這段話明確說名了共享鎖/排他鎖都只是行鎖,與間隙鎖無關,這一點很重要,后面還會強調這一點。其中共享鎖是一個事務並發讀取某一行記錄所需要持有的鎖,比如select ... in share mode;排他鎖是一個事務並發更新或刪除某一行記錄所需要持有的鎖,比如select ... for update。
不過這里需要重點說明的是,盡管共享鎖/排他鎖是行鎖,與間隙鎖無關,但一個事務在請求共享鎖/排他鎖時,獲取到的結果卻可能是行鎖,也可能是間隙鎖,也可能是臨鍵鎖,這取決於數據庫的隔離級別以及查詢的數據是否存在。關於這一點,后面分析場景一和場景二的時候還會提到。
5、意向共享鎖/意向排他鎖
在MySQL的官方文檔中有以下描述:
Intention locks are table-level locks that indicate which type of lock (shared or exclusive) a transaction requires later for a row in a table。
The intention locking protocol is as follows:
-
Before a transaction can acquire a shared lock on a row in a table, it must first acquire an IS lock or stronger on the table.
-
Before a transaction can acquire an exclusive lock on a row in a table, it must first acquire an IX lock on the table.
這段話說明意向共享鎖/意向排他鎖屬於表鎖,且取得意向共享鎖/意向排他鎖是取得共享鎖/排他鎖的前置條件。
共享鎖/排他鎖與意向共享鎖/意向排他鎖的兼容性關系:
|
X |
IX |
S |
IS |
---|---|---|---|---|
X |
互斥 |
互斥 |
互斥 |
互斥 |
IX |
互斥 |
兼容 |
互斥 |
兼容 |
S |
互斥 |
互斥 |
兼容 |
兼容 |
IS |
互斥 |
兼容 |
兼容 |
兼容 |
這里需要重點關注的是IX鎖和IX鎖是相互兼容的,這是導致上面場景一發生死鎖的前置條件,后面會對死鎖原因進行詳細分析。
6、插入意向鎖(IIX)
在MySQL的官方文檔中有以下重要描述:
An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion. This 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. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.
這段話表明盡管插入意向鎖是一種特殊的間隙鎖,但不同於間隙鎖的是,該鎖只用於並發插入操作。如果說間隙鎖鎖住的是一個區間,那么插入意向鎖鎖住的就是一個點。因而從這個角度來說,插入意向鎖確實是一種特殊的間隙鎖。與間隙鎖的另一個非常重要的差別是:盡管插入意向鎖也屬於間隙鎖,但兩個事務卻不能在同一時間內一個擁有間隙鎖,另一個擁有該間隙區間內的插入意向鎖(當然,插入意向鎖如果不在間隙鎖區間內則是可以的)。這里我們再回顧一下共享鎖和排他鎖:共享鎖用於讀取操作,而排他鎖是用於更新或刪除操作。也就是說插入意向鎖、共享鎖和排他鎖涵蓋了常用的增刪改查四個動作。
7、示例分析
到此為止,我們介紹了MySQL常用的七種鎖的前六種,理解了這六種鎖之后,才能很好地分析和理解開頭給出的兩個場景。我們先來分析場景一:
session1 |
session2 |
begin; |
|
|
begin; |
select * from test where id = 12 for update; 先請求IX鎖並成功獲取 再請求X鎖,但因行記錄不存在,故得到的是間隙鎖(10,15) |
|
|
select * from test where id = 13 for update; 先請求IX鎖並成功獲取 再請求X鎖,但因行記錄不存在,故得到的是間隙鎖(10,15) |
insert into test(id, name) values(12, "test1"); 請求插入意向鎖(12),因事務二已有間隙鎖,請求只能等待 |
|
鎖等待中 |
insert into test(id, name) values(13, "test2"); 請求插入意向鎖(13),因事務一已有間隙鎖,請求只能等待 |
鎖等待解除 |
死鎖,session 2的事務被回滾 |
在場景一中,因為IX鎖是表鎖且IX鎖之間是兼容的,因而事務一和事務二都能同時獲取到IX鎖和間隙鎖。另外,需要說明的是,因為我們的隔離級別是RR,且在請求X鎖的時候,查詢的對應記錄都不存在,因而返回的都是間隙鎖。接着事務一請求插入意向鎖,這時發現事務二已經獲取了一個區間間隙鎖,而且事務一請求的插入點在事務二的間隙鎖區間內,因而只能等待事務二釋放間隙鎖。這個時候事務二也請求插入意向鎖,該插入點同樣位於事務一已經獲取的間隙鎖的區間內,因而也不能獲取成功,不過這個時候,MySQL已經檢查到了死鎖,於是事務二被回滾,事務一提交成功。
分析並理解了場景一,那場景二理解起來就會簡單多了:
session1 |
session2 |
begin; |
|
|
begin; |
select * from test where id = 12 for update; 先請求IX鎖並成功獲取 再請求X鎖,但因行記錄不存在,故得到的是間隙鎖(10,15) |
|
|
select * from test where id = 16 for update; 先請求IX鎖並成功獲取 再請求X鎖,但因行記錄不存在,故得到的是間隙鎖(15,20) |
insert into test(id, name) values(12, "test1"); 請求插入意向鎖(12),獲取成功 |
|
commit; |
insert into test(id, name) values(16, "test2"); 請求插入意向鎖(16),獲取成功 |
|
.commit; |
場景二中,兩個間隙鎖沒有交集,而各自獲取的插入意向鎖也不是同一個點,因而都能執行成功。
8、自增鎖
最后,我們再來介紹下自增鎖。在MySQL的官方文檔中有以下描述:
An AUTO-INC lock is a special table-level lock taken by transactions inserting into tables with AUTO_INCREMENT columns.The innodb_autoinc_lock_mode configuration option controls the algorithm used for auto-increment locking. It allows you to choose how to trade off between predictable sequences of auto-increment values and maximum concurrency for insert operations.
這段話表明自增鎖是一種特殊的表級鎖,主要用於事務中插入自增字段,也就是我們最常用的自增主鍵id。通過innodb_autoinc_lock_mode參數可以設置自增主鍵的生成策略。為了便於介紹innodb_autoinc_lock_mode參數,我們先將需要用到自增鎖的Insert語句進行分類:
1)Insert語句分類
-
“INSERT-like” statements(類INSERT語句) (這種語句實際上包含了下面的2、3、4)
所有可以向表中增加行的語句,包括INSERT, INSERT ... SELECT, REPLACE, REPLACE ... SELECT, and LOAD DATA。包括“simple-inserts”, “bulk-inserts”, and “mixed-mode” inserts.
2. “Simple inserts”
可以預先確定要插入的行數(當語句被初始處理時)的語句。 這包括沒有嵌套子查詢的單行和多行INSERT和REPLACE語句,但不包括INSERT ... ON DUPLICATE KEY UPDATE。
3. “Bulk inserts”
事先不知道要插入的行數(和所需自動遞增值的數量)的語句。 這包括INSERT ... SELECT,REPLACE ... SELECT和LOAD DATA語句,但不包括純INSERT。 InnoDB在處理每行時一次為AUTO_INCREMENT列分配一個新值。
4. “Mixed-mode inserts”
這些是“Simple inserts”語句但是指定一些(但不是全部)新行的自動遞增值。 示例如下,其中c1是表t1的AUTO_INCREMENT列:
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
另一種類型的“Mixed-mode inserts”是INSERT ... ON DUPLICATE KEY UPDATE,其在最壞的情況下實際上是INSERT語句隨后又跟了一個UPDATE,其中AUTO_INCREMENT列的分配值不一定會在 UPDATE 階段使用。
2)InnoDB AUTO_INCREMENT鎖定模式分類
-
innodb_autoinc_lock_mode = 0 (“traditional” lock mode)
這種鎖定模式提供了在MySQL 5.1中引入innodb_autoinc_lock_mode配置參數之前存在的相同行為。傳統的鎖定模式選項用於向后兼容性,性能測試以及解決“Mixed-mode inserts”的問題,因為語義上可能存在差異。
在此鎖定模式下,所有“INSERT-like”語句獲得一個特殊的表級AUTO-INC鎖,用於插入具有AUTO_INCREMENT列的表。此鎖定通常保持到語句結束(不是事務結束),以確保為給定的INSERT語句序列以可預測和可重復的順序分配自動遞增值,並確保自動遞增由任何給定語句分配的值是連續的。
在基於語句復制(statement-based replication)的情況下,這意味着當在從服務器上復制SQL語句時,自動增量列使用與主服務器上相同的值。多個INSERT語句的執行結果是確定性的,SLAVE再現與MASTER相同的數據(反之,如果由多個INSERT語句生成的自動遞增值交錯,則兩個並發INSERT語句的結果將是不確定的,並且不能使用基於語句的復制可靠地傳播到從屬服務器)。
-
innodb_autoinc_lock_mode = 1 (“consecutive” lock mode)
這是默認的鎖定模式。在這個模式下,“bulk inserts”仍然使用AUTO-INC表級鎖,並保持到語句結束.這適用於所有INSERT ... SELECT,REPLACE ... SELECT和LOAD DATA語句。同一時刻只有一個語句可以持有AUTO-INC鎖。
而“Simple inserts”(要插入的行數事先已知)通過在mutex(輕量鎖)的控制下獲得所需數量的自動遞增值來避免表級AUTO-INC鎖, 它只在分配過程的持續時間內保持,而不是直到語句完成。 不使用表級AUTO-INC鎖,除非AUTO-INC鎖由另一個事務保持。 如果另一個事務保持AUTO-INC鎖,則“簡單插入”等待AUTO-INC鎖,如同它是一個“批量插入”。
此鎖定模式確保,當行數不預先知道的INSERT存在時(並且自動遞增值在語句過程執行中分配)由任何“類INSERT”語句分配的所有自動遞增值是連續的,並且對於基於語句的復制(statement-based replication)操作是安全的。
這種鎖定模式顯著地提高了可擴展性,並且保證了對於基於語句的復制(statement-based replication)的安全性。此外,與“傳統”鎖定模式一樣,由任何給定語句分配的自動遞增數字是連續的。 與使用自動遞增的任何語句的“傳統”模式相比,語義沒有變化,但有個特殊場景需要注意:The exception is for “mixed-mode inserts”, where the user provides explicit values for an AUTO_INCREMENT column for some, but not all, rows in a multiple-row “simple insert”. For such inserts, InnoDB allocates more auto-increment values than the number of rows to be inserted. However, all values automatically assigned are consecutively generated (and thus higher than) the auto-increment value generated by the most recently executed previous statement. “Excess” numbers are lost.
也就說對於混合模式的插入,可能會有部分多余自增值丟失。
在連續鎖定模式下,InnoDB可以避免為“Simple inserts”語句使用表級AUTO-INC鎖,其中行數是預先已知的,並且仍然保留基於語句的復制的確定性執行和安全性。
-
innodb_autoinc_lock_mode = 2 (“interleaved” lock mode)
在這種鎖定模式下,所有類INSERT(“INSERT-like” )語句都不會使用表級AUTO-INC lock,並且可以同時執行多個語句。這是最快和最可擴展的鎖定模式,但是當使用基於語句的復制或恢復方案時,從二進制日志重播SQL語句時,這是不安全的。
在此鎖定模式下,自動遞增值保證在所有並發執行的“類INSERT”語句中是唯一且單調遞增的。但是,由於多個語句可以同時生成數字(即,跨語句交叉編號),為任何給定語句插入的行生成的值可能不是連續的。
如果執行的語句是“simple inserts”,其中要插入的行數已提前知道,則除了“混合模式插入”之外,為單個語句生成的數字不會有間隙。然而,當執行“批量插入”時,在由任何給定語句分配的自動遞增值中可能存在間隙。
如果不使用二進制日志作為恢復或復制的一部分來重放SQL語句,則可以使用interleaved lock模式來消除所有使用表級AUTO-INC鎖,以實現更大的並發性和性能,其代價是由於並發的語句交錯執行,同一語句生成的AUTO-INCREMENT值可能會產生GAP。
-
innodb_autoinc_lock_mode參數的修改
編輯/etc/my.cnf,加入如下行:
innodb_autoinc_lock_mode=2
直接通過命令修改會報錯:
-
mysql(mdba@localhost:(none) 09:32:19)>set global innodb_autoinc_lock_mode=2;
-
ERROR 1238 (HY000): Variable 'innodb_autoinc_lock_mode' is a read only variable
-
3)InnoDB AUTO_INCREMENT鎖定模式含義
1.在復制環節中使用自增列
mysql主從復制有三種模式:row,statement,mixed三種。
- row模式:
在row模式下,日志中會記錄成每一行數據被修改的形式,然后在slave端再對相同的數據進行修改,只記錄要修改的數據,只有value,不會有sql多表關聯的情況。由於row模式是直接將主庫中的每一行數據在從庫進行復寫,因而row模式的優點是不會存在主從不一致的問題;而row模式的缺點就是會產生大量的binlog日志。
- statement模式:
在statement模式下,每一條會修改數據的sql都會記錄到master的binlog中,slave在復制的時候sql進程會解析成和原來master端執行多相同的sql再執行。statement模式的優點是不需要記錄每一行數據的變化減少了binlog日志量,節省了I/O以及存儲資源,提高性能,因為他只需要記錄在master上所執行的語句的細節以及執行語句的上下文信息;statement模式的缺點是,由於他是記錄的執行語句,所以,為了讓這些語句在slave端也能正確執行,那么他還必須記錄每條語句在執行的時候的一些相關信息,也就是上下文信息,以保證所有語句在slave端被執行的時候能夠得到和在master端執行時候相同的結果。另外就是,由於mysql現在發展比較快,很多的新功能不斷的加入,使mysql的復制遇到了不小的挑戰,自然復制的時候涉及到越復雜的內容,bug也就越容易出現。在statement中,目前已經發現不少情況會造成Mysql的復制出現問題,主要是修改數據的時候使用了某些特定的函數或者功能的時候會出現,比如:sleep()函數在有些版本中就不能被正確復制,在存儲過程中使用了last_insert_id()函數,可能會使slave和master上得到不一致的id等等。由於row是基於每一行來記錄的變化,所以不會出現,類似的問題。
- mixed模式:
從官方文檔中看到,之前的 MySQL 一直都只有基於 statement 的復制模式,直到 5.1.5 版本的 MySQL 才開始支持 row 復制。從 5.0 開始,MySQL 的復制已經解決了大量老版本中出現的無法正確復制的問題。但是由於存儲過程的出現,給 MySQL Replication 又帶來了更大的新挑戰。另外,看到官方文檔說,從 5.1.8 版本開始,MySQL 提供了除 Statement 和 Row 之外的第三種復制模式:Mixed,實際上就是前兩種模式的結合。在 Mixed 模式下,MySQL 會根據執行的每一條具體的 SQL 語句來區分對待記錄的日志形式,也就是在 statement 和 row 之間選擇一種。新版本中的 statment 還是和以前一樣,僅僅記錄執行的語句。而新版本的 MySQL 中對 row 模式也被做了優化,並不是所有的修改都會以 row 模式來記錄,比如遇到表結構變更的時候就會以 statement 模式來記錄,如果 SQL 語句確實就是 update 或者 delete 等修改數據的語句,那么還是會記錄所有行的變更。
由此可知,如果你在使用基於語句的復制(statement-based replication)請將innodb_autoinc_lock_mode設置為0或1,並在主從上使用相同的值。 如果使用innodb_autoinc_lock_mode = 2(“interleaved”)或主從不使用相同的鎖定模式的配置,自動遞增值不能保證在從機上與主機上相同。
如果使用基於行的或混合模式的復制,則所有自動增量鎖定模式都是安全的,因為基於行的復制對SQL語句的執行順序不敏感(混合模式會在遇到不安全的語句是使用基於行的復制模式)。
2. “Lost” auto-increment values and sequence gaps
在所有鎖定模式(0,1和2)中,如果生成自動遞增值的事務回滾,那些自動遞增值將“丟失”。 一旦為自動增量列生成了值,無論是否完成“類似INSERT”語句以及包含事務是否回滾,都不能回滾。 這種丟失的值不被重用。 因此,存儲在表的AUTO_INCREMENT列中的值可能存在間隙。
3. Specifying NULL or 0 for the AUTO_INCREMENT column
在所有鎖定模式(0,1和2)中,如果用戶在INSERT中為AUTO_INCREMENT列指定NULL或0,InnoDB會將該行視為未指定值,並為其生成新值。
4. 為AUTO_INCREMENT列分配一個負值
在所有鎖定模式(0,1和2)中,如果您為AUTO_INCREMENT列分配了一個負值,則InnoDB會將該行為視為未指定值,並為其生成新值。
5. 如果AUTO_INCREMENT值大於指定整數類型的最大整數
在所有鎖定模式(0,1和2)中,如果值大於可以存儲在指定整數類型中的最大整數,則InnoDB會將該值設置為指定類型所允許的最大值。
6. Gaps in auto-increment values for “bulk inserts”
當innodb_autoinc_lock_mode設置為0(“traditional”)或1(“consecutive”)時,任何給定語句生成的自動遞增值是連續的,沒有間隙,因為表級AUTO-INC鎖會持續到 語句結束,並且一次只能執行一個這樣的語句。
當innodb_autoinc_lock_mode設置為2(“interleaved”)時,在“bulk inserts”生成的自動遞增值中可能存在間隙,但只有在並發執行“INSERT-Like”語句時才會產生這種情況。
對於鎖定模式1或2,在連續語句之間可能出現間隙,因為對於批量插入,每個語句所需的自動遞增值的確切數目可能不為人所知,並且可能進行過度估計。
7. 由“mixed-mode inserts”分配的自動遞增值
考慮一下場景,在“mixed-mode insert”中,其中一個“simple insert”語句指定了一些(但不是全部)行的AUTO-INCREMENT值。 這樣的語句在鎖模式0,1和2中表現不同。innodb_autoinc_lock_mode=0時,auto-increment值一次只分配一個,而不是在開始時全部分配。當innodb_autoinc_lock_mode=1時,不同於innodb_autoinc_lock_mode=0時的情況,因為auto-increment值在語句一開始就分配了,但實際可能使用不完。當innodb_autoinc_lock_mode=2時,取決於並發語句的執行順序。
8. 在INSERT語句序列的中間修改AUTO_INCREMENT列值
在所有鎖定模式(0,1和2)中,在INSERT語句序列中間修改AUTO_INCREMENT列值可能會導致duplicate key錯誤。
4)InnoDB AUTO_INCREMENT計數器初始化
如果你為一個Innodb表創建了一個AUTO_INCREMENT列,則InnoDB數據字典中的表句柄包含一個稱為自動遞增計數器的特殊計數器,用於為列分配新值。 此計數器僅存在於內存中,而不存儲在磁盤上。
要在服務器重新啟動后初始化自動遞增計數器,InnoDB將在首次插入行到包含AUTO_INCREMENT列的表時執行以下語句的等效語句。
SELECT MAX(ai_col) FROM table_name FOR UPDATE;
InnoDB增加語句檢索的值,並將其分配給表和表的自動遞增計數器。 默認情況下,值增加1。此默認值可以由auto_increment_increment配置設置覆蓋。
如果表為空,InnoDB使用值1。此默認值可以由auto_increment_offset配置設置覆蓋。
如果在自動遞增計數器初始化前使用SHOW TABLE STATUS語句查看表, InnoDB將初始化計數器值,但不會遞增該值。這個值會儲存起來以備之后的插入語句使用。這個初始化過程使用了一個普通的排它鎖來讀取表中自增列的最大值。InnoDB遵循相同的過程來初始化新創建的表的自動遞增計數器。
在自動遞增計數器初始化之后,如果您未明確指定AUTO_INCREMENT列的值,InnoDB會遞增計數器並將新值分配給該列。如果插入顯式指定列值的行,並且該值大於當前計數器值,則將計數器設置為指定的列值。
只要服務器運行,InnoDB就使用內存中自動遞增計數器。當服務器停止並重新啟動時,InnoDB會重新初始化每個表的計數器,以便對表進行第一次INSERT,如前所述。
服務器重新啟動還會取消CREATE TABLE和ALTER TABLE語句中的AUTO_INCREMENT = N表選項的效果(可在建表時可用“AUTO_INCREMENT=n”選項來指定一個自增的初始值,也可用alter table table_name AUTO_INCREMENT=n命令來重設自增的起始值)。
參考博客:
-
https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html MySQL官網
-
https://www.cnblogs.com/rjzheng/p/9950951.html 史上最全的select加鎖分析
-
https://blog.csdn.net/wufaliang003/article/details/81937418 InnoDB並發插入,居然使用意向鎖
-
https://blog.csdn.net/ignorewho/article/details/86423147 插入意向鎖
-
https://www.cnblogs.com/rjzheng/p/9955395.html MySQL事務隔離級別
-
https://www.jianshu.com/p/68b581481831AUTO-INC鎖和AUTO_INCREMENT在InnoDB中處理方式
-
https://www.jianshu.com/p/10a8d8977aaf MySQL自增鎖模式innodb_autoinc_lock_mode參數詳解
-
https://blog.csdn.net/wanghao112956/article/details/91957538 MySQL 死鎖套路:三個事務插入有一個回滾
-
https://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html Locks Set by Different SQL Statements in InnoDB
-
https://blog.csdn.net/keda8997110/article/details/50895171/ binlog三種模式的區別(row,statement,mixed)
轉載:https://blog.csdn.net/Saintyyu/article/details/91269087