KOCA-JDBC批量插入(batch)性能测试及优化建议

同事反馈在使用koca-jdbc中的批量操作时,性能存在问题。

koca-jdbc的批量操作调用了Jdbctemplate的批量操作方法,在实际执行中,Jdbctemplate直接调用底层Statement的executeBatch()方法。该方法由各个数据库厂商实现,各个数据库间存在差异。

因此针对三种常用数据库(Oracle、mysql、SQLserver)进行批量插入(batch)的性能测试,并尝试进行性能优化。

mysql测试结果:

按原有配置不进行优化:

单次批量插入数据条数:100 平均用时:274.664 ± 465.631 ms/op
单次批量插入数据条数:1000 平均用时:2574.573 ± 3094.136 ms/op
单次批量插入数据条数:10000 平均用时:26478.955 ± 12363.382 ms/op

添加连接参数rewriteBatchedStatements=true优化:

在配置连接属性时添加参数rewriteBatchedStatements=true,例如:

jdbc:mysql://127.0.0.1:3306/kocatest1?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC&allowMultiQueries=true&rewriteBatchedStatements=true

添加该参数进行优化后,测试结果如下:

单次批量插入数据条数:100 平均用时:6.741 ± 2.834 ms/op
单次批量插入数据条数:1000 平均用时:29.511 ± 10.488 ms/op
单次批量插入数据条数:10000 平均用时:226.710 ± 184.052 ms/op
单次批量插入数据条数:100000 平均用时:3427.091 ± 3441.168 ms/op

分析及建议:

由上述结果可见,使用rewriteBatchedStatements参数对mysql的batch操作进行优化是有效的,插入数量相同情况下,性能约提升100倍。
该参数默认为false,设置为true时,会将batch的数据拼接为一个SQL,一次性在数据库中插入。

oracle测试结果:

按原有方式不进行优化:

单次批量插入数据条数:100 平均用时:6.683 ± 5.234 ms/op
单次批量插入数据条数:1000 平均用时:28.906 ± 114.268 ms/op
单次批量插入数据条数:10000 平均用时:341.361 ± 3321.006 ms/op
单次批量插入数据条数:100000 平均用时:2321.217 ± 5584.818 ms/op

拼接字符串,将批量插入的数据全部拼接为一个SQL执行:

由于oracle不支持values后跟多个值的情况,例如

insert into user (id,name,age,email) values (1,"zhang",12,"1@s.com"),(2,"lis",12,"2@s.com");

因此测试时拼接的字符串格式为:

insert all 
into user_test (id,name,age,email) values (1,"zhang",12,"1@s.com")
into user_test (id,name,age,email) values (2,"lis",12,"2@s.com")
...

测试结果为:
单次批量插入数据条数:100 平均用时:30.986 ± 9.973 ms/op
单次批量插入数据条数:1000 平均用时:1114.319 ± 1784.925 ms/op
单次批量插入数据条数:3000 平均用时:14018.944 ± 2488.658 ms/op

此测试最大batch数只有3000,是因为数据量超过4000时,执行会直接卡住,既不成功也不失败。

分析及建议:

由上述结果可见,oracle本身batch的性能很好,拼接字符串执行反而会降低性能。

在查阅源码时,发现底层发包的doRpc方法只会调用一次,猜测oracle在执行batch操作时,会将所有数据一次性发送到数据库服务器上。

对比mysql优化后的执行时间,batch数量相同时,优化后的mysql执行时间与oracle原本batch的执行时间接近,这一结果佐证了oracle在batch时不会频繁和数据库服务器产生交互的推想。

综上所述,认为oracle的批量插入直接使用即可,不需要额外优化。

SQLServer测试结果:

按原有方式不进行优化:

单次批量插入数据条数:100 平均用时:97.333 ± 224.673 ms/op
单次批量插入数据条数:1000 平均用时:1056.959 ± 1420.726 ms/op
单次批量插入数据条数:10000 平均用时:9274.557 ± 9477.218 ms/op
单次批量插入数据条数:100000 平均用时:94328.988 ± 12038.637 ms/op

拼接字符串,将批量插入的数据全部拼接为一个SQL执行:

拼接的SQL格式为:

insert into user (id,name,age,email) values (1,"zhang",12,"1@s.com"),(2,"lis",12,"2@s.com");

测试结果为:

单次批量插入数据条数:100 平均用时:5.215 ± 4.354 ms/op
单次批量插入数据条数:1000 平均用时:138.436 ± 16.014 ms/op

此处批量插入最大的数量为1000,因为SQLserver数据库的insert…values语句允许携带的最大数据量是1000,单次超过1000条会报错。

添加连接参数useBulkCopyForBatchInsert=true进行优化:

在配置连接属性时添加参数useBulkCopyForBatchInsert=true,例如:

jdbc:sqlserver://127.0.0.1:1433;SelectMethod=cursor;database=koca_admin_dev;useBulkCopyForBatchInsert=true

添加该参数进行优化后,测试结果如下:

单次批量插入数据条数:100 平均用时:8.611 ± 0.833 ms/op
单次批量插入数据条数:1000 平均用时:32.905 ± 38.739 ms/op
单次批量插入数据条数:10000 平均用时:228.908 ± 1180.083 ms/op
单次批量插入数据条数:100000 平均用时:2787.956 ± 10517.221 ms/op

分析与建议:

由上三种批量插入方式可看出,添加连接参数进行优化时,用时最短,约是不优化时的50倍。

该参数设置为true,在SQLserver执行batch操作时,如果SQL类型是插入(insert),则会使用SQLServerBulkCopy类进行批量插入。此时会将插入的数据一次性发到数据库服务器上,大大减少批量插入用时。

其他

数据库服务器本身的好坏、不适合的索引设置等,也会影响批量插入的性能。本文仅在执行语句和数据库连接驱动层面进行了研究,在开发过程中,应根据实际情况选用合适的方案。

2 个赞

高斯数据库(GaussDB)jdbc连接参数优化:
reWriteBatchedInserts=true&batchMode=off
但是高斯数据库需要区分是分布式还是集中式,分布式的情况下,在项目中遇到过开启jdbc优化比不开启性能还差的情况