Mysql:一个rewriteBatchedStatements参数导致线上数据库批量更新只能更新一条语句?

3,509 阅读3分钟

rewriteBatchedStatements参数

rewriteBatchedStatements是一个Mysql的参数

原理

批处理的思想

没有配置该参数的时候,一条更新(update、insert)就代表了向Mysql发送一条语句

可以实现多条更新语句合并提交给mysql,他会把多条更新语句,拼接成一条语句发送给mysql

效果对比

image.png

如何开启

数据库连接中的设置URL把参数加上即可

查看日志

没有报错信息

故障排查过程

经过排查,发现是业务更新数据库走的是jdbcTemplate.batchUpdate,并且加了rewriteBatchedStatements这个参数才导致更新失败

那么我们仔细看看batchUpdate这个方法。

当我们继续往里面跟,就会直接去到第三方源码里面了:

JdbcTemplate.batchUpdate

这边我们可以很清晰地看到在Spring的JdbcTemplate是注册了一个PreparedStatement回调函数。

image-20221104095859422

这个回调函数其实就是Spring用来接受数据库语句执行后的结果。

我们再进下一层ps.executeBatch

PreparedStatement.executeBatch

看到这里其实已经能看到我们这个参数rewriteBatchedStatements判断是否开启的地方了

抑或是我们可以通过直接搜索这个参数,去看引用他的地方,也非常好定位

同样也能定位到刚刚那个地方。

我们可以看到这个参数要起作用还要满足一些条件:

  • 版本号要大于4.1.0
  • 要有更新数据
  • 并且数量要大于3条sql

否则还是去走一条一条sql。

到这里,我们再进下一层executePrepareBatchAsMultiStatement

PreparedStatement.executePrepareBatchAsMultiStatement

看到开头我就非常敏感,他这里是直接拼接一个分号;”到语句里面。而且我们看到他的注释:

“这是一种比较滥用的方式,但是确实能够解决问题”

异常出现的地方

总结

因为框架里面会给我们的语句添加一个分号 ; 如果我们在原语句也有分号,那么就会有2个分号,引起语法错误

image.png

  • 加上参数rewriteBatchedStatements能很大程度上提高我们对数据批量写入的能力
  • 注意原sql语句结尾不要带分号;

剩余问题

  • 为什么没有报错信息
  • 为什么批量更新只有一行数据能更新成功

问题1:无报错信息

这边是捕获DataACcessEXception异常,我看可以看看他的继承关系图:

然后我们去看看框架里面batchUpdate,捕获的是也是DataAccessException异常。

我们再进下一里面一层查看,直接去看他在接口的定义,发现他抛的是SQLException

查看的继承关系图:

这2个异常八竿子打不着,所以我们修改为抛父类的异常,这样在应用层面就能捕获到异常

修改完了之后查看一下报错信息:

image.png

不过我们一般不能直接写e.printStackTrace,好像是在阿里的Java规范手册里面说禁止这种写法。

改成这种会比较好

 def updateBatch(sql: String, argList: List[Array[Any]]): Unit = {
   FrequentChecker.sqlRecord(sql)
   try {
     jdbcTemplate.batchUpdate(sql, argList.map(_.map(_.asInstanceOf[AnyRef])).asJava)
   } catch {
     case e: Exception => logger.error(s"updateBatch error, sql: ${sql}, error message: ${e.getMessage}")
   }
 }

问题2:只有一行数据能更新成功

在问题1的时候已经告诉了我们答案

出现语法问题,说白了就是上一条sql语句有2个分号,最后的那个分号跑到下一条sql语句了,导致出现语法错误了。