有一天在查询生产异常日志的时候,突然发现了Mysql死锁的日志,第一反应则是:卧槽,我写的代码居然有死锁,带着疑问开始了长达半个小时的百度之旅,最终才知道原因是索引合并导致。
何为索引合并
1、顾名思义,索引合并,就是把多个索引到的数据合并成一个
2、索引合并的时候,会对索引进行并集,交集操作,以便合成一个索引。
3、这些需要合并的索引只能是一个表的,不能对多表进行索引合并。
举个例子
在下面的sql中,字段name和age都分别建了索引,查询时会分别通过name=lyy和age=18查找符合条件的数据,然后进行合并获取到主键id,再回表查询,然后对两个条件查询到的id进行交集,这个交集合并的过程就叫索引合并。
select * from user where name = 'lyy' and age = 18
为什么索引合并会导致死锁
死锁:我们先回顾下,什么是死锁?
两个或者两个以上的进程在执行的过程中,因争夺一些公共的资源,导致双方都占有了对方需要获取到才能继续往下执行的不同资源时,发生了互相一直等待的情况。这就叫死锁。
因为索引合并发生死锁的sql
update sop_task_statistics set task_num = task_num + #{dto.taskNum}, score = score + #{dto.score}
where slxt_account = #{dto.slxtAccount} and area_id = #{dto.areaId} and month_date = #{dto.monthDate}
因为数据库表分别对slxt_account、area_id、month_date字段建了索引,所以在执行上面的sql时会对上面的三个字段索引进行了锁定操作,并且进行了回表操作时也对主键索引进行了锁定,所以对索引的锁定次数达到了6次。
为什么这个sql会导致死锁?
我们用两个事务来分析下该sql的执行过程
如何解决上面的死锁
1、创建组合索引(slxt_account,area_id,month_date),这样在执行上面的sql语句时,通过组合索引直接定位到具体的主键索引id,更新操作变成了行锁,并且锁粒度细化到一行,这样就不会导致两个事务都锁定了对方需要获取到才能继续往下执行的资源。
2、先查询,查询操作不会发生锁操作,查询到具体的主键id,然后通过id进行更新操作,也就是更新sql改为下面这样
update sop_task_statistics set task_num = task_num + #{dto.taskNum}, score = score + #{dto.score} where id = #{id}