場景
最近寫了一個收集號碼的邏輯,早上來 count 了一下 phone 表,發現已經收集到了 33w 條記錄。
> select count(*) from phone
336019
但細心的我留意到似乎有 id 值很大的記錄
> select min(id) from phone
1
> select max(id) from phone
1003498
咂摸着覺着不對味。
原因
查了查資料這還有個術語,叫 MySQL auto_increment 空洞問題,是因為我插入/更新表的事后偷懶使用了 upsert 函數搞出來,生成的 SQL 語句是
INSERT INTO xxtable ON DUPLICATE KEY UPDATE
而上面的語句是屬於 mixed-mode inserts,分配時並不知道是插入還是更新,所以都統統讓 id 自增加大。而且 innodb 的默認 innodb_autoinc_lock_mode
模式為 1,在 mixed-mode inserts 中的確會造成空洞。
inserts mode
插入類型有以下幾種
simple inserts
simple inserts 指的是那種能夠事先確定插入行數的語句,比如 INSERT/REPLACE INTO
等插入單行或者多行的語句,語句中不包括嵌套子查詢。
此外,INSERT INTO ... ON DUPLICATE KEY UPDATE
也除外。
bulk inserts
bulk inserts 批量插入,事先無法確定插入行數的語句。
mixed-mode inserts
simple inserts 類型中有些行指定了 auto_increment 列的值,有些沒有指定,比如:
INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d');
另一種情況 INSERT ... ON DUPLICATE KEY UPDATE
這種語句,可能導致分配的 auto_increment 值沒有被使用。
innodb_autoinc_lock_mode
Mysql 5.1 后加了一個配置叫 innodb_autoinc_lock_mode
innodb_autoinc_lock_mode = 0(traditional lock mode)
傳統的 auto_increment 機制,針對 auto_increment 列的插入操作都會加 AUTO-INC 鎖,分配的值也是一個個分配,是連續的,正常情況下也不會有空洞(當然如果事務rollback了這個auto_increment值就會浪費掉,從而造成空洞)。
innodb_autoinc_lock_mode = 1(consecutive lock mode), Innodb 默認
這種情況下,針對 bulk inserts 才會采用 AUTO-INC 鎖這種方式,而針對 simple inserts,則采用了一種新的輕量級的互斥鎖來分配 auto_increment 列的值。當然,如果其他事務已經持有了 AUTO-INC 鎖,則 simple inserts 需要等待.
需要注意的是,在 innodb_autoinc_lock_mode=1 時,語句之間是可能出現 auto_increment 值的間隔的。比如 mixed-mode inserts 以及 bulk inserts 中都有可能導致一些分配的 auto_increment 值被浪費掉從而導致空洞。后面會有例子。
innodb_autoinc_lock_mode=2(interleaved lock mode)
這種模式下任何類型的 inserts 都不會采用 AUTO-INC 鎖,性能最好,但是在同一條語句內部產生 auto_increment 值空洞。此外,這種模式對 statement-based replication 也不安全。