天天看點

記一次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 表名));
           

版權聲明:本文為CSDN部落客「weixin_34293911」的原創文章,遵循CC 4.0 BY-SA版權協定,轉載請附上原文出處連結及本聲明。

原文連結:https://blog.csdn.net/weixin_34293911/article/details/92384002