我们有一个基于网络的应用程序。应用程序中有时间限制的数据库操作(INSERT和UPDATE)需要更多时间才能完成,因此这个特定的流程已经更改为Java线程,因此它不会等待(阻止)完成整个数据库操作。
我的问题是,如果超过1个用户遇到此特定流,我将面临PostgreSQL抛出的以下错误:
org.postgresql.util.PSQLException: ERROR: deadlock detected
Detail: Process 13560 waits for ShareLock on transaction 3147316424; blocked by process 13566.
Process 13566 waits for ShareLock on transaction 3147316408; blocked by process 13560.
INSERT语句中始终抛出上述错误。
其他信息: 1)我在此表中定义了PRIMARY KEY。 2)此表中有FOREIGN KEY引用。 3)将单独的数据库连接传递给每个Java线程。
技术 Web服务器:Tomcat v6.0.10 Java v1.6.0 Servlet的 数据库:PostgreSQL v8.2.3 连接管理:pgpool II
答案 0 :(得分:24)
应对死锁的一种方法是使用等待随机间隔的重试机制并尝试再次运行事务。随机间隔是必要的,以便碰撞事务不会持续地相互碰撞,导致所谓的实时锁定 - 甚至更糟糕的调试。实际上,大多数复杂的应用程序迟早需要处理事务序列化失败时需要这样的重试机制。
当然,如果你能够确定死锁的原因,那么消除死锁通常会好得多,或者会回来咬你。对于几乎所有情况,即使在死锁条件很少的情况下,以确定性顺序获得锁定或获得更粗粒度锁定的一点点吞吐量和编码开销也是值得的,以避免偶尔的大延迟命中和突然性能悬崖缩放并发时。
当你一直得到两个INSERT语句死锁时,它很可能是一个唯一的索引插入顺序问题。在两个psql命令窗口中尝试以下示例:
Thread A | Thread B
BEGIN; | BEGIN;
| INSERT uniq=1;
INSERT uniq=2; |
| INSERT uniq=2;
| block waiting for thread A to commit or rollback, to
| see if this is an unique key error.
INSERT uniq=1; |
blocks waiting |
for thread B, |
DEADLOCK |
V
通常,解决此问题的最佳方法是找出保护所有此类交易的父对象。大多数应用程序都有一个或两个主要实体,例如用户或帐户,这些实体很适合。然后,您只需要为每个事务获取通过SELECT ... FOR UPDATE触及的主要实体上的锁定。或者如果碰到几个,那么每次都会锁定所有这些锁,但每次都按相同的顺序排列(按主键排序是一个不错的选择)。
答案 1 :(得分:12)
Explicit Locking的文档中介绍了PostgreSQL的功能。 “死锁”部分中的示例显示了您可能正在执行的操作。您可能没有预料到的部分是,当您更新某些内容时,该行会获取该行上的锁定,直到所涉及的事务结束为止。如果您有多个客户端同时对多个事物进行更新,那么您将不可避免地遇到死锁,除非您不遗余力地阻止它们。
如果你有多个东西可以取出像UPDATE这样的隐式锁,你应该将整个序列包装在BEGIN / COMMIT事务块中,并确保你对它们获取锁的顺序保持一致(即使是隐式锁,如UPDATE)在任何地方抓住。如果您需要更新表A中的内容然后表B,并且应用程序的一部分执行A然后B而另一部分执行B然后A,则有一天您将陷入僵局。针对同一个表的两个UPDATE同样注定要失败,除非你可以强制执行两个在客户端之间可重复的顺序。一旦你有了要更新的记录集,并且总是首先抓住“较低”的记录,按主键排序是一种常见的策略。
你的INSERT在这里被责备的可能性较小,那些陷入僵局的情况要困难得多,除非你违反了Ants已经描述过的主键。
您不想做的是尝试在您的应用中重复锁定,这将变成巨大的可扩展性和可靠性混乱(并且可能仍会导致数据库死锁)。如果您无法在标准数据库锁定方法的范围内解决此问题,请考虑使用建议锁定工具或显式LOCK TABLE来强制执行您需要的操作。这将为您节省一个痛苦的编码世界,而不是试图将所有锁推送到客户端。如果您对表有多个更新并且无法强制执行它们发生的顺序,那么除了在执行表时锁定整个表,您别无选择;这是唯一不会引发死锁的途径。
答案 2 :(得分:5)
死锁解释:
简而言之,正在发生的事情是,特定的SQL语句(INSERT或其他)正在等待另一个语句在数据库的特定部分上释放锁定,然后才能继续。在释放此锁之前,第一个SQL语句,称之为“语句A”将不允许自己访问数据库的这一部分来完成其工作(=常规锁定情况)。但是......语句A还锁定了数据库的另一个部分,以确保没有其他数据库用户访问(用于读取或修改/删除,具体取决于锁的类型) 。现在......第二个SQL语句本身需要访问由语句A的锁定标记的数据部分。这是一个DEAD LOCK:两个语句将无限期地等待。
补救措施......
这需要知道这些不同线程正在运行的特定SQL语句,并查看是否有办法:
a) removing some of the locks, or changing their types. For example, maybe the whole table is locked, whereby only a given row, or a page thereof would be necessary. b) preventing multiple of these queries to be submitted at a given time. This would be done by way of semaphores/locks (aka MUTEX) at the level of the multi-threading logic.
请注意“b)”方法,如果没有正确实现,可能只是将死锁情况从SQL内移到程序/线程逻辑中。关键是只创建一个互斥体,首先由任何即将运行这些容易出现死锁的查询的线程获得。
答案 3 :(得分:0)
您的问题可能是插入命令试图锁定一个或两个索引,并且索引被锁定以用于另一个步骤。
一个常见的错误是在每个线程上以不同的顺序锁定资源。检查订单并尝试在所有线程中以相同的顺序锁定资源。