同事反馈在使用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类进行批量插入。此时会将插入的数据一次性发到数据库服务器上,大大减少批量插入用时。
其他
数据库服务器本身的好坏、不适合的索引设置等,也会影响批量插入的性能。本文仅在执行语句和数据库连接驱动层面进行了研究,在开发过程中,应根据实际情况选用合适的方案。