2010-05-26 85 views
0

我们非常沮丧地在MySQL中导致死锁。这不是因为超过了锁定超时,因为它们确实发生时会立即发生死锁。下面是是在2个独立的线程(从连接池2个独立的连接)执行时产生死锁的SQL代码:在MySQL中获取死锁

UPDATE Sequences SET Counter = LAST_INSERT_ID(Counter + 1) WHERE Sequence IS NULL 

序列表中有2列:序列和反

的LAST_INSERT_ID让我们到retrieve this updated counter value as per MySQL's recommendation。这对我们来说非常合适,但是我们遇到了这些僵局!我们为什么得到它们,我们如何避免它们?

非常感谢您的帮助。

编辑:这是所有事务(因为我使用Hibernate而需要)和AUTO_INCREMENT在这里没有意义。我应该更清楚。序列表包含许多序列(在我们的案例中约有1亿个序列)。我需要增加一个计数器并检索该值。 AUTO_INCREMENT在所有这一切中都没有任何作用,这与Ids或PRIMARY KEY无关。

+0

请问您可以发布'SHOW CREATE TABLE Sequences'的输出吗? – Quassnoi 2010-05-26 23:47:21

+0

CREATE TABLE'sequences'( 'Id' BIGINT(20)NOT NULL AUTO_INCREMENT, 'Sequence' VARBINARY(1005)DEFAULT NULL, 'Counter' INT(11)DEFAULT '0', PRIMARY KEY('Id' ), UNIQUE KEY'Sequences_Constraint'('Sequence'(104)) )ENGINE = InnoDB AUTO_INCREMENT = 134 DEFAULT CHARSET = utf8 – 2010-05-27 00:59:59

回答

2

将你的sql语句包装在一个事务中。如果您没有使用事务,您将在LAST_INSERT_ID上获得竞争条件。

但是真的,你应该有计数器字段auto_increment,所以你让mysql处理这个。

您的第三个解决方案是使用LOCK_TABLES来锁定序列表,以便其他进程不能同时访问它。除非您使用INNODB,否则这可能是最慢的解决方案。

+0

这是在一个事务中。我在上面编辑了我的问题,解释AUTO_INCREMENT在这里没有关系,抱歉不清楚。锁定序列表对我们来说可能是一个严重的性能问题。 – 2010-05-27 01:02:02

+1

事件导致*更多*死锁...他们不是解决死锁的解决方案。 – zombat 2010-05-27 04:42:51

1

死锁是任何事务数据库的正常组成部分,并且可以随时发生。一般来说,你应该编写你的应用程序代码来处理它们,因为没有确定的办法可以保证你永远不会陷入僵局。也就是说,有些情况会增加发生死锁的可能性,例如使用大型事务,还有一些事情可以减轻它们的发生。

首先,您应该阅读this manual page以更好地了解如何避免它们。其次,如果你所做的只是更新一个计数器,那么你确实应该使用一个AUTO_INCREMENT列来代替Counter,而不是依赖于“select then update”过程,正如你所看到的那样,它是一个可能导致僵局的竞争条件。实质上,表列的AUTO_INCREMENT属性将作为您的计数器。

最后,我将假设你在事务中有这个更新语句,因为这会产生频繁的死锁。如果你想看到它的行动,请尝试experiment listed here。这正是您的代码所发生的情况......两个线程正在尝试在提交其中一个之前同时更新相同的记录。即时死锁。

你最好的解决方案是弄清楚如何在没有事务的情况下完成它,AUTO_INCREMENT会让你这样做。

+0

有趣的实验链接到,谢谢。但我不确定这是怎么回事。我只更新了1行。我会阅读手册页。这当然是在交易中。我在上面编辑了我的问题,解释AUTO_INCREMENT在这里没有关系,抱歉不清楚。 – 2010-05-27 01:06:23

+0

@at - 你只更新一行,但它是两个事务中的同一行,因此是死锁。如果两个事务同时触发,则两者都会尝试更新同一行,因为在提交之前,每个事务的LAST_INSERT_ID()将相同。 – zombat 2010-05-27 04:40:24

+1

为什么不更新更新同一行的语句只是一个接一个地执行?为什么会陷入僵局? – 2010-05-27 05:40:15

0

没有涉及其他SQL?似乎对我来说不太可能。

'where sequence is null'可能会导致全表扫描,导致在每行/每页/ ...上读取锁。

如果(您的特定引擎不使用MVCC并且)在同一事务中更新之前有INSERT,则会成为问题。该INSERT将获得对某些资源(行/页/ ...)的排他锁,这将导致任何其他线程获取读取锁等待。因此,两个连接可以先插入它们,导致它们中的每一个都在表的一小部分上拥有排它锁,然后它们都会尝试更新,要求它们中的每一个都能够获得对整个桌子。

+0

死锁中没有涉及与该表有关的任何其他SQL。死锁都发生在上面的UPDATE语句上。有趣的是你对NULL问题的看法。还有一个UPDATE语句,序列名有一个值,但死锁不会在那里发生。我认为是因为我们的应用程序的性质,我们需要更频繁的空序列。如果NULL导致全表扫描,我可以很容易地改变它来寻找其他的东西,比如空字符串。我确实在同一个事务中插入,导致死锁? – 2010-05-27 09:25:39

+0

“没有涉及其他SQL”和“我在同一个事务中插入了”?来吧。事务之间发生死锁,而不是在语句级别。 为什么你首先写“WHERE ... IS NULL”呢?你不想限制你的更新只是由同一个事务插入的行吗? – 2010-05-27 09:32:23

+0

您问我是否没有涉及其他SQL,然后提示问题可能是同一事务中的插入语句。这就是为什么我用那些引用的短语回答。 “WHERE Sequence IS NULL”实际上是将我的更新限制在Sequence为null的那一行。我最后一个问题有点困惑。只有插入才会发生,因为序列名称在序列表中尚不存在。 – 2010-05-27 16:32:44