zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Elasticsearch Query DSL 整理总结(三)—— Match Phrase Query 和 Match Phrase Prefix Query
    Elasticsearch Query DSL 整理总结(二)—— 要搞懂 Match Query,看这篇就够了
    Elasticsearch Query DSL 整理总结(一)—— Query DSL 概要,MatchAllQuery,全文查询简述
    Elasticsearch Java Rest Client API 整理总结 (三)——Building Queries
    Elasticsearch date 类型详解
    python 历险记(五)— python 中的模块
    python 历险记(四)— python 中常用的 json 操作
    python 历险记(三)— python 的常用文件操作
    Elasticsearch Java Rest Client API 整理总结 (二) —— SearchAPI
    Elasticsearch Java Rest Client API 整理总结 (一)——Document API
  • 原文地址:https://www.cnblogs.com/davidwang456/p/3998361.html
Copyright © 2011-2022 走看看