• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

mysql死锁-非主键索引更新唤起的死锁_mysql

mysql 搞代码 7年前 (2018-06-07) 199次浏览 已收录 0个评论

mysql死锁-非主键索引更新引起的死锁
背景:最近线上经常抛出mysql的一个Deadlock,细细查来,长了知识!

分析:错误日志如下:
21:02:02.563 ERROR dao.CommonDao        [pool-15-thread-19] [jbc.trade.qunar.com] [703c9ddbe4b143609035365ca46bff35] – db error , tableId=jbc.trade.qunar.com, sql=update i_pay_record set checktime = now() where order_id in (62818) , par
ams=null
com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction
      
其中sql更新语句:

 update i_pay_record set checktime = now() where order_id in (62818) 

很奇妙吧,执行一条sql会有死锁吗?
答案是:会有。

其中奥妙就在 i_pay_record 中的 order_id 字段有索引。

特此转载下面这篇文章很好的讲解了这个问题 !!!
http://blog.csdn.net/aesop_wubo/article/details/8286215
转载:
表结构如下:

 CREATE TABLE `user_item` (   `id` BIGINT(20) NOT NULL,   `user_id` BIGINT(20) NOT NULL,   `item_id` BIGINT(20) NOT NULL,   `status` TINYINT(4) NOT NULL,   PRIMARY KEY (`id`),   KEY `idx_1` (`user_id`,`item_id`,`status`) ) ENGINE=INNODB DEFAULT CHARSET=utf-8 

SQL语句如下:

 update user_item set status=1 where user_id=? and item_id=?   

原因分析
mysql的事务支持与存储引擎有关,MyISAM不支持事务,INNODB支持事务,更新时采用的是行级锁。这里采用的是INNODB做存储引擎,意味着会将update语句做为一个事务来处理。前面提到行级锁必须建立在索引的基础,这条更新语句用到了索引idx_1,所以这里肯定会加上行级锁。
行级锁并不是直接锁记录,而是锁索引,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;如果一条语句操作了非主键索引,mysql会先锁住非主键索引,再锁定主键索引。
这个update语句会执行以下步骤:
1、由于用到了非主键索引,首先需要获取idx_1上的行级锁
2、紧接着根据主键进行更新,所以需要获取主键上的行级锁;
3、更新完毕后,提交,并释放所有锁。
如果在步骤1和2之间突然插入一条语句:update user_item …..where id=? and user_id=?,这条语句会先锁住主键索引,然后锁住idx_1。
蛋疼的情况出现了,一条语句获取了idx_1上的锁,等待主键索引上的锁;另一条语句获取了主键上的锁,等待idx_1上的锁,这样就出现了死锁。

解决方案
1、先获取需要更新的记录的主键

 select id from user_item where user_id=? and item_id=?   

2、逐条更新

 select id from user_item where user_id=? and item_id=?   for (Long id : idList) {       userItemDAO.updateStatus(id, userId, 1);   }   update user_item set status=? where id=? and user_id=?   

3、这样貌似解决了,都是对单条进行操作,都是先获取主键上的锁,再获取idx_1上的锁。
不过这个解决方案与先前的更新语句不一样,先前的更新语句对所有记录的更新在一个事务中,采用循环更新后并不在同一个事务中,所以在for循环外面还得开一个事务。

 Exception e = (Exception)getDbfeelTransactionTemplate().execute(new TransactionCallback() {    public Object doInTransaction(TransactionStatus status) {       try {          for(Long id:idList) {   <span style="white-space:pre"> </span>userItemDAO.updateStatus(id,userId,1)   }         return null;       }catch(DAOException e) {          status.setRollbackOnly();          return e;       }       catch (Exception e) {          status.setRollbackOnly();          return e;       }    } }); 

小结:在采用INNODB的MySQL中,更新操作默认会加行级锁,行级锁是基于索引的,在分析死锁之前需要查询一下mysql的执行计划,看看是否用到了索引,用到了哪个索引,对于没有用索引的操作会采用表级锁。如果操作用到了主键索引会先在主键索引上加锁,然后在其他索引上加锁,否则加锁顺序相反。在并发度高的应用中,批量更新一定要带上记录的主键,优先获取主键上的锁,这样可以减少死锁的发生。

欢迎大家阅读《mysql死锁-非主键索引更新唤起的死锁_mysql》,跪求各位点评,by 搞代码


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:mysql死锁-非主键索引更新唤起的死锁_mysql

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址