什么是死锁
官方定义如下: 两个事务都持有对方需要的锁, 并且在等待对方释放, 并且双方都不会释放自己的锁。
MySQL有两种死锁处理方式
- 等待, 直到超时(innodb_lock_wait_timeout=50s) 。
- 发起死锁检测, 主动回滚一条事务, 让其他事务继续执行(innodb_deadlock_detect=on) 。
由于性能原因, 一般都是使用死锁检测来进行处理死锁。
死锁检测
死锁检测的原理是构建一个以事务为顶点、 锁为边的有向图, 判断有向图是否存在环, 存在即有死锁。
回滚
检测到死锁之后, 选择插入更新或者删除的行数最少的事务回滚,
基于INFORMATION_SCHEMA.INNODB_TRX 表中的 trx_weight 字段来判断。
对于锁的建议
- 收集死锁信息:
利用命令 SHOW ENGINE INNODB STATUS查看死锁原因。
调试阶段开启 innodb_print_all_deadlocks, 收集所有死锁日志。 - 减少死锁:
1.使用事务, 不使用 lock tables 。
2.保证没有长事务。
3.操作完之后立即提交事务, 特别是在交互式命令行中。
4.如果在用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE), 尝试降低隔离级别。
5.修改多个表或者多个行的时候, 将修改的顺序保持一致。
6.创建索引, 可以使创建的锁更少。
7.最好不要用 (SELECT … FOR UPDATE or SELECT … LOCK IN SHARE MODE)。
8.如果上述都无法解决问题, 那么尝试使用 lock tables t1, t2, t3 锁多张表
对于事务的建议
innodb存储引擎由于实现了行级锁, 颗粒更小, 实现更复杂。
但是innodb行锁在并发性能上远远要高于表锁页锁。
在使用方面可以尽量做到以下几点;
- 控制事务大小, 减少锁定的资源量和锁定时间长度。
- 所有的数据检索都通过索引来完成, 从而避免因为无法通过索引加锁而升级为表锁。
- 减少基于范围的数据检索过滤条件, 避免因为间隙锁带来的负面影响而锁定了不该锁定的数据。
- 在业务条件允许下, 尽量使用较低隔离级别的事务隔离。 减少隔离级别带来的附加成本。
- 合理使用索引, 让innodb在索引上面加锁的时候更加准确。
- 在应用中尽可能做到访问的顺序执行
- 如果容易死锁, 就可以考虑使用表锁来减少死锁的概率