2010-03-18 71 views
10

僵局,我有以下查询(所有表都InnoDB的)如何避免在MySQL

INSERT INTO busy_machines(machine) 
       SELECT machine FROM all_machines 
       WHERE machine NOT IN (SELECT machine FROM busy_machines) 
       and machine_name!='Main' 
       LIMIT 1 

从而导致死锁,当我在线程中运行它,内部的选择显然是因为,对不对?

我得到的错误是:

(1213, 'Deadlock found when trying to get lock; try restarting transaction') 

我怎样才能避免僵局?有没有办法改变查询来使它工作,还是我需要做别的事情?

错误不会总是发生,当然,只有在多次运行此查询并在多个线程中出现错误。

+0

您是否遇到死锁或锁争用? – Quassnoi 2010-03-18 13:47:54

+0

@Quassnoi:我在问题中添加了信息 - (1213,'尝试锁定时发现死锁;尝试重新启动事务') – olamundo 2010-03-18 14:09:33

+0

'show innodb status'会简要描述上一个死锁的原因 – jonny 2010-03-18 15:37:15

回答

5

如果用外连接替换“NOT IN”,您可能会获得更好的性能。

您也可以将其分成两个查询,以避免在单个查询中插入和选择相同的表。

事情是这样的:

  SELECT a.machine 
      into @machine 
      FROM all_machines a 
      LEFT OUTER JOIN busy_machines b on b.machine = a.machine 
      WHERE a.machine_name!='Main' 
      and b.machine IS NULL 
      LIMIT 1; 

      INSERT INTO busy_machines(machine) 
      VALUES (@machine); 
+0

如果使用MySQL 5.0或更高版本,这是一个很好的解决方案,否则您将不会有用户变量可用。这应该避免你的死锁问题。 – 2010-03-18 14:40:18

+0

我不明白你的查询与我的相似如何 - 外连接还会返回处于繁忙机器中的机器,而不仅仅是那些不是 – olamundo 2010-03-18 15:18:17

+0

@noam的机器,使用外连接与“and b.machine IS NULL“在where子句中排除busy_machines中的机器。它将返回与NOT IN相同的数据,但效率更高。 – 2010-03-18 16:10:05

11

据我的理解,一个选择不获取锁定,不应该是死锁的原因。

每次插入/更新/或删除一行时,都会获取锁定。为避免死锁,您必须确保并发事务不会以可能导致死锁的顺序更新行。一般来说,为了避免死锁即使在不同的交易中(例如总是首先是表A,然后是表B),您也必须始终以相同顺序获得锁

但是,如果在一次事务中只插入一个表中,则会满足此条件,而这通常不会导致死锁。你在交易中做了其他事情吗?

但是,如果存在缺失索引,会发生死锁。当插入/更新/删除一行时,数据库需要检查关系约束,也就是确保关系一致。为此,数据库需要检查相关表中的外键。它可能导致其他锁获取比被修改的行。请确保始终在外键(当然还有主键)上有索引,否则可能会导致表锁而不是行锁。如果表锁定发生,则锁定争用更高并且死锁的可能性增加。

不知道你的情况究竟发生了什么,但也许它有帮助。

+0

-1; MySQL甚至不会让你在两列之间创建一个外键,除非两者都被索引,所以外键上缺少的索引不可能解释任何东西。 – 2016-11-10 14:36:38