本次测试用到的环境:
tidb: v8.5.2
nifi:1.28.1
mysql驱动:8.0.33
Apache NiFi 是 Apache 软件基金会的开源数据集成与流处理平台,主要用于 自动化数据流转、数据转换、数据路由。它通过可视化界面配置和管理数据流,能够在不同系统之间安全、高效、可控地移动和处理数据。我们可以使用nifi进行数据库数据同步操作或者使用nifi做相关的数据治理操作。
本次总结主要是在从hive库迁移到tidb数据库过程中,对目前在使用的nifi方式验证过程中遇到的相关问题。
目前遇到了nifi批量insert和upsert速度慢问题:
- nifi执行10000条1批次insert提交,5分钟仅执行18次(单线程)
- nifi执行10000条1批次的upsert提交,5分钟仅13次(单线程)
但是使用jmeter压测结果大概233w每5分钟(单线程),使用的方式为insert into table values (),().... 1万条数据。
针对上述明显差异做如下排查。
一、 insert的优化方式
梳理问题
首先打开tidb的general log,先设置nifi为10条一批次的提交方式,会看到类似一下的日志:
insert into table () values ();
insert into table () values ();
insert into table () values ();
....
insert into table () values ();
commit;
从上面可以看到多次请求一个事务提交的方式。
优化方式一
然而请教了官方售后和售前小伙伴还有参考官方文档中的描述需要再jdbcUrl中增加如下配置:
?useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSqlLimit=10000&prepStmtCacheSize=1000&useConfigs=maxPerformance&rewriteBatchedStatements=true&rewriteBatchedStatements=true
官网描述:https://docs.pingcap.com/zh/tidb/stable/java-app-best-practices/#useserverprepstmts
会看到如下的log信息:
insert into table (name) values (?),(?)(?)(?)(?)(?);
commit
这个时候测试的结果nifi大概每5分钟提交100次;但是这种方式在tidb的日志中会出现如下的错误提示:
[2025/08/18 15:42:04.486 +08:00] [INFO] [conn.go:1184] ["command dispatched failed"] [conn=2881490770] [session_alias=] [connInfo="id:2881490770, addr:100.86.12.242:54106 status:0, collation:utf8_general_ci, user:aa"]
[command=Prep are] [status="inTxn:0, autocommit:0"] [sql="INSERT INTO table (aa, aa1, aa2, zy, aa3, aa4, aa4, aa5, aa6, aa7, aa8, aa9, bb1, bb2, bb3, bb4, bb5, bb6, bb7, bb8, bb9, cc1, cc2, cc3, cc4, cc5, cc6, cc7, cc8,
cc9, dd1, dd2, dd3, dd4, dd5, dd6, dd7, dd8, dd9, ee1, ee2, ee3, ee4, ee5, ee6) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,
?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
,?,?,?,?,?,?,?,?,?,?, ?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? ,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,? ,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?), (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?
,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,? ,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?, ?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?),(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,(len:920380)"] [txn_mode=PESSIMISTIC] [timestamp=0] [err="[executor:1390]Prepared statement contains too many placeholders"]
这种的提示表示太多的占位符,默认最大是65535个。解决这个日志的方式可以减少insert的批次大小,比如减少到1000条每批次。
优化方式二
但是咱们针对客户的需求不好改变的前提,咱们还得找别的方案,所以在多次的调试过程中修改jdbc的参数如下:
?useServerPrepStmts=false&rewriteBatchedStatements=true&allowMultiQuries=true
这种方式是把多条commit转换成insert into table (name) values ('aa'),('bb')....的方式,而不是使用预编译声明的方式。而且这种方式还提高了单线程的insert的速度,这个方式可以达到5分钟133次,即5分钟133万的插入速度,比上面的100万,提高了33%的效率。
二、upsert的优化方式
然而在复杂的业务中,数据的同步不仅有insert,还有变相的治理过程,比如原来的数据保存在hive库中,一个表中的数据可能会出现重复,在同步数据时,使用upsert的方式把设定主键的数据保留唯一。
梳理问题
打开general log,查看nifi生成的upsert语句如下:
insert ... ON DUPLICATE KEY UPDATE
所以使用上述的jdbcurl配置并没有起到任何效果。并且tidblog中会出现INFO的日志:can not prepare multiple statements。
代码优化
那怎么才能使用批量的方式一块提交呢,就是replace into的方式。但是nifi不支持这个replace into的方式。
那就修改源码,上github上拉下来1.28.1的源码,找到PutDatabaseRecord组件的实现代码PutDatabaseRecord.java,找到执行sql的相关代码:org.apache.nifi.processors.standard.PutDatabaseRecord#executeDML,最终定位到mysql的实现代码在src/main/java/org/apache/nifi/processors/standard/db/impl/MySQLDatabaseAdapter.java下
那就修改这个代码为replace into的方式:
重新打包:
使用idea找到nifi-standard-bundle模块,直接使用install即可:
执行完成后会在对应的模块下nifi-standard-nar下的target中看到对应的nar包。
然后打包放到nifi安装目录的lib下,替换原来的nifi-standard-nar-1.28.1.nar。
测试结果
最终测试使用replace的方式,可以达到5分钟96万的速率。
总结
在tidb的使用过程中,可能会使用很多的第三工具用来同步数据,治理数据。但是在开始使用的过程中可能会遇到形形色色的问题。这时候需要我们排查出根本问题来给出相关的优化方案。上面是我在nifi使用过程中的一些优化思路,希望对大家有所帮助。同样感谢咱们tidb售前和售后小伙伴的支持。