23

Serializable Isolation Level的 PostgreSQL 手册页指出:

[像] 可重复读取级别,使用此级别的应用程序必须准备好由于序列化失败而重试事务。

在可重复读取或可序列化级别遇到序列化失败的条件是什么?

我试图通过两个运行实例来引发序列化失败psql,但即使一个事务由一个实例提交,另一个实例在可序列化级别事务中,而另一个实例已成功提交其更改。两者都只是将记录插入到表中,所以也许我需要尝试更复杂的东西。

基本上我试图了解在序列化失败的情况下会发生什么以及序列化失败是如何出现的。

4

4 回答 4

11

序列化失败有很多可能的原因。从技术上讲,两个事务之间的死锁是序列化失败的一种形式,如果对模式(数据库结构)进行并发修改,则可能在任何隔离级别发生。由于您询问的是 PostgreSQL,您应该知道在 PostgreSQL 中,这种类型的序列化失败会从其他类型的序列化失败中获得一个单独的 SQLSTATE:“40P01”。所有其他序列化失败都返回“40001”。这个答案的其余部分将集中在 PostgreSQL 中的这些非死锁品种上。

在活动副本(“热备用”)之外,这些只能发生在两个更严格的隔离级别:REPEATABLE READ 和 SERIALIZABLE。在 REPEATABLE READ 级别,这些只会因为写冲突而发生——两个并发事务尝试更新或删除相同的(现有的)行。进行尝试的第一个事务将锁定该行并继续。如果它提交,第二个事务会因序列化失败而失败。如果第一个事务由于任何原因回滚,则被阻止的事务将被释放以继续进行,并将获取它自己的行锁。这种行为与事务期间的单个“快照”相结合,也称为快照隔离。

在 PostgreSQL 版本 9.1 之前,SERIALIZABLE 事务的工作方式完全相同。从 9.1 开始,PostgreSQL 使用一种称为 Serializable Snapshot Isolation 的新技术来确保任何一组可序列化事务的行为与这些事务的某些串行(一次一次)执行完全一致。在 9.1 中使用 SERIALIZABLE 事务时,您的应用程序应该为除 ROLLBACK 之外的任何语句的序列化失败做好准备——即使在只读事务中甚至在 COMMIT 中也是如此。有关更多信息,请参阅http://www.postgresql.org/docs/current/interactive/transaction-iso.html上的 PostgreSQL 文档页面或 Wiki 页面,其中提供了在新的、更严格的隔离级别中如何发生序列化失败的示例在http://wiki.postgresql.org/wiki/SSI

如果您使用 Hot Standby 功能,如果存在长时间运行的查询(维护数据的稳定视图需要数据库阻止复制太长时间),您可能会在只读副本上获得序列化失败。有一些配置设置可让您平衡复制数据的“新鲜度”与长时间运行查询的容差。一些用户可能希望创建多个副本,以便他们可以拥有最新的数据(甚至可能选择同步复制),同时允许另一个根据需要滞后以服务于长时间运行的查询。

编辑以提供另一个链接:在第 38 届超大型数据库国际会议上发表的题为Serializable Snapshot Isolation in PostgreSQL的论文提供了比其他链接更多的细节和观点,以及对为此实施奠定基础的论文的参考。

于 2012-04-02T22:21:47.503 回答
9

对于REPEATABLE READ此示例将执行以下操作:

准备阶段:

psql-0> CREATE TABLE foo(key int primary key, val int);
CREATE TABLE
psql-0> INSERT INTO foo VALUES(1, 42);

现在密切关注 psql- X部分,指示动作的交错:

psql-1> BEGIN ISOLATION LEVEL REPEATABLE READ;
psql-1> UPDATE foo SET val=val+1;
UPDATE 1
psql-2> BEGIN ISOLATION LEVEL REPEATABLE READ;
psql-2> UPDATE foo SET val=val+1;
*** no output, transaction blocked ***

psql-1> COMMIT;

psql-2> *** unblocks ***
ERROR:  could not serialize access due to concurrent update

PostgreSQL 9.1 的文档中有一个示例,SERIALIZABLE从这里应该没有问题。

于 2011-10-09T18:26:52.300 回答
5

如果这对任何人都有帮助,这里是 Freenode 上#postgresql 的成绩单:

[14:36] <dtrebbien> 遇到序列化失败的条件是什么?

[14:36] <dtrebbien> ^ 遇到序列化失败的条件是什么?

[14:37] <dtrebbien> 是否有 PostgreSQL 开发人员可以识别序列化失败的条件?

[14:38] <peerce> http://www.postgresql.org/docs/current/static/transaction-iso.html#XACT-SERIALIZABLE

[14:43] <dtrebbien> “任何一组并发可序列化事务都将具有与一次运行一个相同的效果”

[14:44] <dtrebbien> PostgreSQL 引擎遵循什么规则?

[14:44] <dtrebbien> 即如果更改了一行,是否会触发失败?

[14:44] <johto> 9.1 中的可序列化隔离模式真的很复杂

[14:45] <dtrebbien> 我想。

[14:45] <dtrebbien> 我也读到,Serializable 级别以某种方式“固定”

[14:45] <RhodiumToad> dtrebbien:在 9.1 之前,基本规则是,如果事务尝试更改其当前值不可见的行,那就是失败

[14:46] <dtrebbien> RhodiumToad:这很有趣。

[14:46] <dtrebbien> 另外,访问一个值,对吧?

[14:46] <selenamarie> dtrebbien:除了别人说的,其背后的基本前提是检测依赖循环

[14:47] <dtrebbien> 哦。

