替代Postgres SERIAL字段,以解决ON CONFLICT导致更新时的增量值

时间:2019-03-11 02:49:16

标签: sql postgresql range auto-increment bigint

我最近由于不知道SERIAL字段会增加是否插入数据的问题而陷入困境。

我在此问题上阅读的大多数答案都讨论了如何防止在列中出现漏洞,我可以肯定的是,在大多数情况下,这与所要解决的问题无关,而且肯定也没有我的情况。

我的情况是我的软件的特定用户正在使用某种功能,该功能导致对单个记录执行数百万次upsert。该记录用作状态信息,当我幼稚的时候,我很高兴地没有意识到当INTEGER id字段nextval()达到其极限时即将发生的错误,即以下错误:

错误:整数超出范围 SQL状态:22003

所以我的问题过去是,曾经发生过,在发生冲突回滚的情况下,如何防止id字段增加下一个序列值。

我希望其他人将他们的知识添加到我的解决方案中。

1 个答案:

答案 0 :(得分:0)

我为缓解此问题而采取的立即解决方案是将列更改为BIGINT,如下所示:

ALTER TABLE MyTable ALTER COLUMN idMyTable TYPE BIGINT;

在我的案例中,记录数非常少(<1000),因此执行起来很琐碎。

一旦这一切都解决了,就该寻找解决潜在问题的解决方案了。我的解决方案不太可能像使用SERIAL字段那样高效,因此,如果您要实现与我所做的类似的事情,请根据您的用例牢记这一点-总是在某处进行权衡。

请考虑下表和插入/查询的数据:

CREATE TABLE TestTable ( id SERIAL PRIMARY KEY NOT NULL, Key TEXT UNIQUE NOT NULL, Val TEXT );
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'banana') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'apple') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'peach') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Animal', 'horse') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
SELECT * FROM TestTable;
id Key    Val
1  Fruit  peach
4  Animal horse

在这种情况下,即使未在TestTable中创建任何新记录,Fruit更新期间的每个冲突也使SERIAL值增加。

现在这是我目前正在使用的解决方法。如果有人知道如何将表名连接到“ NEW.id”,我很想听听,因为我想将ID列idTablename命名为一致性。

CREATE OR REPLACE FUNCTION IncrementSerial()
RETURNS trigger AS $fn$
BEGIN
  EXECUTE format('SELECT COALESCE( MAX( id ), 0 ) + 1 FROM %I.%I;',TG_TABLE_SCHEMA,TG_TABLE_NAME) INTO NEW.id;
  RETURN NEW;
END
$fn$ LANGUAGE 'plpgsql'

CREATE TABLE TestTable ( id INTEGER PRIMARY KEY NOT NULL, Key TEXT UNIQUE NOT NULL, Val TEXT );

CREATE TRIGGER trgIncrementSerial
BEFORE INSERT ON TestTable
  FOR EACH ROW
    EXECUTE PROCEDURE IncrementSerial()

INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'banana') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'apple') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Fruit', 'peach') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
INSERT INTO TestTable (Key,Val) VALUES ('Animal', 'horse') ON CONFLICT( Key ) DO UPDATE SET Val=EXCLUDED.Val;
SELECT * FROM TestTable;
id Key    Val
1  Fruit  peach
2  Animal horse

如您所见,SERIAL ID现在仅使用下一个最高ID号,这对于我的大多数用例来说都是理想的选择。

很显然,如果id密钥必须始终唯一,这将是一个问题,因为删除最后一条记录将释放该id。如果这不是问题(例如,id仅用于引用和级联的地方),那么这对您可能是一个很好的解决方案。