rewriteBatchedStatements实现原理
问题定位
业务中部分批量更新操作超时,后排查发现是数据库连接池满了,导致更新失败,原先的理解是批量更新操作应该不会将连接池占满才对,参考⽂献少配置了rewriteBatchedStatements参数,否则即使使⽤批量操作也会⼀条⼀条将请求提交给mysql,google很多⽂章并没有详细解释这个参数是如何起作⽤的,所以特此记录⼀下
问题解决
在jdbc连接上加⼊rewriteBatchedStatements=true即可以实现多条更新语句合并提交给mysql(合并条数需要看batchSize设置)
代码追踪
以下内容以mybatis-plus的updateBatchById来举例(如果直接使⽤jdbc建议跳⾄第七步):
1.batchSize决定了批量提交的上限,默认为1000,每1000条提交⼀次
2.红⾊框中即满1000条flush⼀次,或全部更新语句构建完成flush⼀次;batchSqlSession是通过mybatis-plus的sqlSessionBatch()获取的(具体获取⽅式之后的⽂章我会整理⼀下),获取到的是mybatis-
3.5.0这个jar包的DefualtSqlSession这个实现类,下⾯让我们进⼊flushStatements()⽅法;
3.此处executor为mybatis-3.5.0的CachingExecutor执⾏器,接下来进⼊到flushStatements⽅法中;
4.CachingExecutor中使⽤了delegate这个Executor来处理,那delegate是从哪⾥获取的呢?是从图2 Configuration类中初始化的,由于我们的操作是batchUpdate所以构建了BatchExecutor,那delegate就是BatchExecutor,那我们在进⼊这个类的flushStatements⽅法中
图1 CachingExecutor
图2 Configuration
5.由于BatchExecutor中没有这个实现⽅法,此处调⽤了⽗类BaExecutor⽅法,我们看到调⽤了doFlushStatements⽅法,然后进⼊查看;
6.BatchExecutor中的doFlushStatements只截取了部分内容,因为我们只需要关注uteBatch()⽅法即可,我⽤的是druid连接池,所以调⽤了DruidPooledPreparedStatement的executeBatch()⽅法,进⼊⽅法;
7.此处stmt为StatementImpl的实例,进⼊executeBatch(),调⽤了executeBatchInternal(),此处的executeBatchInternal()是调⽤了ClientPreparedStatement的executeBatchInternal⽅法;
8. executeBatchInternal()⽅法中关键代码处理逻辑如下:
1)第425⾏判断了两个参数,⼀个是rewriteBatchedStatements,也就是我们设置的是否批量提交的参数,另⼀个参数稍后讲解;如果都满⾜条件则进⼊判断中;
2)第427⾏是判断是否为批量inrt,我们这⾥是update,可以直接跳过;
3)第431⾏判断了三个参数,第⼀个参数和上⾯⼀样,第⼆个是判断batchedArgs不为空,也就是必须为PreparedStatement设置参数,第三个判断是设置参数实体的个数,只有超过4次,通俗讲就是⾄少执⾏4次addBatch才可以进⾏批量操作;
4)如果满⾜上述条件则进⼊executePreparedBatchAsMultiStatement,否则进⼊executeBatchSerially
补充说明:
1、batchHasPlainStatements是是否包含plain statement,如果包含则⽆法进⾏批量提交,因为addB
atch的sql未必是相同的
注:第⼀次写博客,有写的不好的地⽅,如果有哪⾥不理解或者不对望指正