视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
mysql自增id超大问题的排查与解决
2020-11-09 20:21:51 责编:小采
文档


明明更新了一条数据,为什么这里的影响记录条数是2呢?小A,又看了下目前表中的auto_increment

CREATE TABLE `t1` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID,自增',
`uid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '用户uid',
`name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户昵称',
PRIMARY KEY (`id`),
UNIQUE KEY `u_idx_uid` (`uid`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='测试replace into';

竟然是5`,这里本应该是4的。

也就是说,上面的语句,会跟REPLACE INTO ...类似的会将自增ID加1,但实际记录没有加,这是为什么呢?

查了资料之后,小A得知,原来,mysql主键自增有个参数innodb_autoinc_lock_mode,他有三种可能只0,1,2,mysql5.1之后加入的,默认值是1,之前的版本可以看做都是0。

可以使用下面的语句看当前是哪种模式

select @@innodb_autoinc_lock_mode;

小A使用的数据库默认值也是1,当做简单插入(可以确定插入行数)的时候,直接将auto_increment加1,而不会去锁表,这也就提高了性能。当插入的语句类似insert into select ...这种复杂语句的时候,提前不知道插入的行数,这个时候就要要锁表(一个名为AUTO_INC的特殊表锁)了,这样auto_increment才是准确的,等待语句结束的时候才释放锁。还有一种称为Mixed-mode inserts的插入,比如INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d'),其中一部分明确指定了自增主键值,一部分未指定,还有我们这里讨论的INSERT ... ON DUPLICATE KEY UPDATE ...也属于这种,这个时候会分析语句,然后按尽可能多的情况去分配auto_incrementid,这个要怎么理解呢,我看下面这个例子:

truncate table t1;
insert into t1 values(NULL, 100, "test1"),(NULL, 101, "test2"),(NULL, 102, "test2"),(NULL, 103, "test2"),(NULL, 104, "test2"),(NULL, 105, "test2");

-- 此时数据表下一个自增id是7

delete from t1 where id in (2,3,4);

-- 此时数据表只剩1,5,6了,自增id还是7

insert into t1 values(2, 106, "test1"),(NULL, 107, "test2"),(3, 108, "test2");

-- 这里的自增id是多少呢?

上面的例子执行完之后表的下一个自增id是10,你理解对了吗,因为最后一条执行的是一个Mixed-mode inserts语句,innoDB会分析语句,然后分配三个id,此时下一个id就是10了,但分配的三个id并不一定都使用。此处 @总是迟到 多谢指出,看官方文档理解错了

模式0的话就是不管什么情况都是加上表锁,等语句执行完成的时候在释放,如果真的添加了记录,将auto_increment加1。

至于模式2,什么情况都不加AUTO_INC锁,存在安全问题,当binlog格式设置为Statement模式的时候,从库同步的时候,执行结果可能跟主库不一致,问题很大。因为可能有一个复杂插入,还在执行呢,另外一个插入就来了,恢复的时候是一条条来执行的,就不能重现这种并发问题,导致记录id可能对不上。

至此,id跳跃的问题算是分析完了,由于innodb_autoinc_lock_mode值是1,INSERT ... ON DUPLICATE KEY UPDATE ...是简单的语句,预先就可以计算出影响的行数,所以不管是否更新,这里都将auto_increment加1(多行的话大于1)。

如果将innodb_autoinc_lock_mode值改为0,再次执行INSERT ... ON DUPLICATE KEY UPDATE ...的话,你会发现auto_increment并没有增加,因为这种模式直接加了AUTO_INC锁,执行完语句的时候释放,发现没有增加行数的话,不会增加自增id的。

INSERT ... ON DUPLICATE KEY UPDATE ...影响的行数是1为什么返回2?

为什么会这样呢,按理说影响行数就是1啊,看看官方文档的说明

With ON DUPLICATE KEY UPDATE, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values

官方明确说明了,插入影响1行,更新影响2行,0的话就是存在且更新前后值一样。是不是很不好理解?

其实,你要这样想就好了,这是为了区分到底是插入了还是更新了,返回1表示插入成功,2表示更新成功。

解决方案

将innodb_autoinc_lock_mode设置为0肯定可以解决问题,但这样的话,插入的并发性可能会受很大影响,因此小A自己想着DBA也不会同意。经过考虑,目前准备了两种较为可能的解决方案:

修改业务逻辑

修改业务逻辑,将INSERT ... ON DUPLICATE KEY UPDATE ...语句拆开,先去查询,然后去更新,这样就可以保证主键不会不受控制的增大,但增加了复杂性,原来的一次请求可能变为两次,先查询有没有,然后去更新。

删除表的自增主键

删除自增主键,让唯一索引来做主键,这样子基本不用做什么变动,只要确定目前的自增主键没有实际的用处即可,这样的话,插入删除的时候可能会影响效率,但对于查询多的情况来说,小A比较两种之后更愿意选择后者。

结语

其实INSERT ... ON DUPLICATE KEY UPDATE ...这个影响行数是2的,小A很早就发现了,只是没有保持好奇心,不以为然罢了,没有深究其中的问题,这深究就起来会带出来一大串新知识,挺好,看来小A还是要对外界保持好奇心,保持敏感,这样才会有进步。

总结

下载本文
显示全文
专题