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"。你想象的是:
- 根据约束检查要插入的值
- 如果检测到重复项,则中止
- 增量序列
- 插入数据
但实际上,增量必须发生在尝试插入之前。 Postgres 中的 SERIAL
列被实现为 DEFAULT
,它在绑定 SEQUENCE
上执行 nextval()
函数。在 DBMS 可以对数据做任何事情之前,它必须有一组完整的列,所以操作顺序是这样的:
- 解析默认值,包括递增序列
- 根据约束检查要插入的值
- 如果检测到重复项,则中止
- 插入数据
如果重复键在自增字段本身,这可以直观地看出:
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
但人们会重新控制索引。
我正在使用 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"。你想象的是:
- 根据约束检查要插入的值
- 如果检测到重复项,则中止
- 增量序列
- 插入数据
但实际上,增量必须发生在尝试插入之前。 Postgres 中的 SERIAL
列被实现为 DEFAULT
,它在绑定 SEQUENCE
上执行 nextval()
函数。在 DBMS 可以对数据做任何事情之前,它必须有一组完整的列,所以操作顺序是这样的:
- 解析默认值,包括递增序列
- 根据约束检查要插入的值
- 如果检测到重复项,则中止
- 插入数据
如果重复键在自增字段本身,这可以直观地看出:
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
但人们会重新控制索引。