[原文]](https://dev.mysql.com/doc/refman/5.7/en/innodb-deadlocks-handling.html)
死锁最小化建立在死锁检测之上。
死锁是事务型中经典问题,如果出现死锁的频率不高不是一个危险的问题。通常来讲,需要在应用中为死锁场景下事务重试做好准备。
InnoDB 自动使用行级锁。可能仅仅是在插入修改单行就出现死锁,这是因为这些操作并非真正的原子操作,它们会自动地在插入或修改的 index record(可能多个)上设置锁。(设置锁并非直接加锁,而是先请求锁,获得锁后再持有锁。)
降低死锁技巧:
每次都使用
SHOW ENGINE INNODB STATUS
命令查看最近死锁的来源,这样可以帮助微调应用避免死锁。如果高频出现死锁警告,打开
innodb_print_all_deadlocks
配置以收集更多 debug 信息。每个死锁的信息都记录在 MYSQL 的 error log 中。当完成 debug 后关闭此配置。应用中随时为死锁准备重新发布事务,死锁并不危险,只会重试。
尽量精简事务以降低其发生冲突的可能。
更新完相应的记录立即提交事务以降低其冲突的可能。实际操作中,不要让一个交互式的 mysql session 长时间不提交。
如果使用锁读(locking read: SELECT … FOR UPDATE/SELECT … LOCK IN SHARE MODE),尝试使用更低的事务隔离级别,如:READ COMMITTED。
当在同一个事务中修改多个表数据或单表多行数据时,保证每次(业务中不同的代码块)操作顺序的一致。这样事务会形成很好的队列而不出现死锁。例如:在应用中组织数据库的操作在 function 中或调用存储过程,而不是在应用不同的地方编写多个类似的增删改的 sql 语句。
在表中添加合适的索引。这样可以减少查询扫描的 index records 记录,因而添加更少的锁。使用 EXPLAIN SELECT 来让 MYSQL server 决定查询的最适索引。
尽量少加锁。如果允许 SELECT 语句返回老版本快照,就不要选择添加 FOR UPDATE/LOCK IN SHARE MODE 子句进入锁读。使用 READ COMMITTED 隔离级别对一致性读也有帮助,因为在事务中都能读取到最新的快照。
如果别无他法,可以将事务序列化到表级锁上。使用示例(注意语句顺序):
1
2
3
4
5SET autocommit=0;
LOCK TABLES t1 WRITE, t2 READ, ...;
... do something with tables t1 and t2 here ...
COMMIT;
UNLOCK TABLES;表级锁阻止表上的并发更新,可以避免忙系统少响应的消耗上的死锁。
另外一个序列化事务的方法是创建一个辅助标识表,这个表只有一条数据。使每一个事务在访问其他表前必须先更新这行数据。这样可以保证所有事务都排列执行。需要注意的是,InnoDB 死锁检测算法在这种场景下依然工作,因为这个序列化锁是个行级锁。在 MYSQL 表级锁情况下,死锁问题使用超时方法来解决。