写在前面: 前段时间使用DataX从Hive同步数据到MySQL,出现了死锁的报错。MySQL目标表是InnoDB,存在唯一索引。DataX的两个关键参数job.setting.speed.channel=2和writeMode=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相关代码解析
直接从WriterRunner的run()方法来看代码。
@Override
public void run() {
···
taskWriter.startWrite(recordReceiver);
···
}
一路进入到这里CommonRdbmsWriter.Task的startWriteWithConnection(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为唯一索引,此时这张表的锁为行锁。
| session1 | session2 |
|---|---|
| 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报错 |
回到我们的场景,如果数据源的重复数据很多,并且顺序混乱,多channel的replace模式极有可能出现死锁。
4 总结
使用DataX同步数据到MySQL需要注意如下几点:
- 注意目标表的唯一索引,如果使用
replace模式,我能想到的场景可能是数据有重复,但又想正常同步。这种情况channel最好设置为1,然后通过增加batchSize来提高吞吐量; - 从原始需求出发,保证数据的唯一性。或者改变唯一索引的用法,在service层再对数据做一次去重处理。