Jul*_*ier 11 postgresql locking
我需要在 document_revisions 表中保留一个唯一的(每行)修订号,其中修订号的范围仅限于文档,因此它不是整个表唯一的,仅适用于相关文档。
我最初想出了类似的东西:
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
Run Code Online (Sandbox Code Playgroud)
但是有一个竞争条件!
我试图用 解决它pg_advisory_lock
,但文档有点稀缺,我不完全理解它,我不想错误地锁定某些东西。
以下是可以接受的,还是我做错了,或者有更好的解决方案?
SELECT pg_advisory_lock(123);
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(123);
Run Code Online (Sandbox Code Playgroud)
我不应该为给定的操作 (key2) 锁定文档行 (key1) 吗?所以这将是正确的解决方案:
SELECT pg_advisory_lock(id, 1) FROM documents WHERE id = 123;
current_rev = SELECT MAX(rev) FROM document_revisions WHERE document_id = 123;
INSERT INTO document_revisions(rev) VALUES(current_rev + 1);
SELECT pg_advisory_unlock(id, 1) FROM documents WHERE id = 123;
Run Code Online (Sandbox Code Playgroud)
也许我不习惯 PostgreSQL 并且 SERIAL 可以限定范围,或者可能是一个序列并且nextval()
可以更好地完成工作?
(我在尝试重新发现有关该主题的文章时遇到了这个问题。现在我已经找到了它,我将其发布在这里,以防其他人寻求当前所选答案的替代选项\ xe2\x80\x94windowing 与row_number()
)
我有同样的用例。对于插入 SaaS 中特定项目的每条记录,我们需要一个唯一的、递增的数字,该数字可以在并发情况下生成,并且理想情况下INSERT
是无缝的。
本文描述了一个很好的解决方案,为了方便和后代,我将在这里总结一下。
\n\ndocument_id
和counter
。counter
或者DEFAULT 0
,如果您已经有一个document
对所有版本进行分组的实体,则counter
可以在其中添加 a 。BEFORE INSERT
触发器document_versions
,该触发器自动递增计数器 ( UPDATE document_revision_counters SET counter = counter + 1 WHERE document_id = ? RETURNING counter
),然后设置NEW.version
为该计数器值。或者,您可以使用 CTE 在应用程序层执行此操作(尽管出于一致性考虑,我更喜欢将其作为触发器):
\n\nWITH version AS (\n UPDATE document_revision_counters\n SET counter = counter + 1 \n WHERE document_id = 1\n RETURNING counter\n)\n\nINSERT \n INTO document_revisions (document_id, rev, other_data)\n SELECT 1, version.counter, \'some other data\'\n FROM "version";\n
Run Code Online (Sandbox Code Playgroud)\n\n原则上,这与您最初尝试解决该问题的方式类似,只不过通过修改单个语句中的计数器行,它会阻止对过时值的读取,直到提交为止INSERT
。
这是psql
显示此操作的记录:
scratch=# CREATE TABLE document_revisions (document_id integer, rev integer, other_data text, PRIMARY KEY (document_id, rev));\nCREATE TABLE\n\nscratch=# CREATE TABLE document_revision_counters (document_id integer PRIMARY KEY, counter integer DEFAULT 0);\nCREATE TABLE\n\nscratch=# WITH version AS (\n INSERT INTO document_revision_counters (document_id) VALUES (2)\n ON CONFLICT (document_id)\n DO UPDATE SET counter = document_revision_counters.counter + 1\n RETURNING counter;\n )\n INSERT \n INTO document_revisions (document_id, rev, other_data)\n SELECT 2, version.counter, \'doc 1 v1\'\n FROM "version";\nINSERT 0 1\n\nscratch=# WITH version AS (\n INSERT INTO document_revision_counters (document_id) VALUES (2)\n ON CONFLICT (document_id)\n DO UPDATE SET counter = document_revision_counters.counter + 1\n RETURNING counter;\n )\n INSERT \n INTO document_revisions (document_id, rev, other_data)\n SELECT 2, version.counter, \'doc 1 v2\'\n FROM "version";\nINSERT 0 1\n\nscratch=# WITH version AS (\n INSERT INTO document_revision_counters (document_id) VALUES (2)\n ON CONFLICT (document_id)\n DO UPDATE SET counter = document_revision_counters.counter + 1\n RETURNING counter;\n )\n INSERT \n INTO document_revisions (document_id, rev, other_data)\n SELECT 2, version.counter, \'doc 2 v1\'\n FROM "version";\nINSERT 0 1\n\nscratch=# SELECT * FROM document_revisions;\n document_id | rev | other_data \n-------------+-----+------------\n 2 | 1 | doc 1 v1\n 2 | 2 | doc 1 v2\n 2 | 1 | doc 2 v1\n(3 rows)\n
Run Code Online (Sandbox Code Playgroud)\n\n正如您所看到的,您必须小心如何INSERT
发生,因此触发器版本如下所示:
CREATE OR REPLACE FUNCTION set_doc_revision()\nRETURNS TRIGGER AS $$ BEGIN\n WITH version AS (\n INSERT INTO document_revision_counters (document_id, counter) VALUES (NEW.document_id, 1)\n ON CONFLICT (document_id)\n DO UPDATE SET counter = document_revision_counters.counter + 1\n RETURNING counter\n )\n\n SELECT INTO NEW.rev counter FROM version; RETURN NEW; END;\n$$ LANGUAGE \'plpgsql\';\n\nCREATE TRIGGER set_doc_revision BEFORE INSERT ON document_revisions\nFOR EACH ROW EXECUTE PROCEDURE set_doc_revision();\n
Run Code Online (Sandbox Code Playgroud)\n\n这使得INSERT
s 更加直接,并且数据的完整性在面对INSERT
来自任意来源的 s 时更加稳健:
scratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, \'baz\');\nINSERT 0 1\n\nscratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, \'foo\');\nINSERT 0 1\n\nscratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (1, \'bar\');\nINSERT 0 1\n\nscratch=# INSERT INTO document_revisions (document_id, other_data) VALUES (42, \'meaning of life\');\nINSERT 0 1\n\nscratch=# SELECT * FROM document_revisions;\n document_id | rev | other_data \n-------------+-----+-----------------\n 1 | 1 | baz\n 1 | 2 | foo\n 1 | 3 | bar\n 42 | 1 | meaning of life\n(4 rows)\n
Run Code Online (Sandbox Code Playgroud)\n
假设您将文档的所有修订版本存储在一个表中,一种方法是不存储修订版本号,而是根据表中存储的修订版本数量来计算它。
从本质上讲,它是一个派生值,而不是您需要存储的东西。
窗口函数可用于计算修订号,例如
row_number() over (partition by document_id order by <change_date>)
Run Code Online (Sandbox Code Playgroud)
你需要一个类似的列change_date
来跟踪修订的顺序。
另一方面,如果您只是revision
作为文档的属性并且它指示“文档已更改了多少次”,那么我会采用乐观锁定方法,例如:
update documents
set revision = revision + 1
where document_id = <id> and revision = <old_revision>;
Run Code Online (Sandbox Code Playgroud)
如果这更新了 0 行,则说明发生了中间更新,您需要通知用户这一点。
一般来说,尝试使您的解决方案尽可能简单。在这种情况下通过
update
语句而不是select
后跟insert
orupdate
归档时间: |
|
查看次数: |
19012 次 |
最近记录: |