事务
事务是构建数据库驱动的应用程序的基本组件。当一个事务开始后,它可以读取和修改数据库中的数据。当它修改数据时,它会获得对它正在更改的资源(如行或表)的锁定。该锁定可防止其他事务同时修改相同的资源,从而确保以一致且可预测的方式对数据进行更改。
但是,如果两个事务同时尝试访问或修改同一资源,则它们最终可能会陷入死锁情况,即在另一个事务释放其锁定之前,任何一个事务都无法继续进行。
最佳实践
为了避免死锁,在设计和实现事务时,遵循一些最佳实践非常重要:
• 避免长时间锁定资源:长事务和长时间运行的锁定可能会增加死锁的可能性。尽量保持事务简短,避免长时间持有锁。
• 优化查询:一个事务的查询应该是最优的,并尝试仅对必需的行执行操作。这样可以减少事务锁定的行数,并允许其他事务访问这些行。
• 确保更新以一致的顺序进行:确保相关资源的更新以一致的顺序进行。
• 避免显式锁定和表级锁:尽可能避免使用显式锁定或表级锁,因为表级锁会限制对整个表的任何操作,并阻止任何其他事务对其执行任何查询。
解决死锁
如果遇到死锁,解决死锁的第一步是,分析情况并确定所涉及的事务、它们争用的资源以及导致死锁的事件顺序。您可以使用 PostgreSQL 日志文件或查询 pg_stat_activity 系统视图,来收集此信息。您可以使用此信息来识别死锁中涉及的查询和资源。
确定所涉及的查询和资源后,可以检查它们,以确定是否可以根据最佳实践进行任何优化,以减少未来出现死锁的可能性。
PostgreSQL SKIP LOCKED 示例
如果需要从一个表进行 SELECT,并在事务完成之前保护这些行不被更新,则可以指定 FOR UPDATE,但如果某些行被锁定了,则可以指定 SKIP LOCKED 来告诉查询忽略这些行,只对它可以访问的任何行执行操作。
下面是如何使用 “SELECT ... FOR UPDATE SKIP LOCKED“ 的一个示例:
在会话 1 中:
BEGIN;
SELECT * FROM colours;
id | name
----+-------
1 | red
2 | green
3 | blue
(3 rows)
UPDATE colours SET name = 'scarlet' WHERE name = 'red';
在会话 2 中:
BEGIN;
SELECT * FROM colours FOR UPDATE NOWAIT;
ERROR: could not obtain lock on row in relation "colours"
SELECT * FROM colours FOR UPDATE SKIP LOCKED;
id | name
----+-------
2 | green
3 | blue
(2 rows)
在指定 SKIP LOCKED 选项,使用 SELECT ... FOR UPDATE 时,有几件事要记住。
• 应仅在多个事务可能同时尝试锁定相同行的情况下,才使用 SKIP LOCKED,并且需要可以接受某些事务能够跳过锁定的行。
• 如果使用不小心,跳过锁定的行可能会导致数据不一致。请确保应用程序逻辑可以处理跳过某些行的情况。
或者,我们也可以在使用 SELECT ... FOR UPDATE 时,指定 NOWAIT 选项。指定 NOWAIT 选项时,如果无法立即锁定选定的行,则语句将立即报告错误,而不是等待。它要求应用程序中有正确的错误处理逻辑。
总结
在设计实现事务和监控应用程序性能时,通过应用最佳实践,您可以避免和解决 PostgreSQL 数据库中的死锁。在使用诸如 “SELECT ... FOR UPDATE SKIP LOCKED“ 这类技术时,请务必谨慎使用它,并严谨地测试您的应用程序。