击关注上方“SQL数据库开发”,
设为“置顶或星标”,第一时间送达干货
引言
小A正在balabala写代码呢,DBA小B突然发来了一条消息,“快看看你的用户特定信息表T,里面的主键,也就是自增id,都到16亿了,这才多久,在这样下去过不了多久主键就要超出范围了,插入就会失败,balabala......” 我记得没有这么多,最多1k多万,count了下,果然是1100万。原来运维是通过 auto_increment那个值看的,就是说,表中有大量的删除插入操作,但是我大部分情况都是更新的,怎么会这样?问题排查
这张表是一个简单的接口服务在使用,每天大数据会统计一大批信息,然后推送给小A,小A将信息更新到数据库中,如果是新数据就插入,旧数据就更新之前的数据,对外接口就只有查询了。 很快,小A就排查了一遍自己的代码,没有删除的地方,也没有主动插入、更新id的地方,怎么会这样呢?难道是小B的原因,也不太可能,DBA那边儿管理很多表,有问题的话早爆出来了,但问题在我这里哪里也没头绪。 小A又仔细观察了这1000多万已有的数据,将插入时间、id作为主要观察字段,很快,发现了个问题,每天第一条插入的数据总是比前一天多1000多万,有时候递增的多,有时候递增的少,小A又将矛头指向了DBA小B,将问题又给小B描述了一遍。 小B问了小A,“你是是不是用了 REPLACE INTO...语句”,这是怎么回事呢,原来 REPLACE INTO...会对主键有影响。“REPLACE INTO ...”对主键的影响
假设有一张表 t1: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 DEFAULT CHARSET=utf8 COMMENT='测试replace into'
;
如果新建这张表,执行下面的语句,最后的数据记录如何呢?
insert into t1 values(NULL, 100, "test1"),
(NULL, 101, "test2");
replace into t1 values(NULL, 100, "test3");
原来, REPLACE INTO...每次插入的时候如果唯一索引对应的数据已经存在,会删除原数据,然后重新插入新的数据,这也就导致id会增大,但实际预期可能是更新那条数据。
小A说:“我知道replace是这样,所有既没有用它”,但还是又排查了一遍,确实不是自己的问题,没有使用 REPLACE INTO...。
小A又双叒叕仔细的排查了一遍,还是没发现问题,就让小B查下binlog日志,看看是不是有什么奇怪的地方,查了之后还是没发现问题,确实存在跳跃的情况,但并没有实质性的问题。
下图中 @1的值对应的是自增主键 id,用 (@2,@3)作为唯一索引:
后来过了很久,小B给小A指了个方向,小A开始怀疑自己的插入更新语句
INSERT...ON DUPLICATE KEY UPDATE...了,查了许久,果然是这里除了问题。
“INSERT ... ON DUPLICATE KEY UPDATE ...”对主键的影响
这个语句跟 REPLACE INTO...类似,不过他并不会变更该条记录的主键,还是上面 t1这张表,我们执行下面的语句,执行完结果是什么呢?insert into t1 values(NULL, 100, "test4")
on duplicate key
update name = values(name);
没错,跟小A预想的一样,主键并没有增加,而且 name字段已经更新为想要的了,但是执行结果有条提示,引起了小A的注意:
No errors; 2 rows affected, taking 10.7ms明明更新了一条数据,为什么这里的影响记录条数是2呢?小A,又看了下目前表中的 auto_increment: 竟然是5`,这里本应该是4的。 也就是说,上面的语句,会跟 REPLACE INTO...类似的会将自增ID加1,但实际记录没有加,这是为什么呢? 查了资料之后,小A得知,原来,mysql主键自增有个参数 innodb_autoinc_lock_mode,他有三种可能只 0, 1, 2,mysql5.1之后加入的,默认值是 1,之前的版本可以看做都是 0。 可以使用下面的语句看当前是哪种模式:
select @@innodb_autoinc_lock_mode;
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:
deletefrom 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");
“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还是要对外界保持好奇心,保持敏感,这样才会有进步。作者:燕南飞Liam
来自:https://segmentfault.com/a/1190000017268633
——End——
后台回复关键字:1024,获取一份精心整理的技术干货
后台回复关键字:进群,带你进入高手如云的交流群。
推荐阅读
- 昨天大半个科技圈都在吃瓜!腾讯回应:一言难尽,1000瓶老干妈求骗子线索
- 我只会SQL,到底能不能找到好工作呢?
- Oracle常用函数整理
- MySQL常用函数整理
- 干掉Navicat,这个数据库管理工具真香!