PostgreSQL 教程: 应对死锁

二月 1, 2024

摘要:在本教程中,您将学习如何在 PostgreSQL 中处理死锁的问题。

准备工作

在任何数据库管理系统中,由于并发的资源锁定,都可能会发生死锁。数据库引擎负责检测死锁,而应用程序负责防止死锁。PostgreSQL 数据库能够检测死锁;它还为开发人员提供了一些特性,以防止其应用程序代码中的死锁。

处理方案

让我们构造一些简单的死锁情况,我们将看到 PostgreSQL 为处理它们提供的所有选择:

有两个不同的数据库会话,分别执行 SQL 语句,如下所示:

会话 1 会话 2
BEGIN;

UPDATE test SET t=1 WHERE t=1;

UPDATE test SET t=2 WHERE t=2; --
Waiting for the record 2
which is locked in session 2

ERROR: deadlock detected
DETAIL: Process 10417 waits for
ShareLock on transaction 452459;
blocked by process 8913.
Process 8913 waits for ShareLock on
transaction 452458;
blocked by process 10417.

ROLLBACK;
BEGIN;

UPDATE test SET t=2 WHERE t=2;

UPDATE test SET t=1 WHERE t=1; --
Waiting for the record 1
which is locked in session 1

END;

从前面的示例中,在会话 1 中,由于会话 1 和 2 之间的相互锁定,发生了死锁的错误。在前面的详细消息中,它清楚地表明进程 8913 等待由进程 10417 持有的事务,而进程 10417 正在等待由进程 8913 持有的事务。

从前面的示例中可以看出,死锁不会导致任何数据丢失,只会导致事务失败。为了避免这些死锁情况,我们必须使用预锁定技术,如下一节所述。

使用 FOR UPDATE

这是一种尝试避免死锁问题的方法,方法是预先锁定将要在会话中更新的所有必需的记录。要预锁定所有必需的元组,我们必须在 SELECT 语句中使用 FOR UPDATE 子句。让我们来看看我们将如何使用这种方法解决上述问题:

会话 1 会话 2
BEGIN;

SELECT * FROM test WHERE t IN(1, 2)
FOR UPDATE;

UPDATE test SET t=1 WHERE t=1;
UPDATE test SET t=2 WHERE t=2;
END;
BEGIN;

SELECT * FROM test WHERE t IN(1, 2)
FOR UPDATE; --
Waiting for the session to release
the lock on records 1, 2

UPDATE test SET t=2 WHERE t=2;
UPDATE test SET t=1 WHERE t=1;
END;

在前面的示例中,会话 2 的事务将处于等待状态,直到会话 1 的事务完成。在这里,我们只是让两个事务以可序列化的方式运行。这意味着事务不会相互冲突。此方法无法用于处理集合相关操作的 SQL 查询。也就是说,在执行 UNION/INTERSECT/EXCEPT 操作的 SQL 查询中,FOR UPDATE 的使用会受到限制。

咨询锁

PostgreSQL 提供了咨询锁,这是一种外部锁定机制,我们可以在应用程序中强制执行锁定,以实现并发数据访问。让我们来看看我们将如何使用咨询锁避免死锁:

会话 1 会话 2
BEGIN
SELECT pg_advisory_lock(t) FROM test
WHERE t IN (1,2);

UPDATE test SET t=1 WHERE t=1;
UPDATE test SET t=2 WHERE t=2;
SELECT pg_advisory_unlock(t) FROM test
WHERE t IN (1,2);
END;
BEGIN
SELECT pg_advisory_lock(t) FROM test
WHERE t IN (1,2); –Waiting for the
session1 to release lock

UPDATE test SET t=2 WHERE t=2;
UPDATE test SET t=1 WHERE t=1;
SELECT pg_advisory_unlock(t) FROM test
WHERE t IN (1,2);
END;

在前面的示例中,我们使用咨询锁,让事务以可序列化的方式运行。使用咨询锁的唯一缺点是,我们需要一个应用程序,来强制执行锁定和解锁行为。此外,会话级咨询锁不会释放锁,即使事务失败或回滚也是如此。但是,当会话关闭时,该会话中所有关联的咨询锁会自动释放。

注意:有关咨询锁的详细信息,请参阅以下 URL:显式锁定