你好,我是yes。
最近在压测一批接口,发现接口处理速度慢的有点超出预期,感觉很奇怪,后面定位发现是数据库批量保存这块很慢。
这个项目用的是 mybatis-plus,批量保存直接用的是 mybatis-plus 提供的 saveBatch。
我点进去看了下源码,感觉有点不太对劲:
我继续追踪了下,从这个代码来看,确实是 for 循环一条一条执行了 sqlSession.insert,下面的 consumer 执行的就是上面的 sqlSession.insert:
然后累计一定数量后,一批 flush。
从这点来看,这个 saveBach 的性能肯定比直接一条一条 insert 快。
我直接进行一个粗略的实验,简单创建了一张表来对比一波!
粗略的实验
1000条数据,一条一条插入
@Test
void MybatisPlusSaveOne() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
StopWatch stopWatch = new StopWatch();
stopWatch.start("mybatis plus save one");
for (int i = 0; i
复制代码
可以看到,执行一批 1000 条数的批量保存,耗费的时间是 121011 毫秒。
1000条数据用 mybatis-plus 自带的 saveBatch 插入
@Test
void MybatisPlusSaveBatch() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
List openTestList = new ArrayList();
for (int i = 0; i
复制代码
耗费的时间是 59927 毫秒,比一条一条插入快了一倍,从这点来看,效率还是可以的。
然后常见的还有一种利用拼接 sql 方式来实现批量插入,我们也来对比试试看性能如何。
1000条数据用手动拼接 sql 方式插入
搞个手动拼接:
来跑跑下性能如何:
@Test
void MapperSaveBatch() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
List openTestList = new ArrayList();
for (int i = 0; i
复制代码
耗时只有 2275 毫秒,性能比 mybatis-plus 自带的 saveBatch 好了 26 倍!
这时,我又突然回想起以前直接用 JDBC 批量保存的接口,那都到这份上了,顺带也跑跑看!
1000条数据用 JDBC executeBatch 插入
@Test
void JDBCSaveBatch() throws SQLException {
SqlSession sqlSession = sqlSessionFactory.openSession();
Connection connection = sqlSession.getConnection();
connection.setAutoCommit(false);
String sql = "insert into open_test(a,b,c,d,e,f,g,h,i,j,k) values(?,?,?,?,?,?,?,?,?,?,?)";
PreparedStatement statement = connection.prepareStatement(sql);
try {
for (int i = 0; i
复制代码
耗时是 55663 毫秒,所以 JDBC executeBatch 的性能跟 mybatis-plus 的 saveBatch 一样(底层一样)。
综上所述,拼接 sql 的方式实现批量保存效率最佳。
但是我又不太甘心,总感觉应该有什么别的法子,然后我就继续跟着 mybatis-plus 的源码 debug 了一下,跟到了 mysql 的驱动,突然发现有个 if 里面的条件有点显眼:
就是这个叫 rewriteBatchedStatements 的玩意,从名字来看是要重写批操作的 Statement,前面batchHasPlainStatements 已经是 false,取反肯定是 true,所以只要这参数是 true 就会进行一波操作。
我看了下默认是 false。
同时我也上网查了下 rewriteBatchedStatements 参数,好家伙,好像有用!
我直接将 jdbcurl 加上了这个参数:
然后继续跑了下 mybatis-plus 自带的 saveBatch,果然性能大大提高,跟拼接 SQL 差不多!
顺带我也跑了下 JDBC 的 executeBatch ,果然也提高了。
然后我继续 debug ,来探探 rewriteBatchedStatements 究竟是怎么 rewrite 的!
如果这个参数是 true,则会执行下面的方法且直接返回:
看下 executeBatchedInserts 究竟干了什么:
看到上面我圈出来的代码没,好像已经有点感觉了,继续往下 debug。
果然! sql 语句被 rewrite了:
对插入而言,所谓的 rewrite 其实就是将一批插入拼接成 insert into xxx values (a),(b),(c)…这样一条语句的形式然后执行,这样一来跟拼接 sql 的效果是一样的。
那为什么默认不给这个参数设置为 true 呢?
我简单问了下 ChatGPT:
如果批量语句中的某些语句失败,则默认重写会导致所有语句都失败。
批量语句的某些语句参数不一样,则默认重写会使得查询缓存未命中。
服务器托管,北京服务器托管,服务器租用 http://www.fwqtg.net