正确实现子id的方法?

时间:2015-01-20 21:46:01

标签: postgresql race-condition

假设我有一个具有以下结构的表:

CREATE TABLE test(
  id SERIAL,
  type VARCHAR(10),
  sub_id INTEGER,
  UNIQUE (type, sub_id)
)

我希望“sub_id”列成为特定“类型”中的计数器。例如:

id  |  type  | sub_id
---------------------
1   |  'foo' | 1
--------------------
2   |  'foo' | 2
--------------------
3   |  'foo' | 3
--------------------
4   |  'bar' | 1
--------------------
5   |  'bar' | 2

要使用以下查询插入新行:

INSERT INTO test(type,sub_id)
SELECT 'foo', MAX(sub_id)+1
FROM test
WHERE type='foo'

但后来我发现这个查询容易受到竞争条件的影响。 将子计数器保留在类型中的正确方法是什么?

2 个答案:

答案 0 :(得分:0)

如果事先知道可能类型的数量,我建议使用专用序列:

CREATE SEQUENCE foo_sub_id;
CREATE SEQUENCE bar_sub_id;

我不确定您的实际要求,但另一种选择当然不是实际存储sub_id,而是在需要使用它的任何地方进行计算:

SELECT id, type, row_number() OVER (PARTITION BY type ORDER BY id) sub_id
FROM test

答案 1 :(得分:0)

如果将sub_ids放在表格中并按顺序编号而没有间隙是必不可少的,那么使用序列是不会这样做的,
因为序列可能有差距。因此,您必须锁定整个表格,然后使用原始方法。

BEGIN Transaction;
LOCK TABLE test;
INSERT INTO test(type,sub_id)
SELECT 'foo', MAX(sub_id)+1
FROM test
WHERE type='foo';
COMMIT;

如果允许有差距,只需使用id列。

如果只是为了"看起来" Lukas Eder提出的窗口查询对我来说也很好看:

SELECT id, type, 
  row_number() OVER (PARTITION BY type ORDER BY id) AS sub_id 
FROM test