Java 在多线程Spring应用程序中避免MySQL死锁

Java 在多线程Spring应用程序中避免MySQL死锁,java,mysql,multithreading,spring,Java,Mysql,Multithreading,Spring,情况很简单。 我有一个很大的MySQL数据库,其中包含两个表: -- Table 1 id (primary key) | some other columns without constraints -----------------+-------------------------------------- 1 | foo 2 | bar 3 | foobar

情况很简单。
我有一个很大的MySQL数据库,其中包含两个表:

-- Table 1
id (primary key) | some other columns without constraints
-----------------+--------------------------------------
       1         |       foo
       2         |       bar
       3         |       foobar
      ...        |       ...

-- Table 2
id_src | id_trg | some other columns without constraints
-------+--------+---------------------------------------
   1   |   2    |    ...
   1   |   3    |    ...
   2   |   1    |    ...
   2   |   3    |    ...
   2   |   5    |    ...
   ...
  • 在表1中,只有
    id
    是主键。此表包含大约1200万条条目
  • 在表2中,
    id\u src
    id\u trg
    都是主键,在表1的
    id
    上都有外键约束,并且它们还启用了
    DELETE On CASCADE
    选项。该表包含约1.1亿个条目
好的,现在我所做的只是创建一个要从表1中删除的
id
s列表,然后执行一个简单的
DELETE from table1,其中id IN()

您可能已经猜到,后一个过程也会从表2中删除相应的id。到目前为止还不错,但问题是当我在多线程环境上运行它时,会出现许多死锁

一些注意事项:

  • 没有其他进程同时运行,也不会(暂时)运行
  • 我想快点!我有大约24个线程(如果这对答案有任何影响的话)
  • 我已经尝试了几乎所有的事务隔离级别(除了transaction\u NONE)
  • 订购/整理id我认为不会有帮助
  • 我已经尝试了
    选择。。。对于更新
    ,但是一个简单的
    删除
    最多需要30秒!(因此,使用它是没有用的):

我怎样才能解决这个问题

我将感谢任何帮助和提前感谢:)

