Documentation Home
MySQL 8.4 Reference Manual
Related Documentation Download this Manual
PDF (US Ltr) - 39.8Mb
PDF (A4) - 39.9Mb
Man Pages (TGZ) - 257.9Kb
Man Pages (Zip) - 364.9Kb
Info (Gzip) - 4.0Mb
Info (Zip) - 4.0Mb


MySQL 8.4 Reference Manual  /  ...  /  How to Minimize and Handle Deadlocks

17.7.5.3 死锁的最小化和处理

本节基于第17.7.5.2节,“死锁检测”中的概念信息,解释如何组织数据库操作以最小化死锁,并在应用程序中所需的错误处理。

死锁是事务型数据库中的经典问题,但它们只有在频繁到达不能运行某些事务时才会危险。通常,您必须编写应用程序,以便总是准备好重新提交事务,如果它因为死锁而被回滚。

InnoDB 使用自动行级锁定,即使只插入或删除单行记录,也可以出现死锁。这是因为这些操作不是真正的原子;它们自动设置了插入或删除行的索引记录上的锁定。

您可以使用以下技术来应对死锁和减少其发生的可能性:

  • 在任何时候,执行SHOW ENGINE INNODB STATUS以确定最近一次死锁的原因。这可以帮助您调整应用程序以避免死锁。

  • 如果频繁的死锁警告引起关注,可以通过启用innodb_print_all_deadlocks变量来收集更多的调试信息。每个死锁的信息,不仅是最新的一个,还将被记录在MySQL错误日志中。禁用该选项当您完成调试。

  • 总是准备好重新提交事务,如果它由于死锁而失败。死锁不是危险的。只需再次尝试。

  • 保持事务小且短暂,以使它们更不容易发生冲突。

  • 立即提交事务,特别是在您完成了一组相关的变化后。这可以使事务更不容易发生冲突。尤其不要长时间留下交互式mysql会话,且未提交事务。

  • 如果您使用锁定读取(SELECT ... FOR UPDATESELECT ... FOR SHARE),请尝试使用较低的隔离级别,如READ COMMITTED

  • 在事务中修改多个表或同一张表中的不同行时,确保每次操作的顺序一致。这样的事务就可以形成良好的队列,不会死锁。例如,可以将数据库操作组织到应用程序中的函数中,或者调用存储过程,而不是在不同的位置编写多个相似的INSERTUPDATEDELETE语句。

  • 为您的表添加合适的索引,以便查询扫描更少的索引记录并设置更少的锁定。使用EXPLAIN SELECT来确定MySQL服务器认为哪些索引对于您的查询最合适。

  • 使用更少的锁定。如果您可以允许SELECT语句从旧快照中返回数据,不要将FOR UPDATEFOR SHARE子句添加到其中。使用READ COMMITTED隔离级别是这里的好选择,因为每个一致的读取操作都从同一个事务中读取自己的最新快照。

  • 如果其他方法都无效,使用表级锁定来序列化事务。使用LOCK TABLES与事务表,如InnoDB表,正确的方法是首先使用SET autocommit = 0(而不是START TRANSACTION),然后使用LOCK 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 表级锁定,需要使用超时方法来解决死锁。