作者简介

koichi-suzuki EDB 的数据库架构师。核心专业领域包括分布式事务管理和 PostgreSQL 恢复。在 EDB 工作之前,Koichi 曾在 NTT 集团担任工程负责人,并领导 NTT 集团的 PostgreSQL 社区活动。

译者简介

海能达数据库团队(崔鹏),海能达PostgreSQL高级DBA,PG爱好者。

校对者简介

赵全明 华为云资深数据库内核专家,PG中文社区核心委员。多年数据库管理及研发经验,包括Oracle、MySQL、PostgreSQL,曾参与华为云RDS管控研发、GaussDB多个版本的研发,在SQL引擎、高可用、高可靠方面有着丰富的经验,致力于openGauss及PostgreSQL在金融、政企、互联网等行业的应用与推广。

在我之前关于死锁的博文中,我回顾了什么是死锁、如何发生的、如何检测以及它是如何在 PostgreSQL 中实现的。我还展示了当一个事务中涉及多个数据库时如何发生死锁,并且我们可以使用相同的原理来检测此类事务。我们称这些死锁为“全局死锁”。

全局死锁检测原理

如上一篇博文所示,当一个事务正在等待另一个在远程数据库上运行的事务时,可能会发生全局死锁,如图 1 所示。

CENTER_PostgreSQL_Community

在该图中,在数据库 A (A) 上运行的 T1 正在等待在 A 上运行的 T2,T2在等待在数据库 B (B) 上运行的 T3,T3在等待在 B 上运行的 T4。

类似于单个数据库的情况,我们可以考虑跨越多个数据库的等待图 (WfG)。我们称之为全局等待图(或 G-WfG)。如果存在死锁,则 G-WfG 中也存在循环。G-WfG 和其中的一个循环如图 2 所示。

CENTER_PostgreSQL_Community

此图与图 1 相似,但 T4 正在等待在 A 上运行的 T5,而 T5 正在等待图的原点 T1。这形成了一个循环并表明它是一个死锁。该原理在参考文献 [1][2][3][4] 中有所描述。

全局等待图表示

在单个数据库中保存多个数据库中的所有锁定状态是不切实际的。它会导致过多的网络流量来维持状态,甚至可以很容易地限制本地数据库的性能。我们需要一种不同的方式来表示它。

在这篇文章中,我们介绍了一种称为外部锁的新型锁。这表示一个事务正在等待另一个在不同数据库上运行的事务(远程事务)。锁与来自远程事务的信息相关联,例如标识符和到远程数据库的连接字符串、逻辑事务 ID 和远程事务后端进程索引,如图 3 所示。

CENTER_PostgreSQL_Community

由于本地数据库无法捕获此信息,因此等待事务必须提供此类信息。我们需要提供额外的内部 API。这有助于支持不同的全局事务场景,例如FDW、BDR和其他全局事务场景。我们可以为全局死锁场景的应用程序提供额外的 SQL 函数。

我们可以使用这个外部锁跟踪多个数据库上的等待事务链。通过结合局部等待图,我们可以得到如图 4 所示的全局等待图。

CENTER_PostgreSQL_Community

全局死锁检测

我们可以像局部死锁检测一样启动全局死锁检测。当事务在一定时间范围内(deadlock_timeout)无法获得锁时,我们开始本地死锁检测。当我们发现外部锁时,这意味着该事务正在等待远程事务,我们需要访问远程数据库以继续跟踪全局等待图。这也显示在图 4 中。

如图所示,我们发送在本地数据库中找到的等待图。远程数据库将其视为初始等待图,然后组合以继续跟踪等待图。

当这种跟踪返回到原始数据库并且等待图到达原始事务时,这就是一个死锁。该信息被传递回原始事务并被终止,类似于本地死锁场景。

低级锁的考虑

在本地数据库死锁检测的情况下,它需要所有关联的低级锁称为 LWLock。这会阻止所有其他事务操作,并且对于全局死锁检测来说是不切实际的,因为检测时间比本地检测要长得多。

当死锁检测访问远程数据库时,它会释放所有本地低级锁。当检测到死锁时,本地数据库会检查本地等待图是否稳定。如果等待图不相同,则它不再是死锁的一部分。该检查在全局死锁检测的每个局部部分进行。这最大限度地减少了全局死锁检测对其他事务操作的影响。

本地死锁检测示例

图 5 显示了一个简单的本地死锁场景示例及其在两个事务的终端输出中的检测。事务 1 锁定表 T1,而事务 2 锁定表 T2。然后事务 1 尝试锁定 T2 并等待事务 2。如果事务 2 尝试锁定 T1,则事务 1 和 2 相互等待并发生死锁。当检测到死锁时,事务 2 中止,事务 1 成功。这是预期的行为,没有其他方法可以保持数据库整体运行。图 5 还显示了相应的服务器日志,显示了有关等待图和调用查询的更多详细信息。

CENTER_PostgreSQL_Community

全局死锁检测示例

图 6 显示了一个简单的全局死锁场景。我们需要为此实现一个专用的测试应用程序,因为 psql '\c' 命令会断开当前连接。

CENTER_PostgreSQL_Community

该图还显示了全局死锁导致的终端错误。这与局部死锁情况非常相似。图 7 显示了相应的 PostgreSQL 日志行。也非常相似。所有死锁消息/日志都包含额外的数据库 ID 来描述所涉及的数据库。

下一步

目前,这是基于PostgreSQL 13的研究工作。它从PostgreSQL12开始,移植到PG13非常简单,不费吹灰之力。我希望这可以很容易地移植到未来的 PG 版本和其他衍生产品中。

当使用多个数据库访问的应用程序出现时,准备将其应用到 PostgreSQL。

代码位于 Github 存储库https://github.com/koichi-szk/postgres的 koichi/global_deadlock_detection_13_0 分支。PG13_GDD 分支的https://github.com/koichi-szk/gdd_test中提供了测试代码和其他 SQL 函数。详细的实现信息有点复杂,不适合博客文章。如果您对这项技术的更多细节感兴趣,请访问上述仓库并写邮件给我,#### koichi.suzuki@enterprisedb.com。欢迎任何问题或反馈,我将非常乐意回答和讨论。

[1] P.A.Bernstein et al., "Concurrency Control and Recovery in Database Systems", 1987, Addison Wesley[2] J.Gray et al., "Transaction Processing: Concepts and Techniques", 1993, Morgan Kaufmann[3] H.Garcia-Morina et al., "Database Systems - the Complete Book", 2002, Prentice Hall[4] M.T.Ozsu et al., "Principles of Distributed Database Systems, 4th Ed.", 2020, Springer

PostgreSQL中文社区欢迎广大技术人员投稿

投稿邮箱:press@postgres.cn

CENTER_PostgreSQL_Community

请在登录后发表评论,否则无法保存。
© 2010 PostgreSQL中文社区