尽管使用“冲突时不执行任何操作”,为什么串行主键仍会递增?

Don*_*on2 8 postgresql sequence upsert postgresql-13

我在 PostgreSQL 13 中有下表:

表名:新表

field       type 
-----       ----    
Seq         bigserial   
code        varchar     
Run Code Online (Sandbox Code Playgroud)

Seq是主键(自增)
Code是唯一键索引

Insert Into newtable (Code) Values ('001') On Conflict(Code) Do Nothing   --> Seq value is 1
Insert Into newtable (Code) Values ('001') On Conflict(Code) Do Nothing
Insert Into newtable (Code) Values ('001') On Conflict(Code) Do Nothing

Insert Into newtable (Code) Values ('002') On Conflict(Code) Do Nothing   --> Seq value is 4
Run Code Online (Sandbox Code Playgroud)

为什么是序列4?有什么方法可以仅在成功插入时增加价值?

Erw*_*ter 8

MERGE代替使用?

MERGE(在 Postgres 15 中添加)表面上看起来很相似,但在底层却有很大不同。手册:

MERGE与修改目标表的其他命令同时运行时,应用通常的事务隔离规则;有关每个隔离级别的行为的说明,请参见 第 13.2 节。您可能还希望考虑使用作为替代语句,该语句提供 在发生并发时INSERT ... ON CONFLICT运行的能力。这两种语句类型之间存在多种差异和限制,并且它们不可互换。UPDATEINSERT

特别是,MERGE无法可靠地抑制并发写入负载下的独特违规,例如INSERT ... ON CONFLICT DO NOTHINGcan (相关命令)。手册:

如果MERGE尝试INSERT存在唯一索引并且同时插入重复行,则会引发唯一性冲突错误;MERGE不会尝试通过重新开始MATCHED条件评估来避免此类错误。

不同方法的一个较小(通常不重要)的副作用是MERGE通常不会烧毁序列号。(它仍然可以,就像引发异常时一样......)

如果您没有并发写入负载,或者所讨论的问题对您来说不重要,那么MERGE可以作为替代方案。INSERT ... ON CONFLICT DO NOTHING(那么你可能一开始就不需要。) Rudi添加了一个带有代码示例的答案。

您的问题关于INSERT ... ON CONFLICT DO NOTHING

为什么Seq值不断增加?

原因是在检查重复项(尝试输入索引元组)之前DEFAULT应用值(以及触发器和任何可能更改行值的其他内容) 。数字旨在防御并发负载下的竞争条件。一旦数字增加,底层证券就不会“收回”数字。还有其他可能会烧毁序列号的情况。因此,序列号存在差距是可以预料的。只要你不以巨大的速度燃烧数字,这应该不是问题。serialSEQUENCE

有没有办法只有插入成功后才增加Seq值?

并非没有(或多或少)严重影响性能,例如使用SERIALIZABLE事务隔离或手动锁定策略。这就是为什么ON CONFLICT这就是子句(“UPSERT”)首先存在的

如果您实际上没有对同一个表进行并发写入(您确定吗?),此替代查询将避免烧毁序列号:

INSERT INTO newtable (code)
SELECT '001'
WHERE  NOT EXISTS (SELECT FROM newtable WHERE code = '001';
Run Code Online (Sandbox Code Playgroud)

在非冲突情况下,它的成本稍高,因为它首先检查索引中是否存在,然后实际在表和索引中输入新行。但对于冲突的情况稍微快一些。检查和写入之间有一个很小的窗口,其中竞争条件可能会在并发写入负载下导致问题。这就是我们使用 UPSERT 的时候。

有关的:


Rud*_*zen 4

PostgreSQL 15 附带了 MERGE 命令。使用此命令进行的插入不会刻录序列号。

CREATE TABLE newtable (
  seq INT GENERATED ALWAYS AS IDENTITY,
  code varchar UNIQUE
);
Run Code Online (Sandbox Code Playgroud)

运行以下语句几次,然后将值更改001002并重复。Value002获取分配的下一个可用序列号。

MERGE INTO newtable n
USING (SELECT '001' AS code) AS u
ON (n.code = u.code)
WHEN MATCHED THEN
UPDATE SET
code = u.code
WHEN NOT MATCHED THEN
INSERT (code)
VALUES ('001');         

SELECT * FROM newtable ORDER BY 1;

seq code
1   001
2   002
Run Code Online (Sandbox Code Playgroud)