mysql自增id超大问题查询

引言

小A正在balabala写代码呢,DBA小B忽然发来了一条消息,“快看看你的用户特定信息表T,里面的主键,也就是自增id,都到16亿了,这才多久,在这样下去过不了多久主键就要超出范围了,插入就会失败,balabala......”mysql

我记得没有这么多,最多1k多万,count了下,果真是1100万。原来运维是经过auto_increment那个值看的,就是说,表中有大量的删除插入操做,可是我大部分状况都是更新的,怎么会这样?
sql

问题排查

这张表是一个简单的接口服务在使用,天天大数据会统计一大批信息,而后推送给小A,小A将信息更新到数据库中,若是是新数据就插入,旧数据就更新以前的数据,对外接口就只有查询了。数据库

很快,小A就排查了一遍本身的代码,没有删除的地方,也没有主动插入、更新id的地方,怎么会这样呢?难道是小B的缘由,也不太可能,DBA那边儿管理不少表,有问题的话早爆出来了,但问题在我这里哪里也没头绪。segmentfault

小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

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并不必定都使用。此处** @老是迟到[zongshichidao] ** 多谢指出,看官方文档理解错了

模式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仍是要对外界保持好奇心,保持敏感,这样才会有进步。

原文连接:http://www.javashuo.com/article/p-rrmkodiz-gr.html

相关文章
相关标签/搜索