[14:50] <dtrebbien> 公平地说,在 9.1 中,触发隔离级别的规则已经变得更加复杂,以基本上减少“误报”序列化异常?

[14:51] <johto> 他们变得复杂,因为更简单的规则没有捕捉到所有的序列化异常

[14:51] <dtrebbien>啊!我懂了。

[14:51] <dtrebbien> 这就是发行说明说“已修复”的原因。

[14:52] <RhodiumToad> dtrebbien:访问不可见值不是错误,因为它只是获得了在快照时可见的值。

[14:53] <RhodiumToad> dtrebbien:只读可序列化查询仅在其快照时间看到数据库的静态状态。

[14:54] <RhodiumToad> dtrebbien:除了 TRUNCATE 的小问题外,所有序列化问题都涉及读/写查询

[15:03] <dtrebbien> RhodiumToad、johto、selenamarie 和 peerce:您介意我将这次谈话的记录发布到 Stack Overflow 吗?

[15:07] <selenamarie> dtrebbien:当然 :)

[15:07] <dtrebbien> 我不知道它是否会帮助任何人。它可能。

[15:08] <selenamarie> dtrebbien:我在这里发布了 Kevin Grittner 的演讲笔记:http: //www.chesnok.com/daily/2011/03/24/raw-notes-from-kevin-grittners- ssi 通话/

于 2011-10-09T19:33:29.700 回答
0

更多示例

除了AH 的回答中提供的示例之外,这里还有一些可能感兴趣的最小示例。我正在使用这个测试设置来方便地在 PostgreSQL 13.5 上运行它们。

常用设置:

0: CREATE TABLE "MyInt"(i INTEGER);
0: INSERT INTO foo VALUES(0);

我想指出的第一件事是,即使是只读语句SELECT也很重要。

例如,此示例不会引发任何错误:

0: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
0: UPDATE "MyInt" SET i = 1
0: COMMIT
1: UPDATE "MyInt" SET i = 2
1: COMMIT

此示例与上述答案中的示例之间的唯一区别在于,该答案中的示例是在这一个线程中,线程 1 在线程 0UPDATE之后执行COMMIT。 TODO 为什么这很重要?

但是,如果我们SELECT在线程 0 提交之前简单地从线程 1 添加一个,如下所示:

0: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
1: BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ
0: UPDATE "MyInt" SET i = 1
1: SELECT * FROM "MyInt"
0: COMMIT
1: UPDATE "MyInt" SET i = 2
1: COMMIT

然后最后一个UPDATE爆炸:

could not serialize access due to concurrent update

TODO 准确了解SELECTPostgreSQL 为何如此重要/如何跟踪它。似乎发生的是 PostgreSQL 考虑了类型:

如果客户有这样那样的信息,这样的数据库序列化属性可能会被违反吗?

这要求它还跟踪SELECT语句。

仅在SERIALIZABLE

这是另一个作用于两个不同行的有趣示例。此示例仅在 上炸毁SERIALIZABLE,但在REPEATABLE READ(TODO 为什么)上没有炸毁:

INSERT INTO "MyInt" VALUES (0)
INSERT INTO "MyInt" VALUES (10)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE
UPDATE "MyInt" SET i = 1 WHERE i = 0
SELECT * FROM "MyInt"
COMMIT
UPDATE "MyInt" SET i = 11 WHERE i = 10
COMMIT

这种情况下的错误信息是:

could not serialize access due to read/write dependencies among transactions

不幸的是,在盯着https://www.postgresql.org/docs/13/transaction-iso.html#XACT-REPEATABLE-READ几个小时后,我仍然无法准确解释为什么其中一些会爆炸而其他人不会,但我觉得这些示例无论如何都足以引起人们的兴趣,也许有人可以澄清 PostgreSQL 在未来的编辑/评论中采取的确切步骤顺序。

测试示例

不出所料,许多此类示例的另一个来源是src/test/isolation下的树内测试,它们也非常易读。一个 grepcould not serialize

src/test/isolation/specs/*.spec下的文件确定要执行哪些步骤,src/test/isolation/expected/*.out下的相应文件包含确切的预期psql原始输出。因此,我们只需阅读.out文件即可查看许多完整的失败示例。

例如src/test/isolation/expected/insert-conflict-do-nothing-2.out包含:

starting permutation: beginrr1 beginrr2 donothing1 donothing2 c1 c2 show
step beginrr1: BEGIN ISOLATION LEVEL REPEATABLE READ;
step beginrr2: BEGIN ISOLATION LEVEL REPEATABLE READ;
step donothing1: INSERT INTO ints(key, val) VALUES(1, 'donothing1') ON CONFLICT DO NOTHING;
step donothing2: INSERT INTO ints(key, val) VALUES(1, 'donothing2'), (1, 'donothing3') ON CONFLICT DO NOTHING; <waiting ...>
step c1: COMMIT;
step donothing2: <... completed>
ERROR:  could not serialize access due to concurrent update
step c2: COMMIT;
step show: SELECT * FROM ints;
key|val
---+----------
  1|donothing1
(1 row)

唯一没有清楚显示的是表创建语句,我们可以在相应的.spec文件src/test/isolation/specs/insert-conflict-do-nothing-2.spec 中看到

  CREATE TABLE ints (key int, val text, PRIMARY KEY (key) INCLUDE (val));

从中我们了解到,这INSERT INTO ON CONFLICT DO NOTHING也可能导致序列化失败。

Some of the tests also link to a paper: https://www.cs.umb.edu/~poneil/ROAnom.pdf A Read-Only Transaction Anomaly Under Snapshot Isolation. You know a software is serious when the bug reports are addressed by papers.

于 2021-12-02T11:33:15.477 回答