本节基于死锁的概念信息(见第 17.7.5.2 节,“死锁检测”)。它解释了如何组织数据库操作以最小化死锁,并在应用程序中需要的错误处理。
死锁是事务数据库中的经典问题,但它们并不危险,除非它们太频繁,以至于您无法运行某些事务。通常,您必须编写应用程序,使其总是准备好重新发出事务,如果它由于死锁而回滚。
InnoDB
使用自动行级锁定。您甚至可以在只插入或删除单行的事务中获得死锁。这是因为这些操作不是真正的 “原子”;它们自动设置锁在插入或删除的行的(可能是多个)索引记录上。
您可以使用以下技术来cope with 死锁并减少它们的发生可能性:
-
随时发出
SHOW ENGINE INNODB STATUS
以确定最新死锁的原因。这可以帮助您调整应用程序以避免死锁。 -
如果频繁的死锁警告引起关注,可以通过启用
innodb_print_all_deadlocks
变量来收集更多的调试信息。在 MySQL 错误日志 中记录了每个死锁的信息,而不仅仅是最新的一个。禁用该选项时,您完成调试。 -
总是准备好重新发出事务,如果它由于死锁而失败。死锁并不危险。只是再试一次。
-
保持事务小和短暂,以使它们不太容易碰撞。
-
立即提交事务,以便在进行一系列相关更改后,使它们不太容易碰撞。特别是,不要长时间保持交互式 mysql 会话打开,带有未提交的事务。
-
如果您使用 锁定读取 (
SELECT ... FOR UPDATE
或SELECT ... FOR SHARE
),尝试使用较低的隔离级别,例如READ COMMITTED
。 -
当在事务中修改多个表或同一表中的不同行集时,总是以一致的顺序执行这些操作。然后,事务将形成明确定义的队列,不会死锁。例如,在应用程序中组织数据库操作为函数,或者调用存储过程,而不是在不同地方编写多个相似的
INSERT
、UPDATE
和DELETE
语句。 -
添加适当的索引到您的表中,以便您的查询扫描较少的索引记录和设置较少的锁。使用
EXPLAIN SELECT
确定 MySQL 服务器认为最适合您的查询的索引。 -
使用较少的锁。如果您可以允许
SELECT
从旧快照返回数据,不要添加FOR UPDATE
或FOR SHARE
子句。使用READ COMMITTED
隔离级别是这里的好选择,因为每个一致读取在同一事务中从其自己的新快照中读取。 -
如果其他方法都无效,请使用表级锁来序列化事务。使用事务表(如
InnoDB
表)时,正确使用LOCK TABLES
的方法是:首先开始事务,使用SET autocommit = 0
(而不是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 表级锁,必须使用超时方法来解决死锁。