在 MySQL 中,如果两个不同的事务在执行时,互相持有了对方所需的锁,此时由于它们都在等待某个资源,永远不会释放自己获得的锁,因此就会产生死锁(deadlock)。
以下是一个产生死锁的示例。首先,在客户端 A 中创建一个表 t,它只有一行数据。然后开始一个事务,并且通过共享查询模式获取该行数据上的 S 锁。
mysql> CREATE TABLE t (i INT) ENGINE = InnoDB;
Query OK, 0 rows affected (1.07 sec)
mysql> INSERT INTO t (i) VALUES(1);
Query OK, 1 row affected (0.09 sec)
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM t WHERE i = 1 FOR SHARE;
+------+
| i |
+------+
| 1 |
+------+
接下来,在客户端 B 中开始另一个事务,并且尝试删除该行数据:
mysql> START TRANSACTION;
Query OK, 0 rows affected (0.00 sec)
mysql> DELETE FROM t WHERE i = 1;
删除操作需要获得一个排他锁(X)。由于客户端 A 已经获得了一个 S 锁,客户端 B 的锁请求需要进入锁的等待队列,因此客户端 B 被阻塞。
最后,在客户端 A 中也尝试删除该行数据:
mysql> DELETE FROM t WHERE i = 1;
Query OK, 1 row affected (0.00 sec)
同时,在客户端 B 返回以下错误信息:
mysql> DELETE FROM t WHERE i = 1;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
此时产生了死锁,因为客户端 A 需要获得一个 X 锁才能删除该行数据。然而,客户端 A 无法获得该锁,因为客户端 B 已经请求了一个 X 锁,并且等待客户端 A 释放该数据行上的 S 锁。客户端 A 上的 S 锁也无法升级为 X 锁,因为客户端 B 的 X 锁请求优先级更高。结果就是,InnoDB 将会在某个客户端产生错误,并且释放它所获取的锁。
至此,另一个客户端(A)能够获得请求的锁,并且删除该数据行。
通过以上示例可以看出,当启用了死锁检测时(默认设置),InnoDB 自动执行事务的死锁检测,并且回滚一个或多个事务以解决死锁。InnoDB 尝试回滚更小的事务,事务的大小由它所插入、更新或者删除的数据行数决定。
MySQL 8.0 新特性之死锁检测控制
最新推荐文章于 2024-07-18 04:04:26 发布