编辑:

  • 使用InnoDB引擎
  • 在一个线程上,这个过程可能需要十几个小时甚至一整天,但我的目标是几个小时
  • 我已经在使用连接池管理器:
    java.util.concurrent
  • 有关双嵌套
    选择的说明,请参阅
  • 要从数据库中删除的列表可能总共包含数百万个条目,这些条目被分为200个块
  • UPDATE的
    子句是,我听说它锁定一行,而不是锁定整个表
  • 该应用程序使用Spring的batchUpdate(String sqlQuery)方法,因此事务是自动管理的
  • 所有ID都启用了索引,并且ID是唯一的,最多50个字符
  • id\u src
    id\u trg
    (分别)上的级联删除将意味着表1
    id=x
    上的每次删除将导致表2
    id\u src=x
    id\u trg=x
  • 按要求提供一些代码:

    public void write(List data){
        try{
            Arraylist idsToDelete = getIdsToDelete();
            String query = "DELETE FROM table1 WHERE id IN ("+ idsToDelete + " )";
            mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(query);
           } catch (Exception e) {
               LOG.error(e);
           }
    }
    

  • myJdbcTemplate
    只是一个抽象类,它扩展了
    JdbcDaoSupport

    首先,在传递ID的第一个简单删除查询中,如果将ID传递到1000这样的限制(子表中的行总数也应该接近但不超过10000等),应该不会产生问题,但如果你通过了50000或更多,那么它可能会产生锁定问题

    为了避免死锁,您可以按照下面的方法来处理这个问题(假设批量删除不是生产系统的一部分)-

    步骤1:通过选择查询获取所有ID并保留在游标中

    步骤2:现在逐个删除存储过程中游标中存储的这些ID


    注意:要检查删除为什么会获得锁,我们必须检查以下几项内容,如您传递的ID数量、在DB级别设置的事务级别、my.cnf中的Mysql配置设置等。

    删除多个(>10000)父记录可能是危险的,每个父记录都有通过级联删除的子记录,由于一次删除的记录最多,因此锁冲突导致死锁或回滚的可能性最大

    如果可以接受(意味着您可以直接与数据库建立JDBC连接),您应该(此处不涉及线程):

    • 计算要删除的ID列表
    • 每提交100或1000条记录,按批删除它们(先验值介于10和100之间)
    由于更重的工作应该在数据库部分,我毫不怀疑线程在这里会有所帮助。如果您想尝试,我建议您:

    • 一个线程(具有专用的数据库连接)计算要删除的ID列表,并与之保持同步队列
    • 少量线程(4个或8个),每个线程都有自己的数据库连接:
      • 使用准备好的
        从表1中批量删除,其中id=?
      • 从队列中获取ID并准备批
      • 每10或100条记录向数据库发送一批
      • 每10或100批进行一次提交
    我无法想象整个过程可能需要几分钟以上


    经过一些其他的阅读,看起来我已经习惯了旧的系统,我的数据非常保守。

    好的,这就是我所做的,它实际上可能不会避免死锁,但它是我当时唯一的选择

    这个解决方案实际上是一种使用Spring处理MySQL死锁的方法

    捕获并重试死锁:

    public void write(List data){
        try{
            Arraylist idsToDelete = getIdsToDelete();
            String query = "DELETE FROM table1 WHERE id IN ("+ idsToDelete + " )";
            try {
                 mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(query);
            } catch (org.springframework.dao.DeadlockLoserDataAccessException e) {
                 LOG.info("Caught DEADLOCK : " + e);
                 retryDeadlock(query); // Retry them!
            }
           } catch (Exception e) {
               LOG.error(e);
           }
    }
    
    public void retryDeadlock(final String[] sqlQuery) {
        RetryTemplate template = new RetryTemplate();
        TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
        policy.setTimeout(30000L);
        template.setRetryPolicy(policy);
        try {
            template.execute(new RetryCallback<int[]>() {
    
                public int[] doWithRetry(RetryContext context) {
                    LOG.info("Retrying DEADLOCK " + context);
                    return mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(sqlQuery);
                }
            });
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
    
    公共无效写入(列表数据){
    试一试{
    Arraylist idsToDelete=getIdsToDelete();
    String query=“从表1中删除,其中id位于(“+idsToDelete+”);
    试一试{
    mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(查询);
    }catch(org.springframework.dao.deadlocklesserDataAccessException){
    LOG.info(“捕捉到的死锁:+e”);
    retryDeadlock(query);//重试!
    }
    }捕获(例外e){
    日志错误(e);
    }
    }
    public void retryDeadlock(最终字符串[]sqlQuery){
    RetryTemplate=新RetryTemplate();
    TimeoutRetryPolicy policy=新的TimeoutRetryPolicy();
    策略设置超时(30000L);
    template.setRetryPo
    
    public void write(List data){
        try{
            Arraylist idsToDelete = getIdsToDelete();
            String query = "DELETE FROM table1 WHERE id IN ("+ idsToDelete + " )";
            try {
                 mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(query);
            } catch (org.springframework.dao.DeadlockLoserDataAccessException e) {
                 LOG.info("Caught DEADLOCK : " + e);
                 retryDeadlock(query); // Retry them!
            }
           } catch (Exception e) {
               LOG.error(e);
           }
    }
    
    public void retryDeadlock(final String[] sqlQuery) {
        RetryTemplate template = new RetryTemplate();
        TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
        policy.setTimeout(30000L);
        template.setRetryPolicy(policy);
        try {
            template.execute(new RetryCallback<int[]>() {
    
                public int[] doWithRetry(RetryContext context) {
                    LOG.info("Retrying DEADLOCK " + context);
                    return mysqlJdbcTemplate.getJdbcTemplate().batchUpdate(sqlQuery);
                }
            });
        } catch (Exception e1) {
            e1.printStackTrace();
        }
    }
    
    Step1: Delete id_trg from child table;
    Step2: Delete id_src from child table;
    Step3: Delete id from parent table;