最近在項目當中,有用到批量新增的操作。總結一下,大概有三種方式來完成這個操作,(1)在業務代碼中循環逐條新增(2)在業務代碼中循環逐漸新增-開啓batch模式(3)使用Mybatis-foreach標籤拼接sql執行,逐條更新操作是在數據庫中執行的,在業務代碼中體現的是一次性更新。下面將通過本地連接MySQL數據庫的方式,測試三種方式之間的差異。
表結構
CREATE TABLE IF NOT EXISTS `role` (
`id` bigint(20) NOT NULL COMMENT 'id',
`name` varchar(100) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '名稱',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
一、在業務代碼中循環插入
public void inert() {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE,false);
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Long start = System.currentTimeMillis();
Role role;
for (int i = 0; i < 10000; i++) {
role = new Role();
role.setId(i);
role.setName("name" + i);
roleMapper.insert(role);
}
log.info("業務代碼循環插入10000條數據耗時={}" + (System.currentTimeMillis() - start));
}
對應Mybatis-Mapper配置
<insert id="insert" parameterType="com.example.transaction.dto.Role" >
insert into role (id, name)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR})
</insert>
二、在業務代碼中循環新增-開啓批處理
public void batchInsert() {
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Long start = System.currentTimeMillis();
Role role;
for (int i = 0; i < 10000; i++) {
role = new Role();
role.setId(i);
role.setName("name" + i);
roleMapper.insert(role);
}
sqlSession.commit();
log.info("(使用批處理)業務代碼循環插入1000000條數據耗時={}" + (System.currentTimeMillis() - start));
}
對應的mapper配置一樣
<insert id="insert" parameterType="com.example.transaction.dto.Role" >
insert into role (id, name)
values (#{id,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR})
</insert>
另外,jdbc連接時是加上rewriteBatchedStatements=true纔是真正開啓批處理。
spring:
datasource:
name: test
url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8&rewriteBatchedStatements=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
總結:Mybatis的ExecutorType有三種(REUSE、SIMPLE、BATCH),默認是SIMPLE,這種模式會爲每個語句的執行創建新的預處理,單條提交。而BATCH模式把SQL語句發送到數據庫後,數據庫預處理SQL,只打印一次SQL語句,多次設置參數。
三、Mybatis-foreach-sql拼接方式
@GetMapping("insert")
public void inert(@RequestParam("count") Integer count) {
Long start = System.currentTimeMillis();
List<Role> list = new ArrayList<>();
Role role;
for (int i = 0; i < count; i++) {
role = new Role();
role.setId(i);
role.setName("name" + i);
list.add(role);
}
roleMapper.insertBatch(list);
log.info("sql使用foreach方式循環插入{}條數據耗時={}", count, (System.currentTimeMillis() - start));
}
SQL語句如下:
<insert id="insertBatch" parameterType="java.util.List" >
insert into role (id, name)
values
<foreach collection="list" item="role" separator=",">
(#{role.id,jdbcType=INTEGER}, #{role.name,jdbcType=VARCHAR})
</foreach>
</insert>
關於foreach標籤的使用
item:將當前遍歷出的元素賦值給指定的變量
index:索引。遍歷list的時候是index就是索引,item就是當前值
遍歷map的時候index表示的就是map的key,item就是map的值
#{變量名}就能取出變量的值也就是當前遍歷出的元素
在本次測試過程中,使用的數據庫版本是5.7.19,單條sql的最大數據量是4194304,故當數據量太大時,需調整MySQL的配置。(1)可通過MySQL安裝目錄下的my.ini文件修改單條sql的數據量,在‘mysqld’段上添加max_allowed_packet = 20M,(2)或者通過命令行set global max_allowed_packet = 20*1024*1024,然後退出命令行,重啓MySQL服務即可。
在這次測試過程中,當插入100萬條數據時,就會有問題,需調整配置。
四、結果比較
毫秒 | 業務代碼循環新增 | batch批處理 | foreach-sql拼接 |
10000條 | 17057 | 495 | 576 |
100000條 | 159297 | 1095 | 3265 |
1000000條 | 1912256 | 17666 | 36304 |
總結:當數據量比較大時,使用BATCH批處理方式執行批量操作的效率會高很多。