(当我尝试重新发现有关该主题的文章时遇到了这个问题。现在,我已经找到了它,我将其发布在这里,以防其他人寻求当前选择答案的替代选项– row_number()
)
我有相同的用例。对于插入到SaaS中特定项目中的每条记录,我们需要一个唯一的递增编号,该编号可以在并发INSERT
s的情况下生成,并且理想情况下是无间隙的。
本文介绍了一个不错的解决方案,为方便起见,我将在此总结。
- 有一个单独的表作为提供下一个值的计数器。它将有两列,
document_id
和counter
。counter
将DEFAULT 0
另外,如果你已经有了一个document
组织了所有版本,一个实体counter
可以有加。
BEFORE INSERT
向该document_versions
表添加一个触发器,该触发器可以自动使计数器(UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
)递增,然后将其设置NEW.version
为该计数器值。
或者,您可能可以在应用程序层使用CTE进行此操作(尽管出于一致性考虑,我更喜欢将其作为触发器):
WITH version AS (
UPDATE document_revision_counters
SET counter = counter + 1
WHERE document_id = 1
RETURNING counter
)
INSERT
INTO document_revisions (document_id, rev, other_data)
SELECT 1, version.counter, 'some other data'
FROM "version";
从原理上讲,这与您最初尝试解决该问题的方式类似,不同之处在于,通过在单个语句中修改计数器行,它会阻止读取过时的值,直到INSERT
提交为止。
这是psql
展示此操作的记录:
scratch=# CREATE TABLE document_revisions (document_id integer, rev integer, other_data text, PRIMARY KEY (document_id, rev));
CREATE TABLE
scratch=# CREATE TABLE document_revision_counters (document_id integer PRIMARY KEY, counter integer DEFAULT 0);
CREATE TABLE
scratch=# WITH version AS (
INSERT INTO document_revision_counters (document_id) VALUES (2)
ON CONFLICT (document_id)
DO UPDATE SET counter = document_revision_counters.counter + 1
RETURNING counter;
)
INSERT
INTO document_revisions (document_id, rev, other_data)
SELECT 2, version.counter, 'doc 1 v1'
FROM "version";
INSERT 0 1
scratch=# WITH version AS (
INSERT INTO document_revision_counters (document_id) VALUES (2)
ON CONFLICT (document_id)
DO UPDATE SET counter = document_revision_counters.counter + 1
RETURNING counter;
)
INSERT
INTO document_revisions (document_id, rev, other_data)
SELECT 2, version.counter, 'doc 1 v2'
FROM "version";
INSERT 0 1
scratch=# WITH version AS (
INSERT INTO document_revision_counters (document_id) VALUES (2)
ON CONFLICT (document_id)
DO UPDATE SET counter = document_revision_counters.counter + 1
RETURNING counter;
)
INSERT
INTO document_revisions (document_id, rev, other_data)
SELECT 2, version.counter, 'doc 2 v1'
FROM "version";
INSERT 0 1
scratch=# SELECT * FROM document_revisions;
document_id | rev | other_data
-------------+-----+------------
2 | 1 | doc 1 v1
2 | 2 | doc 1 v2
2 | 1 | doc 2 v1
(3 rows)
如您所见,您必须注意INSERT
s的发生方式,因此要注意触发器版本,如下所示:
CREATE OR REPLACE FUNCTION set_doc_revision()
RETURNS TRIGGER AS $$ BEGIN
WITH version AS (
INSERT INTO document_revision_counters (document_id, counter) VALUES (NEW.document_id, 1)
ON CONFLICT (document_id)
DO UPDATE SET counter = document_revision_counters.counter + 1
RETURNING counter
)
SELECT INTO NEW.rev counter FROM version; RETURN NEW; END;
$$ LANGUAGE 'plpgsql';
CREATE TRIGGER set_doc_revision BEFORE INSERT ON document_revisions
FOR EACH ROW EXECUTE PROCEDURE set_doc_revision();
INSERT
面对INSERT
来自任意来源的,这将使s更加简单明了,数据的完整性也更加可靠:
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'baz');
INSERT 0 1
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'foo');
INSERT 0 1
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, 'bar');
INSERT 0 1
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (42, 'meaning of life');
INSERT 0 1
scratch=# SELECT * FROM document_revisions;
document_id | rev | other_data
-------------+-----+-----------------
1 | 1 | baz
1 | 2 | foo
1 | 3 | bar
42 | 1 | meaning of life
(4 rows)