Mysql索引引起的死锁

提到索引,首先想到的是效率提高,查询速度提升,不知不觉都会有一种心理趋向,管它三七二十一,先上个索引提高一下效率..但是索引其实也是暗藏杀机的...

今天压测带优化项目,开着Jmeter高并发访问项目,后台连着mysql通过show processlist命令查看查询情况,发现些sql语句需要优化,就在关键字段上上了索引.效果很明显项目的吞吐量瞬间提高到原来3倍,但是问题也出现了,日志中报出大量的死锁错误.本来以为程序逻辑有问题,定位了一下程序块,大体逻辑如下:

userDao.updateByUserId(userId);
userDao.save(user);

简单说就是,一个按照表中的userId字段进行更新,一个按照表中的主键进行保存.逻辑上看不出来什么问题,前前后后也尝试了各种方法,并添加了各种日志进行排查都无果.一筹莫展之际想到这个问题是在我添加索引之后出现的,也许和索引有关,之前记得读到过有关索引引起问题的帖子,但是具体内容已经忘记了,然后去掉了表中的索引(userId字段)后,果然再也不报错了,死锁消失了.进而探究了一下原因:

找到一篇博客进行了比较详细的介绍: http://blog.csdn.net/aesop_wubo/article/details/8286215

简要的说就是Mysql的innodb引擎支持事务,更新时采用的是行级锁,会在使用中的索引上加锁,如果使用的主键索引,直接锁主键索引,如果使用的非主键索引,则先锁索引,再锁对应的主键索引.故而在根据非主键索引进行更新时,实际上需要3步:

1)先获取索引锁

2)获取对应记录的主键锁

3)按照主键完成更新操作

在高并发的情况下实际上这里就存在问题了, 由于上面说的1)和2)是按照先锁索引,再锁主键的顺序,那么只要存在先锁主键,再锁索引这种反顺序操作那么就能达成死锁.也就是说一个获取了索引锁,等待主键锁,另一个获取了主键锁,等待获取索引锁.造成互相等待的死锁情况.

这里补充说明一下我上面例子中为何导致死锁:
userDao.updateByUserId(userId)这句是按照非主键索引更新,故先锁非主键索引,再锁主键索引.
userDao.save(user)这句按照主键保存对象,由于更新的字段中包含索引字段,故在获取主键索引后,需要获取索引字段的锁,以便完成字段更新.

这就满足了获取锁的顺序与上一句完全相反,达成死锁条件~

 

解法:

说了这么多,解决方案也就明晰了,只要让更新操作中带有主键即可.也就是让获取锁的顺序一致即可.我这里的例子userDao.updateByUserId(userId)更新时加入主键,要么就是拆分成2次操作,先查询到对应的记录,再根据主键来更新.

posted on 2017-07-20 22:26  蓝萝卜blu  阅读(2668)  评论(0编辑  收藏  举报

导航