DataX同步数据至MySQL出现死锁

4,709 阅读2分钟

写在前面: 前段时间使用DataX从Hive同步数据到MySQL,出现了死锁的报错。MySQL目标表是InnoDB,存在唯一索引。DataX的两个关键参数job.setting.speed.channel=2writeMode=replace。本文通过描述InnoDB的锁原理以及DataX的代码解析,来解释为何会出现死锁。

1 DataX配置说明

目前所在公司使用的平台封装了DataX,用于实现数据同步功能。平台内有两个默认配置:

job.setting.speed.channel=2 表示有两个通道同时写数据至MySQL;

writeMode=replace 表示MySQL以replace into的方式写数据。

DataX对于writeMode=replace有这样一段描述:

(没有遇到主键/唯一性索引冲突时,与 insert into 行为一致,冲突时会用新行替换原有行所有字段) 的语句写入数据到 Mysql。出于性能考虑,采用了 `PreparedStatement + Batch`,并且设置了:`rewriteBatchedStatements=true`,将数据缓冲到线程上下文 Buffer 中,当 Buffer 累计到预定阈值时,才发起写入请求。

这说明DataX使用了批量写的方式来写入MySQL。

2 DataX相关代码解析

直接从WriterRunnerrun()方法来看代码。

    @Override
    public void run() {
        ···
        taskWriter.startWrite(recordReceiver);
        ···
    }

一路进入到这里CommonRdbmsWriter.TaskstartWriteWithConnection(recordReceiver, taskPluginCollector, connection);方法中。

    public void startWriteWithConnection(RecordReceiver recordReceiver, TaskPluginCollector taskPluginCollector, Connection connection) {
        ···
        if (writeBuffer.size() >= batchSize || bufferBytes >= batchByteSize) {
            doBatchInsert(connection, writeBuffer);
            writeBuffer.clear();
            bufferBytes = 0;
        }
        ···
    }

batchSize为1024,是MysqlWriter的默认配置。进入doBatchInsert(connection, writeBuffer)方法看一下。

    protected void doBatchInsert(Connection connection, List<Record> buffer)
            throws SQLException {
        ···
        connection.setAutoCommit(false);
        preparedStatement = connection
                .prepareStatement(this.writeRecordSql);

        for (Record record : buffer) {
            preparedStatement = fillPreparedStatement(
                    preparedStatement, record);
            preparedStatement.addBatch();
        }
        preparedStatement.executeBatch();
        connection.commit();
        ···
    }

由于InnoDB的update、delete、insert、replace都会自动给涉及到的数据加上排他锁,此处的connection.setAutoCommit(false)将事务设置为手动提交,每次批量写完数据后再手动commit。死锁就出现在这块儿逻辑。

3 死锁解析

想要了解详细的MySQL锁机制可以先看我的这篇文章juejin.cn/post/686043…

简单来看一个死锁过程,假设有mock_table这样一张表,id为唯一索引,此时这张表的锁为行锁。

session1session2
set autocommit=0;set autocommit=0;
replace into mock_table where id = 1;replace into mocl_table where id = 2;
replace into mock_table where id = 2;
此处会被阻塞
replace into mock_table where id = 1;
出现Deadlock报错

回到我们的场景,如果数据源的重复数据很多,并且顺序混乱,多channelreplace模式极有可能出现死锁。

4 总结

使用DataX同步数据到MySQL需要注意如下几点:

  • 注意目标表的唯一索引,如果使用replace模式,我能想到的场景可能是数据有重复,但又想正常同步。这种情况channel最好设置为1,然后通过增加batchSize来提高吞吐量;
  • 从原始需求出发,保证数据的唯一性。或者改变唯一索引的用法,在service层再对数据做一次去重处理。