如果给我们一张桌子:
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
答案 0 :(得分:0)
您的第一个示例是经典的死锁示例。
第二个示例(带有IN
)是innodb_lock_wait_timeout
演示的开始,在这种情况下,一个连接可以等待而不必死锁。
WHERE id IN (...)
必须以原子方式处理所有有问题的ID。这与您的第一个示例不同,在第一个示例中,很明显一次锁定了行。
过去,最好是对IN
列表进行排序。但是我认为MySQL现在可以对它们进行排序。
可能存在一个阈值,在该阈值以上它会扫描而不是单独获取每个id。据称这导致锁定IN
列表中未提及的行。