可以用IN(,,,)进行行锁定生成死锁吗?

时间:2014-06-26 16:02:19

标签: mysql sql locking innodb deadlock

我的目标是避免死锁,所以我将所有锁集中在同一个地方,按表名排序,然后按ID升序:

SELECT * FROM table1 WHERE ID = 1 FOR UPDATE
SELECT * FROM table1 WHERE ID = 2 FOR UPDATE
SELECT * FROM table1 WHERE ID = 3 FOR UPDATE
SELECT * FROM table1 WHERE ID = 4 FOR UPDATE

SELECT * FROM table2 WHERE ID = 1 FOR UPDATE
SELECT * FROM table2 WHERE ID = 2 FOR UPDATE
SELECT * FROM table2 WHERE ID = 3 FOR UPDATE
SELECT * FROM table2 WHERE ID = 4 FOR UPDATE

但我想知道我是否可以使用IN()(可能更快一点)来做同样的事情

SELECT * FROM table1 WHERE ID IN(1,2,3,4) FOR UPDATE
SELECT * FROM table2 WHERE ID IN(1,2,3,4) FOR UPDATE

是否会按照IN()操作数指定的确切顺序锁定行,否则将使用“自然表排序”来应用锁定?

ID是所有表中的主要auto_increment字段,我不“重用”旧的已删除ID(因此理论上自然顺序应该始终是升序的)

提前感谢!


添加了更新:

UPDATE table1 SET t1="hello1" WHERE ID = 1;
UPDATE table1 SET t1="hello2" WHERE ID = 2;
UPDATE table1 SET t1="hello3" WHERE ID = 3;
UPDATE table1 SET t1="hello4" WHERE ID = 4;
UPDATE table2 SET t2="hello1" WHERE ID = 1;
UPDATE table2 SET t2="hello2" WHERE ID = 2;
UPDATE table2 SET t2="hello3" WHERE ID = 3;
UPDATE table2 SET t2="hello4" WHERE ID = 4;
...
COMMIT;

3 个答案:

答案 0 :(得分:1)

好的,所以这个问题并不是很清楚你想要什么...所以这可能不是你问题的答案..但我做了一些测试,以帮助你可视化数据和IN()如何工作..所以我希望至少有帮助。

SETUP:

CREATE TABLE table1
    (`id` int, `username` varchar(10), `t1` varchar(55));   
INSERT INTO table1
    (`id`, `username`, `t1`)
VALUES
(4, 'John', 'Hi1'),
(3, 'Ram ', 'Hi2'),
(2, 'Jack', 'Hi3'),
(1, 'Jill', 'Hi4');


CREATE TABLE table2
    (`id` int, `username` varchar(10), `t1` varchar(55));
INSERT INTO table2
    (`id`, `username`, `t1`)
VALUES
(1, 'Joe', 'Hey1'),
(2, 'Fes', 'Hey2'),
(3, 'Ned', 'Hey3'),
(4, 'Abe', 'Hey4');

我使table1具有向后ID ..又名 4,3,2,1 ,然后table2具有常规递增ID。 1,2,3,4 ......

1. SELECT * FROM table1 

RESULT-OF-1

2. SELECT * FROM table2 

RESULT-OF-2

3. SELECT * FROM table1 WHERE id = 1 OR id = 2 OR id = 3 OR id = 4 same result as 1

4. SELECT * FROM table1 WHERE id IN(1, 2, 3, 4).. same result as 1.

5. SELECT * FROM table1 WHERE id IN(1, 4, 3, 2).. same result as 1.

IN()将表中每行的id与IN()语句中指定的进行比较.. 如果匹配,则返回行..所以它返回数据“自然表排序”..更多信息HERE

有一种方法可以使用IN()语句执行更新,而无需写出每个更新。

UPDATE table1 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UPDATE table2 SET t2= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);

您在这里所做的就是将'hello'字符串与ID结合起来,并将列设置为它,因为这就是您发布的内容。我希望这有助于理解数据的提取方式。

两个更新的输出:table1 .... table2

对于要更新的​​锁定表,您应该将它们锁定为WRITE和UNLOCK以防止出现深度锁定。见post

LOCK TABLES table1 WRITE, table2 WRITE;
UPDATE table1 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UPDATE table2 SET t1= CONCAT('hello', ID) WHERE ID IN(1,2,3,4);
UNLOCK TABLES;

答案 1 :(得分:1)

即使有点不清楚,但MySQL's documentation中列出了问题答案的某些部分:

  

expr IN(值,...)

     

如果expr等于IN列表中的任何值,则返回1,否则返回   返回0. 如果所有值都是常量,则根据它们进行评估   到expr的类型和排序。然后搜索该项目   使用二进制搜索。

以下是你应该得到的:如果列表中的所有值都是常量,则使用二进制搜索对它们进行比较。

所以最后,如果你已经对值进行了排序并不重要,因为MySQL会对它们进行排序,即使它们不是。然而,这不是你的问题。现在让我们回到你的问题。

首先,当您使用InnoDb并且它们一直在发生时(至少对我而言),MySQL中的死锁绝对是可能的。您选择用于防止死锁的策略是有效的(根据某种顺序获取锁定)。但不幸的是,我不认为它会在MySQL中起作用。你看,即使在你的查询中清楚地说明了你想锁定哪些记录,但事实是they are not the only records that will be locked:

  

锁定读取,UPDATE或DELETE通常设置记录锁定   在处理SQL时扫描的每个索引记录   声明。是否存在WHERE条件并不重要   将排除该行的语句。 InnoDB不记得了   确切的WHERE条件,但只知道扫描了哪个索引范围。   锁通常是下一把钥匙锁,也可以阻止插入   记录前的“差距”。但是,间隙锁定可以   显式禁用,导致不使用下一键锁定。

因此很难说实际锁定了哪些记录。现在考虑MySQL在索引中搜索列表中的第一个值。正如我刚才所说,当MySQL正在扫描索引时,可能还会锁定更多记录。而且由于扫描索引不是按顺序发生的(或者至少是我所相信的),记录会被锁定而不管它们的顺序如何。这意味着不会阻止死锁。

最后一部分是我对情况的理解,我实际上从未在任何地方读过这篇文章。但理论上听起来是对的。然而,我真的希望有人证明我是错的(只是这样我可以更信任MySQL)。

答案 2 :(得分:1)

行按读取顺序锁定,因此无法保证订单。即使您添加了ORDER BY子句,行也会在读取时被锁定,而不是按顺序排列。 Here is another good question有一些很好的答案