二月 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:显式锁定。