为啥 PostgreSQL 会中止这个可序列化的计划

Posted

技术标签:

【中文标题】为啥 PostgreSQL 会中止这个可序列化的计划【英文标题】:Why does PostgreSQL abort this serializable schedule为什么 PostgreSQL 会中止这个可序列化的计划 【发布时间】:2018-11-21 06:11:50 【问题描述】:

理论表明,一组并发事务是可序列化的,当且仅当它们的并发执行等同于它们可能的串行执行之一。

现在事务T1和T2的以下并发执行是可序列化的,因为它相当于串行执行“T1 then T2”

T1: r1x   w1y  c1
T2:    w2x   c2

(i.e., T1 reads x, T2 writes x, T1 writes y, T2 commits, and finally, T1 commits)

但是,在 PostgreSQL 10.4 中尝试时,如下所示:

T1: begin
T1: set transaction isolation level serializable;
T2: begin
T2: set transaction isolation level serializable;
T2: update variables set value = value + 1 where name = 'x'
T1: update variables set value = value + 1 where name = 'y'
T2: commit
T1: commit

当此事务尝试提交时,数据库中止 T1。为什么?

【问题讨论】:

【参考方案1】:

PostgreSQL 使用启发式方法来确定是否中止可序列化事务,因为它太难准确了。因此,即使有等效的串行执行(误报),也可能会中止事务。

但我怀疑在这种情况下有不同的原因。如果您查看执行计划,您可能会看到顺序扫描。现在顺序扫描读取所有行,因此 T2 在更新期间读取了y

可序列化事务的行为取决于选择的执行计划!

【讨论】:

我要去看看那个 Laurenz。哦,天哪,这太可怕了,因为在这种情况下,我们不仅需要分析 SQL 代码,还需要分析它的执行方式以推断可能的并发问题!管理并发和性能调整变得更加复杂...... 嗯...执行计划是否会在运行时发生变化,即使 SQL 没有更改,因此数据库是否更改了其提交/中止决策? 好的,确认 T2 在x 上的更新执行了explain update ... where name = 'x' 报告的seq 扫描。我什至在name 上创建了一个索引,并且仍然是一个 seq 扫描。我了解到 PostgreSQL 的查询计划在运行时也会根据表统计信息而有所不同(它仅在有利时才使用索引)。现在,这是否意味着没有办法覆盖 PostgreSQL 行为来避免像这样的 seq 扫描,从而减少不必要的中止?或者这是我们开发人员不应该关心的事情,因为我们无能为力让它表现得更好? PostgreSQL 执行查询的内部方式会影响事务的结果似乎很奇怪。我这样说是因为在应用程序级别 T2 不关心 y 的行,因此 PostgreSQL 内部检查该行的事实不应该影响 T2 上的提交/中止决定。我注意到,正如 PostgreSQL 所做的那样,它在这种情况下打破了 T1 和 T2 的可序列化性...... 我认为这个例子表明 SERIALIZABLE 隔离并不总是最好的选择。我什至开始怀疑它是否是一个好的选择(在某些情况下它可能有用)。例如,在这种特殊情况下,READ COMMITTED 效果更好。这可能是 SERIALIZABLE 在 PostgreSQL 中不是默认值的原因。同样,这可能是 Oracle 不支持真正序列化的一个原因,并且可能总有另一种解决方案不需要它(例如应用程序级别的序列化)。

以上是关于为啥 PostgreSQL 会中止这个可序列化的计划的主要内容,如果未能解决你的问题,请参考以下文章

Django:“TypeError:[] 不是 JSON 可序列化的”为啥?

SQLAlchemy,以惯用的 Python 方式进行可序列化事务隔离和重试

为啥我会在这个程序中“中止”? [复制]

Postgresql 可序列化事务未按预期工作

为啥这个类不是 JSON 可序列化的? [复制]

为啥我收到错误“中止 loadExtensionAsync”