Существует три основных способа пакетной обработки данных:
- Повторно выполнять один оператор вставки
-
foreach
соединениеsql
- пакетная обработка
1. Предварительная подготовка
на основеSpring Boot
+ Mysql
, а чтобы опуститьget
/set
,использовалlombok
, видетьpom.xml
.
1.1 Структура таблицы
id
Используйте автоинкремент базы данных.
DROP TABLE IF EXISTS `user_info_batch`;
CREATE TABLE `user_info_batch` (
`id` bigint(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
`user_name` varchar(100) NOT NULL COMMENT '账户名称',
`pass_word` varchar(100) NOT NULL COMMENT '登录密码',
`nick_name` varchar(30) NOT NULL COMMENT '昵称',
`mobile` varchar(30) NOT NULL COMMENT '手机号',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱地址',
`gmt_create` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`gmt_update` timestamp NULL DEFAULT NULL COMMENT '更新时间',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT 'Mybatis Batch';
1.2 Конфигурационный файл проекта
Осторожно, вы могли обнаружить, что база данныхurl
за которым следует абзацrewriteBatchedStatements=true
, какая функция? Не волнуйтесь, я представлю его позже.
# 数据库配置
spring:
datasource:
url: jdbc:mysql://47.111.118.152:3306/mybatis?rewriteBatchedStatements=true
username: mybatis
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
# mybatis
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: cn.van.mybatis.batch.entity
1.3 Класс сущности
@Data
@Accessors(chain = true)
public class UserInfoBatchDO implements Serializable {
private Long id;
private String userName;
private String passWord;
private String nickName;
private String mobile;
private String email;
private LocalDateTime gmtCreate;
private LocalDateTime gmtUpdate;
}
1.4 UserInfoBatchMapper
public interface UserInfoBatchMapper {
/** 单条插入
* @param info
* @return
*/
int insert(UserInfoBatchDO info);
/**
* foreach 插入
* @param list
* @return
*/
int batchInsert(List<UserInfoBatchDO> list);
}
1.5 UserInfoBatchMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="cn.van.mybatis.batch.mapper.UserInfoBatchMapper">
<insert id="insert" parameterType="cn.van.mybatis.batch.entity.UserInfoBatchDO">
insert into user_info_batch (user_name, pass_word, nick_name, mobile, email, gmt_create, gmt_update)
values (#{userName,jdbcType=VARCHAR}, #{passWord,jdbcType=VARCHAR},#{nickName,jdbcType=VARCHAR}, #{mobile,jdbcType=VARCHAR}, #{email,jdbcType=VARCHAR}, #{gmtCreate,jdbcType=TIMESTAMP}, #{gmtUpdate,jdbcType=TIMESTAMP})
</insert>
<insert id="batchInsert">
insert into user_info_batch (user_name, pass_word, nick_name, mobile, email, gmt_create, gmt_update)
values
<foreach collection="list" item="item" separator=",">
(#{item.userName,jdbcType=VARCHAR}, #{item.passWord,jdbcType=VARCHAR}, #{item.nickName,jdbcType=VARCHAR}, #{item.mobile,jdbcType=VARCHAR}, #{item.email,jdbcType=VARCHAR}, #{item.gmtCreate,jdbcType=TIMESTAMP}, #{item.gmtUpdate,jdbcType=TIMESTAMP})
</foreach>
</insert>
</mapper>
1.6 Предварительные данные
Чтобы облегчить тестирование, несколько переменных извлекаются и загружаются заранее.
private List<UserInfoBatchDO> list = new ArrayList<>();
private List<UserInfoBatchDO> lessList = new ArrayList<>();
private List<UserInfoBatchDO> lageList = new ArrayList<>();
private List<UserInfoBatchDO> warmList = new ArrayList<>();
// 计数工具
private StopWatch sw = new StopWatch();
- Для облегчения сборки данных извлекается общедоступный метод.
private List<UserInfoBatchDO> assemblyData(int count){
List<UserInfoBatchDO> list = new ArrayList<>();
UserInfoBatchDO userInfoDO;
for (int i = 0;i < count;i++){
userInfoDO = new UserInfoBatchDO()
.setUserName("Van")
.setNickName("风尘博客")
.setMobile("17098705205")
.setPassWord("password")
.setGmtUpdate(LocalDateTime.now());
list.add(userInfoDO);
}
return list;
}
- Прогрев данных
@Before
public void assemblyData() {
list = assemblyData(200000);
lessList = assemblyData(2000);
lageList = assemblyData(1000000);
warmList = assemblyData(5);
}
2. Несколько раз выполнить один оператор вставки
Может "ленивые" программисты сделают это, очень просто, прямо в оригинальном сингле
insert
вложенный в заявлениеfor
цикл.
2.1 Соответствующий интерфейс картографа
int insert(UserInfoBatchDO info);
2.2 Метод испытаний
Поскольку этот метод слишком медленный, данные сокращаются до
2000
полоска
@Test
public void insert() {
log.info("【程序热身】");
for (UserInfoBatchDO userInfoBatchDO : warmList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
log.info("【热身结束】");
sw.start("反复执行单条插入语句");
// 这里插入 20w 条太慢了,所以我只插入了 2000 条
for (UserInfoBatchDO userInfoBatchDO : lessList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
sw.stop();
log.info("all cost info:{}",sw.prettyPrint());
}
2.3 Время выполнения
- первый раз
-----------------------------------------
ms % Task name
-----------------------------------------
59887 100% 反复执行单条插入语句
- второй раз
-----------------------------------------
ms % Task name
-----------------------------------------
64853 100% 反复执行单条插入语句
- в третий раз
-----------------------------------------
ms % Task name
-----------------------------------------
58235 100% 反复执行单条插入语句
Вставьте так2000
данные, среднее время трех исполнений:60991 ms
.
три,foreach
соединениеSQL
3.1 Соответствующий интерфейс картографа
int batchInsert(List<UserInfoBatchDO> list);
3.2 Метод испытаний
И этот метод, и следующий метод используют
20w
тест данных.
@Test
public void batchInsert() {
log.info("【程序热身】");
for (UserInfoBatchDO userInfoBatchDO : warmList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
log.info("【热身结束】");
sw.start("foreach 拼接 sql");
userInfoBatchMapper.batchInsert(list);
sw.stop();
log.info("all cost info:{}",sw.prettyPrint());
}
3.3 Время выполнения
- первый раз
-----------------------------------------
ms % Task name
-----------------------------------------
18835 100% foreach 拼接 sql
- второй раз
-----------------------------------------
ms % Task name
-----------------------------------------
17895 100% foreach 拼接 sql
- в третий раз
-----------------------------------------
ms % Task name
-----------------------------------------
19827 100% foreach 拼接 sql
Вставьте так20w
данные, среднее время трех исполнений:18852 ms
.
4. Пакетная обработка
путь
mapper
а такжеxml
повторно использованный2.1
.
4.1 rewriteBatchedStatements
параметр
В начале теста я обнаружил, что он был изменен на
Mybatis Batch
Способ подачи не работает.По сути при вставке все равно вставляет записи по одной,и скорость намного меньше оригинала.foreach
соединениеSQL
метод, который является очень ненаучным.
Позже я узнал, что если вы хотите выполнять его в пакетном режиме, подключитеURL
В строку необходимо добавить новый параметр:rewriteBatchedStatements=true
-
rewriteBatchedStatements
Введение параметра
MySql
изJDBC
связанныйurl
добавитьrewriteBatchedStatements
параметры и гарантии5.1.13
Вышеупомянутая версия драйвера обеспечивает высокопроизводительную пакетную вставку.MySql JDBC
Драйверы игнорируют по умолчаниюexecuteBatch()
оператор, поместите набор пакетов, которые мы ожидаем выполнитьsql
Заявление разбивается и отправляется по одномуMySql
В базе данных пакетные вставки на самом деле представляют собой одиночные вставки, что напрямую снижает производительность. только поставитьrewriteBatchedStatements
параметр установлен наtrue
, драйвер поможет вам пакетное выполнениеSQL
. Этот вариантINSERT
/UPDATE
/DELETE
действительны.
4.2 Подготовка партии
- Ручной впрыск
SqlSessionFactory
@Resource
private SqlSessionFactory sqlSessionFactory;
- тестовый код
@Test
public void processInsert() {
log.info("【程序热身】");
for (UserInfoBatchDO userInfoBatchDO : warmList) {
userInfoBatchMapper.insert(userInfoBatchDO);
}
log.info("【热身结束】");
sw.start("批处理执行 插入");
// 打开批处理
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
UserInfoBatchMapper mapper = session.getMapper(UserInfoBatchMapper.class);
for (int i = 0,length = list.size(); i < length; i++) {
mapper.insert(list.get(i));
//每20000条提交一次防止内存溢出
if(i%20000==19999){
session.commit();
session.clearCache();
}
}
session.commit();
session.clearCache();
sw.stop();
log.info("all cost info:{}",sw.prettyPrint());
}
4.3 Время выполнения
- первый раз
-----------------------------------------
ms % Task name
-----------------------------------------
09346 100% 批处理执行 插入
- второй раз
-----------------------------------------
ms % Task name
-----------------------------------------
08890 100% 批处理执行 插入
- в третий раз
-----------------------------------------
ms % Task name
-----------------------------------------
09042 100% 批处理执行 插入
Вставьте так20w
данные, среднее время трех исполнений:9092 ms
.
4.4 Если данных больше
Когда я расширяю данные до100w
час,foreach
соединениеsql
То, как вставка не может быть выполнена, поэтому я могу проверить только время вставки партии.
При тестировании вам нужно только добавить [4.2] в тестовый код, чтобы
list
НарезатьlageList
Просто протестируйте.
- первый раз
-----------------------------------------
ms % Task name
-----------------------------------------
32419 100% 批处理执行 插入
- второй раз
-----------------------------------------
ms % Task name
-----------------------------------------
31935 100% 批处理执行 插入
- в третий раз
-----------------------------------------
ms % Task name
-----------------------------------------
33048 100% 批处理执行 插入
Вставьте так100w
данные, среднее время трех исполнений:32467 ms
.
V. Резюме
пакетная вставка | Объем данных | Среднее время для трех исполнений |
---|---|---|
Вставьте один фрагмент данных в цикл | 2000 | 60991 ms |
foreach соединениеsql
|
20w | 18852 ms |
пакетная обработка | 20w | 9092 ms |
пакетная обработка | 100w | 32467 ms |
- Хотя эффективность циклической вставки одного фрагмента данных чрезвычайно низка, объем кода очень мал, и его можно использовать при небольшом объеме данных, но запрещается использовать при большом объеме данных, и эффективность слишком низкая;
-
foreach
соединениеsql
Способ его использования, есть большие разделы операторов xml и sql для написания, в которых легко ошибиться.Хотя эффективность приемлема, он по-прежнему непригоден при работе с большим объемом данных, поэтому не рекомендуется используй это; - Пакетное выполнение является рекомендуемой практикой, когда вставляется большой объем данных, и это более удобно в использовании.