如果我们依靠语句中的索引扫描顺序,两个InnoDB UPDATE语句是否可以针对PK索引死锁?

时间:2019-02-18 16:36:00

标签: mysql innodb transaction-isolation

如果给我们一张桌子:

MariaDB [test]> create table foo (
    -> id integer primary key,
    -> version_id integer);
Query OK, 0 rows affected (0.05 sec)

以及具有主键1和2的两行

MariaDB [test]> insert into foo (id, version_id) values(1, 1);
Query OK, 1 row affected (0.01 sec)

MariaDB [test]> insert into foo (id, version_id) values(2, 1);
Query OK, 1 row affected (0.00 sec)

在发出使用WHERE子句中的主键的UPDATE语句时,InnoDB使用索引记录锁,如https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-gap-locks中所述。也就是说,它分别锁定每一行。

基于此,我们可以通过以相反的顺序发出主键1和2的UPDATE来说明两个事务之间的简单死锁:

transaction 1 # MariaDB [test]> begin;
transaction 1 # Query OK, 0 rows affected (0.00 sec)

transaction 2 # MariaDB [test]> begin;
transaction 2 # Query OK, 0 rows affected (0.00 sec)

transaction 1 # MariaDB [test]> update foo set 
                    -> version_id=version_id+1 where id=1;
transaction 1 # Query OK, 1 row affected (0.01 sec)
transaction 1 # Rows matched: 1  Changed: 1  Warnings: 0

transaction 2 # MariaDB [test]> update foo set 
                    -> version_id=version_id+1 where id=2;
transaction 2 # Query OK, 1 row affected (0.01 sec)
transaction 2 # Rows matched: 1  Changed: 1  Warnings: 0

transaction 1 # MariaDB [test]> update foo set 
                    -> version_id=version_id+1 where id=2;
<blocks on index lock created by transaction 2 on id=2>

transaction 2 # MariaDB [test]> update foo set 
                    -> version_id=version_id+1 where id=1;
transaction 2 # ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

<wakes up>
transaction 1 # Query OK, 1 row affected (22.24 sec)
transaction 1 # Rows matched: 1  Changed: 1  Warnings: 0

最后是问题。如果我们改为使用IN作为主键值列表将这些UPDATE语句写为单个语句,那么在不同事务中的这两个UPDATE语句可以产生相同的条件吗?请注意,我还颠倒了IN内部参数的顺序,这没关系,因为这不是我期望UPDATE扫描索引的方式。还是行锁定的顺序是确定的? (或者还有其他两个原因不能冲突的原因)?

transaction 1 # MariaDB [test]> update foo set 
                    -> version_id=version_id+1 
                    -> where id in (1, 2);
transaction 1 # Query OK, 2 rows affected (0.00 sec)
transaction 1 # Rows matched: 2  Changed: 2  Warnings: 0


transaction 2 # MariaDB [test]> update foo set 
                    -> version_id=version_id+1 
                    -> where id in (2, 1);
# note it blocked until the other transaction was done
transaction 2 # Query OK, 2 rows affected (6.28 sec) 
transaction 2 # Rows matched: 2  Changed: 2  Warnings: 0

1 个答案:

答案 0 :(得分:0)

您的第一个示例是经典的死锁示例。

第二个示例(带有IN)是innodb_lock_wait_timeout演示的开始,在这种情况下,一个连接可以等待而不必死锁。

WHERE id IN (...)必须以原子方式处理所有有问题的ID。这与您的第一个示例不同,在第一个示例中,很明显一次锁定了行。

过去,最好是对IN列表进行排序。但是我认为MySQL现在可以对它们进行排序。

可能存在一个阈值,在该阈值以上它会扫描而不是单独获取每个id。据称这导致锁定IN列表中未提及的行。