記一次pg序列致使的線上bug

pg實現自增id

pg在實現自增id的時候,主要有兩種方式,方法1是將id設置爲SERIAL類型。方法2是新建一個序列,設置id的默認值爲序列的NEXT值。
方法1:sql

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中,如今進行了開發,自動調用接口進行同步。在測試環境上測試沒有問題了。上線,當使用的時候,發現沒有插入進去。
下面爲分析的過程:code

**1.看報錯日誌 **
報錯日誌上的信息是Sequelize Validation error。Sequelize是一種orm,開始懷疑是sequelize進行了錯誤的驗證。將插入前的sequelize驗證取消,上線後發現仍是不行。orm

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 表名));
相關文章
相關標籤/搜索