问题描述:
日志有完整DML内容,但是INSERT数据有时会出现丢失没同步
环境:
env: canal 1.1.5; mysql 5.7.32; jdk1.8
场景: 从mysql同步到mysql
配置描述:有多个表需要同步,在adapter的rdb目录下配了,一个表写一个yml文件
问题分析:
调整canal-adapter日志级别:trace
在源码中增加日志
具体业务与日志分析:
业务需求,表字段有唯一索引,binlog同步过来的顺序为delete,insert。源码默认线程数量为3,因此分析后得出,delete的commit还未提交insert的commit就提交了,造成了sql异常捕获后并未抛出或输出异常,DML日志正常输出造成无法准确定位问题。
引发思考:canal组 亦或是 未考虑到同一表拥有唯一索引时 事务应怎样处理,不能盲目的使用线程池来解决大批量数据同步
问题代码出处
解决方式:
修改rdb配置文件
dataSourceKey: defaultDS
destination: example
groupId: g1
outerAdapterKey: mysql1
concurrent: true
updateChangeColumns: true
dbMapping:
database: mytest
table: rdb_test
targetDb: mytest2
targetTable: rdb_test
targetPk:
id: id
mapAll: true
readBatch: 1
commitBatch: 1 # 批量提交的大小
修改源码(只解决了业务需求,并未全面分析源码)
增加目标表判断
if ("xxxx".equals(dml.getTable())) {
threads = 1;
}
/**
* 批量同步回调
*
* @param dmls 批量 DML
* @param function 回调方法
*/
public void sync(List<Dml> dmls, Function<Dml, Boolean> function) {
try {
boolean toExecute = false;
for (Dml dml : dmls) {
if ("xxxx".equals(dml.getTable())) {
threads = 1;
}
if (!toExecute) {
toExecute = function.apply(dml);
} else {
function.apply(dml);
}
}
if (toExecute) {
List<Future<Boolean>> futures = new ArrayList<>();
for (int i = 0; i < threads; i++) {
int j = i;
if (dmlsPartition[j].isEmpty()) {
// bypass
continue;
}
futures.add(executorThreads[i].submit(() -> {
try {
dmlsPartition[j].forEach(syncItem -> sync(batchExecutors[j],
syncItem.config,
syncItem.singleDml));
dmlsPartition[j].clear();
batchExecutors[j].commit();
return true;
} catch (Throwable e) {
dmlsPartition[j].clear();
batchExecutors[j].rollback();
throw new RuntimeException(e);
}
}));
}
futures.forEach(future -> {
try {
future.get();
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
});
}
} finally {
for (BatchExecutor batchExecutor : batchExecutors) {
if (batchExecutor != null) {
batchExecutor.close();
}
}
}
}
问题源码:

