InnoDB死锁示例
以下示例说明了锁定请求将导致死锁时如何发生错误。该示例涉及两个客户端A和B。
首先,客户端A创建一个包含一行的表,然后开始事务。在事务中,A通过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 LOCK IN SHARE MODE;+------+| i |+------+| 1 |+------+
接下来,客户端B开始事务并尝试从表中删除该行:
mysql> START TRANSACTION;Query OK, 0 rows affected (0.00 sec)mysql> DELETE FROM t WHERE i = 1;
删除操作需要一个X
锁。无法授予该S
锁,因为它与客户端A持有的锁不兼容 ,因此该请求进入针对行和客户端B块的锁请求队列中。
最后,客户端A还尝试从表中删除该行:
mysql> DELETE FROM t WHERE i = 1;ERROR 1213 (40001): Deadlock found when trying to get lock;try restarting transaction
此处发生死锁,因为客户端A需要 X
锁才能删除该行。但是,不能授予该锁定请求,因为客户端B已经有一个X
锁定请求,并且正在等待客户端A释放其S
锁定。由于B事先要求锁,所以S
A持有的锁也不能 升级 X
为X
锁。结果, InnoDB
为其中一个客户端生成错误并释放其锁。客户端返回此错误:
ERROR 1213 (40001): Deadlock found when trying to get lock;try restarting transaction
届时,可以授予对另一个客户端的锁定请求,并从表中删除该行。
死锁检测
当死锁检测被使能(缺省值), InnoDB
自动检测事务 的死锁和回退事务或交易打破僵局。 InnoDB
尝试选择要回滚的小事务,其中事务的大小由插入,更新或删除的行数确定。
InnoDB
知道表锁if innodb_table_locks = 1
(默认)和 autocommit = 0
,它上面的MySQL层知道行级锁。否则, InnoDB
无法检测死锁,该死锁是由MySQLLOCK TABLES
语句设置的表锁或由存储引擎设置的锁InnoDB
所涉及的锁 。通过设置innodb_lock_wait_timeout
系统变量的值来解决这些情况 。
如果LATEST DETECTED DEADLOCK
第 InnoDB
监视器输出包括一条消息指出,“过深或长时间的搜寻锁表WAITS-FOR图中,我们将回滚下面的事务, ”这表明交易对所述等待名单已经达到了数限制为200。超过200个事务的等待列表将被视为死锁,并且尝试检查等待列表的事务将回滚。如果锁定线程必须查看等待列表上的事务拥有的1,000,000个以上的锁,也可能发生相同的错误。
有关组织数据库操作以避免死锁的技术,请参见第14.7.5节“ InnoDB中的死锁”。
禁用死锁检测
在高并发系统上,当多个线程等待相同的锁时,死锁检测会导致速度变慢。有时,禁用死锁检测并在innodb_lock_wait_timeout
发生死锁时依靠设置进行事务回滚可能会更有效 。可以使用innodb_deadlock_detect
配置选项禁用死锁检测 。
如何最小化和处理死锁
本节以第14.7.5.2节“死锁检测”中有关死锁的概念性信息为基础。它说明了如何组织数据库操作以最大程度地减少死锁和应用程序中所需的后续错误处理。
死锁是事务数据库中的经典问题,但是除非死锁如此频繁以至于您根本无法运行某些事务,否则它们并不危险。通常,您必须编写应用程序,以便在由于死锁而使事务回滚时,它们始终准备重新发出事务。
InnoDB
使用自动行级锁定。即使在仅插入或删除单行的事务中,也可能会遇到死锁。这是因为这些操作并不是真正的“原子”操作;它们会自动对插入或删除的行的(可能是多个)索引记录设置锁定。
您可以使用以下技术来处理死锁并减少发生死锁的可能性:
•在任何时候,发出 SHOW ENGINE INNODB STATUS
命令以确定最近死锁的原因。这可以帮助您调整应用程序以避免死锁。•如果频繁出现死锁警告引起关注,请通过启用innodb_print_all_deadlocks
配置选项来收集更广泛的调试信息 。有关每个死锁的信息,而不仅仅是最新的死锁,都记录在MySQL 错误日志中。完成调试后,请禁用此选项。•如果由于死锁而失败,请始终准备重新发行事务。死锁并不危险。请再试一次。•保持交易小巧且持续时间短,以使交易不易发生冲突。•进行一系列相关更改后立即提交事务,以减少冲突的发生。特别是,不要长时间未提交事务就打开交互式 mysql会话。•如果您使用锁定读取(SELECT ... FOR UPDATE
或 SELECT ... LOCK IN SHARE MODE
),请尝试使用较低的隔离级别,例如 READ COMMITTED
。•修改事务中的多个表或同一表中的不同行集时,每次都要以一致的顺序执行这些操作。然后,事务形成定义明确的队列,并且不会死锁。例如,组织数据库操作到功能在应用程序中,或调用存储程序,而不是编码的多个相似序列 INSERT
,UPDATE
以及 DELETE
在不同的地方语句。•将精选的索引添加到表中。然后,您的查询需要扫描更少的索引记录,并因此设置更少的锁。使用EXPLAIN SELECT
以确定哪些索引MySQL认为最适合您的查询。•使用较少的锁定。如果你能负担得起,以允许 SELECT
从一个旧的快照返回数据,不要添加条款FOR UPDATE
或LOCK IN SHARE MODE
给它。在READ COMMITTED
这里使用隔离级别是件好事,因为同一事务中的每个一致性读取均从其自己的新快照读取。•如果没有其他帮助,请使用表级锁序列化事务。LOCK TABLES
与事务表(例如InnoDB
表)一起使用的正确方法 是,以SET autocommit = 0
(not START TRANSACTION
)后跟来开始事务,直到明确提交事务后才LOCK TABLES
调用 UNLOCK TABLES
。例如,如果您需要写表 t1
和从表中读取数据 t2
,则可以执行以下操作:
SET autocommit=0;LOCK TABLES t1 WRITE, t2 READ, ...;... do something with tables t1 and t2 here ...COMMIT;UNLOCK TABLES;表级锁可防止对表的并发更新,从而避免死锁,但代价是对繁忙系统的响应速度较慢。•序列化事务的另一种方法是创建一个仅包含一行的辅助“信号量”表。在访问其他表之前,让每个事务更新该行。这样,所有事务都以串行方式发生。请注意,InnoDB
即时死锁检测算法在这种情况下也适用,因为序列化锁是行级锁。对于MySQL表级锁,必须使用超时方法来解决死锁。




