mybatis 批量插入 插件

2017-01-07 更新:这个插件做了重命名、梳理,新的 github 地址为 MyBatis-Batch

可直接从 Maven 中央仓库引用:
<dependency>
<groupId>net.coderbee</groupId>
<artifactId>mybatis-batch</artifactId>
<version>1.1.0</version>
</dependency>

背景

项目中有个设计不合理的表,总共 8 个字段,有 5 个索引,有几个索引字段还是 32 位的字符串。该表数据量已达 1 亿,最近每天新增 100 万。根据日志看,有次用户上传一个有 200 行记录的 excel,需要往这个表插入 3940 条记录,耗时 72 秒。这么大延迟是没法接受的。

要分析数据库方面的问题,首先是找 DBA 分析下表的情况,说跟以往没多大区别,只是跟这个表有关的插入的执行计划很多。因为这个表的批量插入是这样的:

<select id="batchSave" parameterType="java.util.List">
    INSERT INTO TABLE_NAME(ID,NAME) 
<foreach collection="list"item="itm" separator="union all">
    (SELECT #{itm.id}, #{itm.name} FROM DUAL)
</foreach></select>

这是用 MyBatis 对 Oracle 做批量插入的唯一方法。副作用是:假定 List 的最大长度是 N,那么 Oracle 服务器端就可能有 N 个插入的执行计划。这么多执行计划,DBA 也不乐意去分析呀,而且确实生成的每个执行计划都是很简单的。

优化

如果放弃 union all 的方式,则每条记录都需要各提交一次到数据库,显然也不好。

为了解决 N 个执行计划的问题,做到真正的批量插入,只能修改 MyBatis 的执行逻辑,因此就有了这个项目:mybatis-batchinsert-plugin

目前可以批量插入,但不支持返回主键等其他的功能,有空再完善。

我做这个插件主要是希望达到:

  • 1、解决 Oracle 服务器端对一个表做插入的执行计划过多的问题;用 union all 的方式还可能导致硬解析增多,比如需要插入的总记录有 123 条,以 100 条为一批拼接成一条 sql A,那么剩下的 23 条也会拼接成一条 B,而 B 的频率肯定比 A 的频率低很多,因为要做批量插入的记录总数不是固定的,所以余数也不是固定的,容易导致 B 的解析过期,被清除出去,然后又来了条余数 23 的,那么就需要硬解析了。
  • 2、避免用 union all 拼接成一条 sql,导致这条 sql 里的绑定变量过多。
  • 3、兼容 MyBatis 的用法,在使用上更简单了,因为不需要用 MyBatis 的 for 循环,由插件来做循环;用 union all 方式的话,Java 代码要控制批的大小,需要一个循环,MyBatis 里需要一个,用于拼接 union all。
  • 4、一个对于性能有益的是:每次调用 MyBatis 的 insert 语句,都需要调用一次 Connection.prepareStatement 方法,也就需要访问数据库一次,如果对语句没有缓存的话;用插件的话,不管多少记录,只需要一次。
  • 这个插件对性能提升不会很显著,在我们的开发数据库上测试时,以 1000 条为 1 批,已有数据量 160w,插入 40w 数据,用 union all 的方式平均一条需要 32ms,用插件大概是 27ms。


    欢迎关注我的微信公众号: coderbee笔记,可以更及时回复你的讨论。