记一次pg序列导致的线上bug

pg实现自增id

pg在实现自增id的时候,主要有两种方式,方法1是将id设置为SERIAL类型。方法2是新建一个序列,设置id的默认值为序列的NEXT值。
方法1:

CREATE TABLE test1
(
  id SERIAL primary key
)

方法2:

CREATE TABLE test2
(
  id primary key
);
CREATE SEQUENCE test2_id_seq 
START WITH 1 
INCREMENT BY 1 
NO MINVALUE 
NO MAXVALUE 
CACHE 1;
alter table test2 alter column id set default nextval('test2_id_seq');

方法1较简单,其实他的实现方式,是数据库自动创建了一个序列,然后将id的默认值设置为这个序列的next值。可以看出方法1和方法2都是通过序列实现了id的自增。那方法1和方法2的区别呢?

首先,方法1较简单。但是方法2更加灵活,能够自己设定起始值,最大值,最小值,步长等(当然方法1也可以再用命令改系统生成的序列,那为什么不直接用方法2呢?)。还有就是当删除方法1建立的表时,对应的序列会自动删除,而方法2不会。

线上bug

有一张表A,服务1有一个接口在A中插入数据。由于某种原因服务2也有一张这样的表B,需要保持这两张表的数据一致。之前是手动将表B的数据添加到表A中,现在进行了开发,自动调用接口进行同步。在测试环境上测试没有问题了。上线,当使用的时候,发现没有插入进去。
下面为分析的过程:

**1.看报错日志 **
报错日志上的信息是Sequelize Validation error。Sequelize是一种orm,开始怀疑是sequelize进行了错误的验证。将插入前的sequelize验证取消,上线后发现还是不行。

2.加上sql日志
怀疑是sql语句有问题,将sequelize转化出的sql语句打印在日志中。发现是一句最简单的插入语句。将其复制,直接在数据库执行,发现报错了。

3.查看数据库报错信息
简要报错信息上写的是唯一索引冲突。表中确实是有唯一索引。通过sql查询到,表中并没有与其冲突的记录。查看详细的报错信息,信息上写着id:xxx已经存在。看了下这个id确实是存在了。

4.id的生成方式
sql语句中并没有指定id,id是通过自增序列获取的。那自增序列为什么不对呢?原来当插入数据时,指定了id的值时,id序列不会变化,不会取出下一个。比如一张表,id默认值是序列的next,步长是1。当表中的最大的id为100,序列的next值101时,如果执行了insert into table (id,name) values(101,'xixi'),那么序列的next值不会变化,还是101。此时执行insert into table (name) values ('haha'),由于id为101的记录已经存在了,所以会报错:id上的唯一索引冲突。

5.产生bug的原因
线上有一次批量同步数据,是直接将sql复制过来(带id),执行sql插入数据。造成了表中的最大id和id序列的当前值不一致。调用接口插入的时候是不带id的,取得是id序列的next,而next的值,表中有记录的id与之相同,造成了id唯一索引冲突。而测试环境中,没有进行过带id的数据同步,同步数据的时候,id序列同步变化,所以没有这种问题。

6.解决办法
解决办法就是手动设置下序列的当前值为表中id的最大值。sql语句为:

SELECT setval('序列名', (SELECT max(id) FROM 表名));

猜你喜欢

转载自my.oschina.net/u/3361610/blog/1824779