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