场景
最近写了一个收集号码的逻辑,早上来 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 也不安全。