一、产生死锁的条件
如果我们的数据库并发请求不高一般不会产生死锁。即便存在高并发,也不一定会产生死锁。只有满足一定的条件才会产生死锁,即死锁产生的4个必要条件:
- 资源排它性。一个资源(数据)只能被一个事务占有;
- 不可剥夺性。一个事务在占有一份资源期间,该资源不能再被其它事务剥夺;
- 请求与保持。一个事务在占有一份资源的同时,继续保持请求其它的资源;
- 循环等待。多个事务在保持占有自己的资源,同时请求其他资源构成了一个环;
二、排查数据库死锁
- 首先,确定所使用的数据库的版本:
select version()
2. 确定数据表使用的存储引擎及表结构信息,以我本地一个数据表为例:
show create table `students`;
3. 确定当前数据库中事务的隔离级别:
# mysql 8.x 版本
select @@transaction_isolation;
# mysql 5.x 版本
select @@tx_isolation;
4. 查看死锁日志。同时,我们应该明确一点:数据库表中对字段进行加锁,锁的是索引,所以下面我们下面在查看死锁日志时应该留意加了索引的字段。
- MySQL5.6及之前的版本可以通过
show engine innodb status
命令来查看最近发生的死锁的死锁日志。可以看到那几个SQL执行形成了死锁,以及他们分别持有哪个锁,在等待哪个锁等; - MySQL5.6以上的版本可以通过参数
innodb_print_all_deadlocks
对MySQL进行配置,决定着MySQL 是否开启死锁日志;
# 查询MySQL死锁日志开关是否开启
show variables like 'innodb_print_all_deadlocks';
# 开启MySQL死锁日志
set global innodb_print_all_deadlocks=1;
如果开启了死锁日志,死锁日志就会被输出到MySQL的错误日志中,通过 show variables like 'log_error%'
查看MySQL错误日志的路径,在对应的错误日志路径下查看对应的文件内容。
三、死锁解决办法
3.1 一个事务中多个SQL访问多张表产生死锁
- 看是否可以指定获取表上记录的顺序。例如将业务上使用的所有表都设置一个序号,当在一个事务中执行多个SQL ,要获取到多个锁时,我们按照表序号的顺序获取锁,如果可以获得所有的锁则提交该事务,否则立刻回滚。
- 当然,要尽量避免长事务,不推荐在一个事务里执行多个SQL。
3.2 重新定义索引
- 需明确:数据库上的锁是加在索引上的。所以,当索引的区分度不够的时候,一个索引值可能会对应很多的数据,这也意味着如果一个事务对一条索引值加了锁,这个索引值对应记录的请求事务都可能被锁住。这很大程度提高了死锁发生的概率。
3.3 降低事务隔离级别
- 如果数据库并发度较高的话,可通过降低事务的隔离级别进一步提高并发。MySQL的默认隔离级别是
Repeatable Read
,如果业务场景上较少发生幻读或者对幻读容忍度高,可将事务的隔离级别调整到Read Committed
;