postgres 中的序列号正在增加,即使我添加了冲突什么也不做

serial in postgres is being increased even though I added on conflict do nothing

我正在使用 Postgres 9.5 并在此处看到一些有线的东西。

我有一个 cron 作业 运行 每 5 分钟触发一个 sql 语句,如果不存在则添加记录列表。

INSERT INTO 
   sometable (customer, balance)
VALUES
    (:customer, :balance)
ON CONFLICT (customer) DO NOTHING

sometable.customer是主键(文本)

某表结构为:
编号:序列号
客户:文本
余额:bigint

现在好像每次运行此作业时,id 字段都会默默地递增 +1。所以下一次,我真的添加了一个字段,它比我上次的值高出数千个数字。我认为此查询会检查冲突,如果是,则什么也不做,但目前它似乎试图插入记录,增加 id 然后停止。

有什么建议吗?

如@a_horse_with_no_name所说,即设计。序列类型字段是通过序列在后台实现的,并且出于显而易见的原因,一旦您从序列中获得了新值,就无法回滚最后一个值。想象一下以下场景:

  • 序列在 n
  • A​​ 需要一个新值:得到 n+1
  • 在并发事务中 B 需要一个新值:得到 n+2
  • 出于任何原因 A 回滚其事务 - 您觉得重置序列是否安全?

这就是为什么序列(和序列字段)只记录在回滚事务的情况下返回值中可能会出现漏洞的原因。只保证唯一性。

正如@a_horse_with_no_name 和@Serge Ballesta 所说,即使 INSERT 失败,连续剧总是递增的。

你可以试试把"rollback"序列值改成最大id使用,改变对应序列:

SELECT setval('sometable_id_seq', MAX(id), true) FROM sometable;

您觉得这很奇怪的原因是您认为计数器的增量是插入操作的一部分,因此 "DO NOTHING" 应该表示 "don't increment anything"。你想象的是:

  1. 根据约束检查要插入的值
  2. 如果检测到重复项,则中止
  3. 增量序列
  4. 插入数据

但实际上,增量必须发生在尝试插入之前。 Postgres 中的 SERIAL 列被实现为 DEFAULT,它在绑定 SEQUENCE 上执行 nextval() 函数。在 DBMS 可以对数据做任何事情之前,它必须有一组完整的列,所以操作顺序是这样的:

  1. 解析默认值,包括递增序列
  2. 根据约束检查要插入的值
  3. 如果检测到重复项,则中止
  4. 插入数据

如果重复键在自增字段本身,这可以直观地看出:

CREATE TABLE foo ( id SERIAL NOT NULL PRIMARY KEY, bar text );
-- Insert row 1
INSERT INTO foo ( bar ) VALUES ( 'test' );
-- Reset the sequence
SELECT setval(pg_get_serial_sequence('foo', 'id'), 0, true);
-- Attempt to insert row 1 again
INSERT INTO foo ( bar ) VALUES ( 'test 2' )
     ON CONFLICT (id) DO NOTHING;

显然,如果不增加序列就无法知道是否存在冲突,因此 "do nothing" 必须在 之后 增加。

好吧,有一种技术可以让你做那样的事情。他们称之为插入互斥体。它是旧的旧的,但它有效。

https://www.percona.com/blog/2011/11/29/avoiding-auto-increment-holes-on-innodb-with-insert-ignore/

一般的想法是你做 INSERT SELECT 如果你的值是重复的 SELECT 不会 return 任何结果当然会阻止 INSERT 并且索引是没有递增。有点令人难以置信,但完全有效且性能良好。

这当然会完全忽略 ON DUPLICATE 但人们会重新控制索引。