• MySQL批量更新死锁案例分析--转载


    问题描述

    在做项目的过程中,由于写SQL太过随意,一不小心就抛了一个死锁异常,如下:

    1. com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction  
    2.         at sun.reflect.GeneratedConstructorAccessor247.newInstance(Unknown Source)  
    3.         at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)  
    4.         at java.lang.reflect.Constructor.newInstance(Constructor.java:513)  
    5.         at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)  
    6.         at com.mysql.jdbc.Util.getInstance(Util.java:381)  
    7.         at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1045)  
    8.         at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:956)  
    9.         at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3491)  
    10.         at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3423)  
    11.         at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1936)  
    12.         at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2060)  
    13.         at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2542)  
    14.         at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1734)  
    15.         at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:2019)  
    16.         at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1937)  
    17.         at com.mysql.jdbc.PreparedStatement.executeUpdate(PreparedStatement.java:1922)  

    表结构如下:

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

    SQL语句如下:

    1. 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、先获取需要更新的记录的主键 

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

    2、逐条更新

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

    这样貌似解决了,都是对单条进行操作,都是先获取主键上的锁,再获取idx_1上的锁。

    不过这个解决方案与先前的更新语句不一样,先前的更新语句对所有记录的更新在一个事务中,采用循环更新后并不在同一个事务中,所以在for循环外面还得开一个事务。

    1. Exception e = (Exception)getDbfeelTransactionTemplate().execute(new TransactionCallback() {  
    2.    public Object doInTransaction(TransactionStatus status) {  
    3.       try {  
    4.             for(Long id:idList) {  
    5.         <span style="white-space:pre">  </span>userItemDAO.updateStatus(id,userId,1)  
    6.         }  
    7.             return null;  
    8.       }catch(DAOException e) {  
    9.          status.setRollbackOnly();  
    10.          return e;  
    11.       }  
    12.       catch (Exception e) {  
    13.          status.setRollbackOnly();  
    14.          return e;  
    15.       }  
    16.    }  
    17. });  

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

    原文:http://blog.csdn.net/aesop_wubo/article/details/8286215

  • 相关阅读:
    Docker之概述
    redis命令
    spring mvc(1) 为什么要使用mvc
    学习到的
    HttpWebRequest简单使用
    推手总结
    react 生命周期
    利用反射对应数据库字段
    扩展方法
    发送请求并返回
  • 原文地址:https://www.cnblogs.com/davidwang456/p/3998361.html
Copyright © 2020-2023  润新知