【问题标题】:Deadlock error in INSERT statementINSERT 语句中的死锁错误
【发布时间】:2010-12-03 23:22:15
【问题描述】:

我们有一个基于网络的应用程序。应用程序中有时间限制的数据库操作(插入和更新)需要更多时间才能完成,因此该特定流程已更改为 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 线程。

技术 网络服务器:Tomcat v6.0.10 Java v1.6.0 小服务程序 数据库:PostgreSQL v8.2.3 连接管理:pgpool II

【问题讨论】:

  • 其他信息将有助于诊断您的问题。您能否提供具体的外键约束、有关表模式的一些基本信息以及导致死锁的实际 SQL 语句?

标签: sql postgresql database-deadlocks


【解决方案1】:

处理死锁的一种方法是有一个重试机制,等待一个随机间隔并尝试再次运行事务。随机间隔是必要的,这样冲突的事务就不会不断地相互碰撞,从而导致所谓的活锁——甚至更难调试。实际上,大多数复杂的应用程序在需要处理事务序列化失败时,迟早会需要这种重试机制。

当然,如果您能够确定死锁的原因,通常最好消除它,否则它回来咬你。对于几乎所有情况,即使死锁情况很少见,为了获得确定顺序的锁或获得更多粗粒度锁所付出的一点点吞吐量和编码开销也是值得的,以避免偶尔的大延迟命中和突然的性能悬崖扩展并发时。

当您始终遇到两个 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 获得它所触及的主要实体上的锁。或者如果涉及多个,则每次都以相同的顺序锁定所有这些(按主键排序是一个不错的选择)。

【讨论】:

    【解决方案2】:

    Explicit Locking 上的文档介绍了 PostgreSQL 在这里所做的事情。 “死锁”部分中的示例显示了您可能在做什么。您可能没有预料到的部分是,当您更新某些内容时,它会在该行上获得一个锁定,该锁定一直持续到所涉及的事务结束。如果您有多个客户端同时更新不止一件事,那么您将不可避免地陷入死锁,除非您竭尽全力防止它们发生。

    如果你有多个像 UPDATE 那样取出隐式锁的东西,你应该将整个序列包装在 BEGIN/COMMIT 事务块中,并确保你获得锁的顺序是一致的(即使是像 UPDATE 这样的隐式锁)抓取)无处不在。如果您需要更新表 A 然后表 B 中的某些内容,并且应用程序的一部分执行 A 然后 B,而另一部分执行 B 然后 A,那么您总有一天会死锁。对同一个表的两次更新同样注定会失败,除非您可以强制执行在客户端之间可重复的两者的某种顺序。一旦您拥有要更新的记录集并始终先获取“较低”的记录集,就按主键排序是一种常见的策略。

    你的 INSERT 不太可能在这里受到责备,它们更难陷入僵局,除非你违反了 Ants 已经描述的主键。

    您不想做的是尝试在您的应用程序中重复锁定,这将变成巨大的可扩展性和可靠性混乱(并且可能仍会导致数据库死锁)。如果您无法在标准数据库锁定方法的范围内解决此问题,请考虑使用咨询锁定工具或显式LOCK TABLE 来强制执行您需要的操作。这将为您节省一个痛苦的编码世界,而不是尝试将所有锁推到客户端。如果您对一个表有多个更新并且无法强制执行它们发生的顺序,那么您别无选择,只能在执行它们时锁定整个表;这是唯一不会引入死锁可能性的途径。

    【讨论】:

      【解决方案3】:

      死锁解释
      简而言之,正在发生的事情是一个特定的 SQL 语句(INSERT 或其他)正在等待另一个语句释放对数据库特定部分的锁定,然后才能继续执行。在此锁被释放之前,第一条 SQL 语句,称为“语句 A”将不允许自己访问这部分数据库以完成其工作(= 常规锁情况)。但是... 语句 A 还对数据库的 另一个 部分进行了锁定,以确保没有其他数据库用户可以访问(用于读取或修改/删除,具体取决于锁定的类型) .现在...第二条 SQL 语句本身需要访问由语句 A 的锁标记的数据部分。这是一个死锁:两个语句将无限等待。

      补救措施...

      这将需要知道这些不同线程正在运行的特定 SQL 语句,并在其中查找是否有办法:

      a) 移除一些锁,或者改变它们的类型。 例如,可能整个表都被锁定,因此只有给定的行,或者 其中一页是必要的。 b) 防止在给定时间提交多个此类查询。 这将通过信号量/锁(又名 MUTEX)在 多线程逻辑。

      请注意,“b)”方法如果没有正确实施,可能只会将死锁情况从 SQL 内部转移到程序/线程逻辑内部。关键是只创建一个互斥锁,任何线程将首先获取这些互斥锁,该线程将要运行这些容易死锁的查询之一。

      【讨论】:

        【解决方案4】:

        您的问题可能是插入命令试图锁定一个或两个索引,并且索引被锁定以用于另一个胎面。

        一个常见的错误是在每个线程上以不同的顺序锁定资源。检查顺序并尝试在所有线程中以相同的顺序锁定资源。

        【讨论】:

        • PostgreSQL 在常规语句期间不锁定索引,只锁定表中的行。为了锁定索引,您必须专门修改或维护它。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多