MySQL之批量插入的4种方案总结
作者:莫轻言舞 发布时间:2024-01-19 16:13:11
一、前言
最近趁空闲之余,在对MySQL数据库进行插入数据测试,对于如何快速插入数据的操作无从下手,在仅1W数据量的情况下,竟花费接近47s,实在不忍直视!在不断摸索之后,整理出一些较实用的方案。
二、准备工作
测试环境:SpringBoot项目、MyBatis-Plus框架、MySQL8.0.24、JDK13
前提:SpringBoot项目集成MyBatis-Plus上述文章有配置过程,同时实现IService接口用于进行批量插入数据操作saveBatch()方法
1、Maven项目中pom.xml文件引入的相关依赖如下
<dependencies>
<!-- SpringBoot Web模块依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MyBatis-Plus 依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.1</version>
</dependency>
<!-- 数据库连接驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- 使用注解,简化代码-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2、application.yml配置属性文件内容(重点:开启批处理模式)
server:
端口号
port: 8080
# MySQL连接配置信息(以下仅简单配置,更多设置可自行查看)
spring:
datasource:
连接地址(解决UTF-8中文乱码问题 + 时区校正)
(rewriteBatchedStatements=true 开启批处理模式)
url: jdbc:mysql://127.0.0.1:3306/bjpowernode?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
用户名
username: root
密码
password: xxx
连接驱动名称
driver-class-name: com.mysql.cj.jdbc.Driver
3、Entity实体类(测试)
/**
* Student 测试实体类
*
* @Data注解:引入Lombok依赖,可省略Setter、Getter方法
*/
@Data
@TableName(value = "student")
public class Student {
/** 主键 type:自增 */
@TableId(type = IdType.AUTO)
private int id;
/** 名字 */
private String name;
/** 年龄 */
private int age;
/** 地址 */
private String addr;
/** 地址号 @TableField:与表字段映射 */
@TableField(value = "addr_num")
private String addrNum;
public Student(String name, int age, String addr, String addrNum) {
this.name = name;
this.age = age;
this.addr = addr;
this.addrNum = addrNum;
}
}
4、数据库student表结构(注意:无索引)
三、测试工作
简明:完成准备工作后,即对for循环、拼接SQL语句、批量插入saveBatch()、循环插入+开启批处理模式,该4种插入数据的方式进行测试性能。
注意:测试数据量为5W、单次测试完清空数据表(确保不受旧数据影响)
( 以下测试内容可能受测试配置环境、测试规范和数据量等诸多因素影响,读者可自行结合参考进行测试 )
1、for循环插入(单条)(总耗时:177秒)
总结:测试平均时间约是177秒,实在是不忍直视(捂脸),因为利用for循环进行单条插入时,每次都是在获取连接(Connection)、释放连接和资源关闭等操作上,(如果数据量大的情况下)极其消耗资源,导致时间长。
@GetMapping("/for")
public void forSingle(){
// 开始时间
long startTime = System.currentTimeMillis();
for (int i = 0; i < 50000; i++){
Student student = new Student("李毅" + i,24,"张家界市" + i,i + "号");
studentMapper.insert(student);
}
// 结束时间
long endTime = System.currentTimeMillis();
System.out.println("插入数据消耗时间:" + (endTime - startTime));
}
(1)第一次测试结果:190155 约等于 190秒
(2)第二次测试结果:175926 约等于 176秒(服务未重启)
(3)第三次测试结果:174726 约等于 174秒(服务重启)
2、拼接SQL语句(总耗时:2.9秒)
简明:拼接格式:insert into student(xxxx) value(xxxx),(xxxx),(xxxxx).......
总结:拼接结果就是将所有的数据集成在一条SQL语句的value值上,其由于提交到服务器上的insert语句少了,网络负载少了,性能也就提上去。但是当数据量上去后,可能会出现内存溢出、解析SQL语句耗时等情况,但与第一点相比,提高了极大的性能。
@GetMapping("/sql")
public void sql(){
ArrayList<Student> arrayList = new ArrayList<>();
long startTime = System.currentTimeMillis();
for (int i = 0; i < 50000; i++){
Student student = new Student("李毅" + i,24,"张家界市" + i,i + "号");
arrayList.add(student);
}
studentMapper.insertSplice(arrayList);
long endTime = System.currentTimeMillis();
System.out.println("插入数据消耗时间:" + (endTime - startTime));
}
// 使用@Insert注解插入:此处为简便,不写Mapper.xml文件
@Insert("<script>" +
"insert into student (name,age,addr,addr_num) values " +
"<foreach collection='studentList' item='item' separator=','> " +
"(#{item.name},{item.age},{item.addr},{item.addrNum}) " +
"</foreach> " +
"</script>")
int insertSplice(@Param("studentList") List<Student> studentList);
(1)第一次测试结果:3218 约等于 3.2秒
(2)第二次测试结果:2592 约等于 2.6秒(服务未重启)
(3)第三次测试结果:3082 约等于 3.1秒(服务重启)
3、批量插入saveBatch(总耗时:2.7秒)
简明:使用MyBatis-Plus实现IService接口中批处理saveBatch()方法,对底层源码进行查看时,可发现其实是for循环插入,但是与第一点相比,为什么性能上提高了呢?因为利用分片处理(batchSize = 1000) + 分批提交事务的操作,从而提高性能,并非在Connection上消耗性能。
@GetMapping("/saveBatch1")
public void saveBatch1(){
ArrayList<Student> arrayList = new ArrayList<>();
long startTime = System.currentTimeMillis();
// 模拟数据
for (int i = 0; i < 50000; i++){
Student student = new Student("李毅" + i,24,"张家界市" + i,i + "号");
arrayList.add(student);
}
// 批量插入
studentService.saveBatch(arrayList);
long endTime = System.currentTimeMillis();
System.out.println("插入数据消耗时间:" + (endTime - startTime));
}
(1)第一次测试结果:2864 约等于 2.9秒
(2)第二次测试结果:2302 约等于 2.3秒(服务未重启)
(3)第三次测试结果:2893 约等于 2.9秒(服务重启)
重点注意:MySQL JDBC驱动默认情况下忽略saveBatch()方法中的executeBatch()语句,将需要批量处理的一组SQL语句进行拆散,执行时一条一条给MySQL数据库,造成实际上是分片插入,即与单条插入方式相比,有提高,但是性能未能得到实质性的提高。
测试:数据库连接URL地址缺少 rewriteBatchedStatements = true 参数情况
# MySQL连接配置信息
spring:
datasource:
连接地址(未开启批处理模式)
url: jdbc:mysql://127.0.0.1:3306/bjpowernode?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
用户名
username: root
密码
password: xxx
连接驱动名称
driver-class-name: com.mysql.cj.jdbc.Driver
测试结果:10541 约等于 10.5秒(未开启批处理模式)
4、循环插入 + 开启批处理模式(总耗时:1.7秒)(重点:一次性提交)
简明:开启批处理,关闭自动提交事务,共用同一个SqlSession之后,for循环单条插入的性能得到实质性的提高;由于同一个SqlSession省去对资源相关操作的耗能、减少对事务处理的时间等,从而极大程度上提高执行效率。(目前个人觉得最优方案)
@GetMapping("/forSaveBatch")
public void forSaveBatch(){
// 开启批量处理模式 BATCH 、关闭自动提交事务 false
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH,false);
// 反射获取,获取Mapper
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
long startTime = System.currentTimeMillis();
for (int i = 0 ; i < 50000 ; i++){
Student student = new Student("李毅" + i,24,"张家界市" + i,i + "号");
studentMapper.insertStudent(student);
}
// 一次性提交事务
sqlSession.commit();
// 关闭资源
sqlSession.close();
long endTime = System.currentTimeMillis();
System.out.println("总耗时: " + (endTime - startTime));
}
(1)第一次测试结果:1831 约等于 1.8秒
(2)第二次测试结果:1382 约等于 1.4秒(服务未重启)
(3)第三次测试结果:1883 约等于 1.9秒(服务重启)
来源:https://blog.csdn.net/yuechuzhixing/article/details/127726271
猜你喜欢
- 这篇博客对于考公人或者其他用华图或者粉笔做题的人比较友好,通过输入网址可以自动化获取华图以及粉笔练习的错题。粉笔网站我们从做过的题目组中获取
- 在使用出colab进行模型训练时,发现colab的python版本更新为了3.7.11,而我的代码要在python3.6下才行配置好环境,于
- PYQT5中给listwidget的滚动条添加滚动信号self.listWidget.verticalScrollBar().valueCh
- 这篇文章主要介绍了Python遍历字典方式就实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友
- 折线图是数据分析中非常常用的图形。其中,折线图主要是以折线的上升或下降来表示统计数量的增减变化的统计图。用于分析自变量和因变量之间的趋势关系
- 表单在提交前我们通常会用客户端JS对其内容进行验证,通常都是写一个函数然后在onsumbit事件中调用,如下:<html><
- 一、前言1.1.环境python版本:3.6Django版本:1.11.61.2.预览效果最终搭建的blog的样子,基本上满足需求了。框架搭
- 世界疫情数据下载请点击》》:疫情数据下载注:此数据是2022年3月12号的结果,其中透明的地方代表确诊人数小于10万人,白色的地方代表无该国
- 注意,在改变数值之前锁定应用,确保一段时间里只有一个客户执行该语句。<SCRIPT LANGUAGE="VBScr
- 完成人机猜拳互动游戏的开发,用户通过控制台输入实现出拳,电脑通过程序中的随机数实现出拳,每一局结束后都要输出结果。当用户输入n时停止游戏,并
- MaxDB和MySQL是独立的数据库管理服务器。系统间的协同性是可能的,通过相应的方式,系统能够彼此交换数据。要想在MaxDB和MySQL之
- MySQL5.6主从复制(读写分离)教程1、MySQL5.6开始主从复制有两种方式:基于日志(binlog);基于GTID(全局事务标示符)
- 如何用WSH获取机器的IP配置信息?我们用VBSCRIPT转换了: Option Explicit Dim&n
- 序言这次玩次狠得。除了编译器使用yum安装,其他全部手动编译。哼~看似就Nginx、PHP、MySql三个东东,但是它们太尼玛依赖别人了。没
- 1. auth介绍Django 自带一个用户验证系统。它负责处理用户账号、组、权限和基于cookie的用户会话。认证系统由以下部分
- 写在之前在我们的现实生活中,「日志记录」其实是一件非常重要的事情,比如银行的转账记录,汽车的行车记录仪记录行驶过程中的一切,如果出现了什么问
- 箱线图箱线图一般用来展现数据的分布,如上下四分位值、中位数等,也可以直观地展示异常点。Matplotlib提供了boxplot()函数绘制箱
- 【导语】:对自己写的冗长代码,想重构但又无思路?小编整理了系列介绍python代码重构优化的方法,助你一臂之力。编写干净的 Pythonic
- jupyter notebook安装完成之后需要密码,还有某些情况下也会出现需要输入密码的情况解决方法如下:1、在运行界面输入 jupyte
- 示意图:html:(模态框等 html和js代码,参考:Django 创建/删除用户){# 权限管理 #} <div id