PostgreSQL 教程: 检查死锁

八月 1, 2024

摘要:在本教程中,您将学习如何检查 PostgreSQL 中的死锁。

目录

介绍

许多人可能看到过 PostgreSQL 发出这样的错误消息:"ERROR: deadlock detected"。但这到底意味着什么?我们如何防止死锁,如何重现问题?让我们来深入了解下 PostgreSQL 的锁,并了解死锁和 deadlock_timeout 的真正含义。

死锁是一个重要的问题,每个数据库都可能发生死锁。基本上,如果两个事务必须相互等待,就会发生死锁。死锁通常与阻塞的查询有关,但也略有不同,它会导致查询被取消,因为它给另一个查询造成了死锁。

示例

让我们来看一个简单的例子,首先开始设置一个小的测试用例:

CREATE TABLE test1 (id int, num int);

INSERT INTO test1 VALUES (1, 100), (2, 200);

重现一个死锁的最简单方法是,执行以下操作:

--- session 1
BEGIN;
UPDATE test1 SET num = num + 10 WHERE id = 1;

--- session 2
BEGIN;
UPDATE test1 SET num = num + 10 WHERE id = 2;
UPDATE test1 SET num = num + 10 WHERE id = 1; --- this will block waiting for session 1 to finish

--- session 1
UPDATE test1 SET num = num + 10 WHERE id = 2; --- this can never finish as it deadlocks against session 2

在经过deadlock_timeout设置的时间之后,你会在 PostgreSQL 日志中看到死锁的问题。在这种情况下,PostgreSQL 发现此操作永远不会完成,并向日志中发出以下内容:

ERROR: deadlock detected
DETAIL: Process 70725 waits for ShareLock on transaction 891717; blocked by process 70713.
Process 70713 waits for ShareLock on transaction 891718; blocked by process 70725.
HINT: See server log for query details.
CONTEXT: while updating tuple (0,1) in relation "test1"

PostgreSQL 甚至还善意地告诉我们是哪一行导致了冲突。在此处的示例中,问题的根源是在一个元组 (0,1)。您在此处可以看到的是 ctid,它是表中行的唯一标识符。它告诉我们表内一个行的物理位置。在此示例中,它是第一个块 (0) 中的第一行。

你可能会认为,在生产环境中永远不会发生死锁,但不幸的事实是,在大量使用 ORM 框架时,可能会隐藏产生死锁的循环依赖情况,当你使用到复杂的事务时,这肯定是需要注意的。

查询 pg_stat_database 视图

您可以在视图pg_stat_database中看到数据库级别的统计信息,其中有一个 deadlocks 列。你应该多留意这个列,因为它会告诉你,你的数据库是否有很多死锁,这将不可避免地减慢操作速度。

当两个或多个事务都锁定了资源,并试图获取已被其他事务锁定的资源时,就会发生死锁。在正常情况下,事务 A 锁定资源 A,事务 B 锁定资源 B。在死锁情况下,事务 A 将尝试锁定资源 B。这反过来又使其进入等待模式,因为事务 B 已经锁定了资源 B。这样,两个事务都会锁定彼此的资源。当发生死锁时,PostgreSQL 会取消其中一个涉及的事务,并将其从等待队列中删除,以让其他事务继续其工作。

通常,死锁的出现,意外着您的应用程序存在设计错误,一般的建议是,修复导致死锁的应用程序逻辑。一个推荐的实践是在 postgresql.conf 中启用 log_lock_waits 选项,重新加载配置,并不时地检查日志。当发生死锁时,将会在日志中记录有关冲突查询的额外信息。

我们可以使用下面的查询,来检查死锁:

SELECT deadlocks FROM pg_stat_database
  WHERE datname = current_database();

一旦你发现了很多死锁,你就可以找到那些导致死锁的查询,并优化它们。

了解更多

PostgreSQL 监控