说明如何组织数据库操作以最大程度地减少死锁和应用程序中所需的后续错误处理。
死锁是事务数据库中的经典问题,但是除非死锁如此频繁以至于您根本无法运行某些事务,否则它们并不危险。通常,您必须编写应用程序,以便在由于死锁而使事务回滚时,它们始终准备重新发出事务。
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认为最适合您的查询。
使用更少的锁定。如果你能负担得起,以允许 SELECT从一个旧的快照返回数据,不要添加条款FOR UPDATE或FOR SHARE给它。在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表级锁,必须使用超时方法来解决死锁。