前段時間遇到一個InnoDB表自增鎖導致的問題,最近剛好有一個同行網友也問到自增鎖的疑問,所以抽空系統的總結一下,這兩個問題下篇會有闡述。
1. 划分三種插入類型
這里區分一下幾種插入數據行的類型,便於后面描述:(純邏輯上的划分)
-
“Simple inserts”
簡單插入,就是在處理sql語句的時候,能夠提前預估到插入的行數,包括INSERT
/REPLACE
的單行、多行插入,但不含嵌套子查詢以及INSERT ... ON DUPLICATE KEY UPDATE
。 -
“Bulk inserts”
本文暫且叫做 大塊插入,不能提前預知語句要插入的行數,也就無法知道分配多少個自增值,包括INSERT ... SELECT
,REPLACE ... SELECT
, 以及LOAD DATA
導入語句。InnoDB會每處理一行記錄就為 AUTO_INCREMENT 列分配一個值。 -
“Mixed-mode inserts”
混合插入,比如在 “簡單插入” 多行記錄的時候,有的新行有指定自增值,有的沒有,所以獲得最壞情況下需要插入的數量,然后一次性分配足夠的auto_increment id。比如:1
2# c1 是 t1 的 AUTO_INCREMENT 列
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
又比如 INSERT ... ON DUPLICATE KEY UPDATE
,它在 update 階段有可能分配新的自增id,也可能不會。
2. 三種自增模式:innodb_autoinc_lock_mode
在以 5.6 版本,自增id累加模式分為:
-
傳統模式
traditional,innodb_autoinc_lock_mode = 0
在具有 AUTO_INCREMENT 的表上,所有插入語句會獲取一個特殊的表級鎖 AUTO-INC ,這個表鎖是在語句結束之后立即釋放(無需等到事務結束),它可以保證在一個insert里面的多行記錄連續遞增,也能保證多個insert並發情況下自增值是連續的(不會有空洞)。 -
連續模式
consecutive,innodb_autoinc_lock_mode = 1
MySQL 5.1.22開始,InnoDB提供了一種輕量級互斥的自增實現機制,在內存中會有一個互斥量(mutex),每次分配自增長ID時,就通過估算插入的數量(前提是必須能夠估算到插入的數量,否則還是使用傳統模式),然后更新mutex,下一個線程過來時從新 mutex 開始繼續計算,這樣就能避免傳統模式非要等待每個都插入之后才能獲取下一個,把“鎖”降級到 只在分配id的時候 鎖定互斥量。
在innodb_autoinc_lock_mode = 1
(默認) 模式下,“簡單插入”采用上面的 mutex 方式,“大塊插入”(insert/replace … select … 、load data…)依舊采用 AUTO-INC 表級鎖方式。當然如果一個事務里已經持有表 AUTO-INC 鎖,那么后續的簡單插入也需要等待這個 AUTO-INC 鎖釋放。這能夠保證任意insert並發情況下自增值是連續的。 -
交叉模式
interleaved,innodb_autoinc_lock_mode = 2
該模式下所有 INSERT SQL 都不會有表級 AUTO-INC 鎖,多個 語句 可以同時執行,所以在高並發插入場景下性能會好一些。但是當 binlog 采用 SBR 格式時,對於從庫重放日志或者主庫實例恢復時,並不可靠。
另者,它只能保證自增值在 insert語句級別 (單調)遞增,所以多個insert可能會交叉着分配id,最終可能導致多個語句之間的id值不連續,這種情況出現在 混合插入:1
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
mutex 會按行分配4個id,但實際只用到2個,因此出現空洞。
3. 自增空洞(auto-increment sequence gap)
關於 AUTO_INCREMENT 自增出現空洞的問題,有必要再說明一下。
-
在 0, 1, 2 三種任何模式下,如果事務回滾,那么里面獲得自增值的sql回滾,但產生的自增值會一起丟失,不可能重新分配給其它insert語句。這也會產生空洞。
-
在大塊插入情景下
innodb_autoinc_lock_mode
為 0 或 1 時,因為 AUTO-INC 鎖會持續到語句結束,同一時間只有一個 語句 在表上執行,所以自增值是連續的(其它事務需要等待),不會有空洞;innodb_autoinc_lock_mode
為 2 時,兩個 “大塊插入” 之間可能會有空洞,因為每條語句事先無法預知精確的數量而導致分配過多的id,可能有空洞。
4. 混合插入對 AUTO_INCREMENT 的影響
混合插入在 innodb_autoinc_lock_mode 不同模式下會有對 表自增值有不同的表現。
1 |
CREATE TABLE t1 ( |
1. mode 0
1 |
mysql> select @@innodb_autoinc_lock_mode; |
可以看到下一個自增值是 103 ,因為即使這是 一條 insert語句(多行記錄),自增值還是每次分配一個,不會在語句開始前一次分配全。
2. mode 1
1 |
mysql> truncate table t1; ALTER TABLE t1 AUTO_INCREMENT 101; -- 復原 |
可以看到最終插入的值是一樣的,但下一個自增值變成了 105,因為該模式下insert語句處理的時候,提前分配了 4 個自增值,但實際只有了兩個。
注:如果你的insert自增列全都有帶值,那么處理的時候是不會分配自增值的,經過下面這個實驗,可以知道 分配自增值,是在遇到第一個沒有帶自增列的行時,一次性分配的 :
1 |
-- Tx1,先運行。 -- 插入第2行的時候 sleep 5s |
3. mode 2
1 |
mysql> truncate table t1; ALTER TABLE t1 AUTO_INCREMENT 101; -- 復原 |
結果看起來與 連續模式 一樣,其實不然!該模式下,如果另外一個 大塊插入 並發執行時,可能會出現以下現象:
- 大塊插入的的自增值有間斷
- 其它並發執行的事務插入出現 duplicate-key error
1 |
第1點 (create t2 select * from t1) |
總結
上面說了這么多,那么自增模式到底該怎么選擇呢?其實很簡單,目前數據庫默認的 consecutive 即 innodb_autoinc_lock_mode=1
就是最好的模式,一般業務生產庫不會有 insert into ... select ...
或者 load data infile 這樣的維護動作。(提示:即使晚上有數據遷移任務,也不要通過這樣的形式進行)
innodb_autoinc_lock_mode=2
可以提高獲取表自增id的並發能力(性能),但是除非出現上面演示的 duplicate-key 特殊用法情形,不會像網上所說的獲取到相同key導致重復的問題。但是如果binlog在 RBR 格式下不建議使用,可能出現主從數據不一致。還有就是能夠容忍gap的存在,以及多個語句insert的自增值交叉。
參考: https://dev.mysql.com/doc/refman/5.6/en/innodb-auto-increment-handling.html
下篇分析遇到過的 MySQL 自增主鍵相關的具體問題。
轉載地址:http://seanlook.com/2017/02/16/mysql-autoincrement/