MySQL 插入10万条数据性能分析

MySQL 插入10万条数据性能分析

一、背景

笔者想复现一个索引失效的场景,故需要一定规模的数据作支撑,所以需要向数据库中插入大约一百万条数据。那问题就来了,我们应该怎样插入才能使插入的速度最快呢?

为了更加贴合实际,下面的演示只考虑使用 Mybaits 作为 ORM 框架的情况,不使用原生的 JDBC。下面,我们只向数据库中插入十万条数据作为演示。

二、实现

1. 使用 Mybaits 直接插入

Java 代码为:

public void insertByMybatis() {for (int i = 0; i < 100000; i++) {InvoiceOrder invoiceOrder = new InvoiceOrder();invoiceOrder.setOrderId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceName("test" + i);invoiceOrder.setInvoiceDate(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));invoiceOrder.setOrderTime(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));orderMapper.insertSelective(invoiceOrder);}
}

插入结果:

同步插入10万条数数据

同步插入10万条数据的耗时为 242s

2. 使用 Mybatis 直接插入数据,取消事务自动提交

@Autowired
private DataSourceTransactionManager transactionManager;public void insertByMybatisWithNoTransaction() {DefaultTransactionDefinition def = new DefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);TransactionStatus status = transactionManager.getTransaction(def);for (int i = 0; i < 100000; i++) {InvoiceOrder invoiceOrder = new InvoiceOrder();invoiceOrder.setOrderId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceName("test" + i);invoiceOrder.setInvoiceDate(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));invoiceOrder.setOrderTime(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));orderMapper.insertSelective(invoiceOrder);}transactionManager.commit(status);
}

插入结果:

同步插入数据,取消事务自动提交

直接插入数据,并取消事务自动提交,耗时为 28s

3. 使用 Mybatis 批量插入数据

Java 代码为:

/*** Mybatis批量插入*/
public void batchInsertByMybatis() {List<InvoiceOrder> invoiceOrders = new ArrayList<>();for (int i = 0; i < 100000; i++) {InvoiceOrder invoiceOrder = new InvoiceOrder();invoiceOrder.setOrderId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceName("test" + i);invoiceOrder.setInvoiceDate(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));invoiceOrder.setOrderTime(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));invoiceOrders.add(invoiceOrder);if (i % 1500 == 0) {orderMapper.insertBatch(invoiceOrders);invoiceOrders = new ArrayList<>();}}// 最后插入剩下的数据orderMapper.insertBatch(invoiceOrders);
}

结果:

批量插入10万数据

批量插入10条数据耗时 4s

4. 使用 Mybatis 批量插入数据,取消事务自动提交

@Autowired
private DataSourceTransactionManager transactionManager;public void batchInsertByMybatisWithNoTransaction() {DefaultTransactionDefinition def = new DefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);TransactionStatus status = transactionManager.getTransaction(def);List<InvoiceOrder> invoiceOrders = new ArrayList<>();for (int i = 0; i < 100000; i++) {InvoiceOrder invoiceOrder = new InvoiceOrder();invoiceOrder.setOrderId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceName("test" + i);invoiceOrder.setInvoiceDate(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));invoiceOrder.setOrderTime(DateUtil.date().offset(DateField.HOUR_OF_DAY, -i));invoiceOrders.add(invoiceOrder);if ( i % 10000 == 0) {orderMapper.insertBatch(invoiceOrders);invoiceOrders = new ArrayList<>();}}// 最后插入剩下的数据orderMapper.insertBatch(invoiceOrders);transactionManager.commit(status);
}

结果为:

批量插入数据并取消事务自动提交

耗时4s,与批量插入自动提交事务方式的耗时相差不大

5. 使用多线程批量插入数据

public void asyncInsertTest() throws InterruptedException {for (int i = 0; i < THREAD_COUNT; i++) {int finalI = i;threadPoolExecutor.submit(new Runnable() {@Overridepublic void run() {List<InvoiceOrder> invoiceOrders = new ArrayList<>();int begin = finalI * 20000;int end = 20000 * (finalI + 1);for (int id = begin; id < end; id++) {InvoiceOrder invoiceOrder = new InvoiceOrder();invoiceOrder.setId((long) id);invoiceOrder.setOrderId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceId(UUID.randomUUID().toString().replace("-", ""));invoiceOrder.setInvoiceName("test" + id);invoiceOrder.setInvoiceDate(DateUtil.date().offset(DateField.HOUR_OF_DAY, -id));invoiceOrder.setOrderTime(DateUtil.date().offset(DateField.HOUR_OF_DAY, -id));invoiceOrders.add(invoiceOrder);}orderMapper.insertBatch(invoiceOrders);}});}threadPoolExecutor.shutdown();while (!threadPoolExecutor.isTerminated()) {Thread.sleep(100); // 等待线程池中的任务执行完毕}}

结果为:

多线程插入数据

耗时 3s,与同步插入相比,有很大的性能提升,这里的每条线程都是使用批量插入的模式,一次事务提交。

6. 使用 第三方数据库连接池插入数据

SpringBoot 中默认使用的数据库连接池是 Hikari,这里我们换用 Druid 和 c3p0 连接池,同步插入10万条数据。

6.1 使用 Druid 连接池插入数据

Druid 依赖

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version>
</dependency>

配置 Druid 文件信息

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/test_db?characterEncoding=utf-8&serverTimezone=Hongkong&useSSL=falseusername: rootpassword: ezrealtype: com.alibaba.druid.pool.DruidDataSourceserver:port: 8080

结果如下:

使用Druid连接池插入10条数据

  • 与使用 Hikari 连接池相比,Hikari 耗时 242s,Druid 耗时 246s 两者相差不大;
6.2 使用 c3p0 连接池插入数据

c3p0 依赖

<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version>
</dependency>

配置 c3p0 文件信息

spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/test_db?characterEncoding=utf-8&serverTimezone=Hongkong&useSSL=falseusername: rootpassword: ezrealtype: com.alibaba.druid.pool.DruidDataSourceserver:port: 8080

结果为:

c3p0插入10条数据

  • 与使用 Hikari 连接池相比,Hikari 耗时 242s,c3p0 耗时 242s 两者相差不大;

三、结果比较分析

3.1 单次循环插入和批量插入

对比方式 1 和 方式 3,前者是单次循环插入,后者是批量插入,分别耗时 242s 和 4s,显然批量插入的效率高。

这个问题可以分三个方面网络数据库交互事务索引分析:

  1. 网络:客户端每次与数据库交互,都要进行一次 TCP 三次握手和四次挥手,频繁的建立和释放数据库连接增加了数据库负担;
  2. 数据库交互:在建立网络连接后,数据库要进行一系列的准备工作:查询缓存语法分析词法分析、优化器分析、存储引擎执行 SQL;
  3. 事务:每次数据插入操作都会开启一个事务,频繁的开启和提交事务,也会增加数据库的负担;
  4. 索引:如果插入的字段存在对应的二级索引,那么就要在该二级索引上也要添加上对应的数据,涉及到大量的磁盘操作;

对于索引来说,可能会存在页分裂和页合并的情况,比如说插入时数据库的主键不是自增的。

对于单次循环插入,每次都需要重复以上三个方面的内容,非常影响性能;而对于批量插入来说,每次操作大量的数据,减少网络、数据库交互、事务等操作,提高插入的效率;

3.2 事务自动提交分析

对比方式 1 和 方式 2,前者是每次插入就进行一次事务操作,而后者是提前开启事务,等到所有的数据都 insert 后,再提交事务,前者耗时 242s,后者耗时 28s,显然只进行一次事务操作的插入效率高

3.2.1 事务执行流程

回答这个问题之前,我们需要了解一次事务执行的流程,以 insert 操作为例:

  1. 向 buffer pool 中写入数据,将数据写入到 flush 链表中,由后台线程定时同步到磁盘上
  2. 记录 undo log buffer ,数据插入之前,InnoDB 会在 Undo Log 记录对应的 delete 语句,用于在生事务回滚的情况下,将修改操作回滚到插入前的状态,undo log 先写入到 undo log buffer 中,由后台线程定时落盘
  3. 记录 redolog buffer,InnoDB 在 buffer pool 插入数据的同时,会把操作记录写入到 redolog buffer 中;
  4. 提交事务,InnoDB 会把 redo log 从 redolog buffer 写入到磁盘中(顺序写入),此时 redolog 处于 prepare 状态,接着执行器生成这个操作的 binlog 写入磁盘,最后把刚刚 redo log 改为 commit 状态,数据插入成功,这就是所谓的二阶段提交

这里主要涉及到两处内存操作和两处磁盘操作

  • 将 undolog 写入到 undolog buffer 中;

  • 将 redolog 写入到 redolog buffer 中;

  • 在事务提交后,InnoDB 会把 redo log 从 redolog buffer 写入到磁盘中;

  • 将该操作的 binlog 也写入到磁盘中

所以,对于方式1,每一次插入数据都要进行两次的磁盘 IO,然而磁盘的读取速度是非常耗时的,大量的磁盘 IO就会影响插入的性能。如果能够减少大量的磁盘 IO,即减少事务开启的次数,那么就可以大大减少插入的耗时。

3.2.2 事务操作涉及到的锁

涉及到事务就可能会涉及到锁的竞争。一个事务在插入一条记录时需要判断一下插入位置是不是被别的事务加了所谓的 gap lock,如果有的话,插入操作就需要等待,直到拥有 gap lock 的事务释放了锁。

InnoDB 规定,在上述等待过程中,会在内存中生成一个锁结构,表明有事务想在间隙中插入一条新记录,但是现在在等待。所以 InnoDB 就把这种类型的锁名称命名为 Insert Intention Locks ,我们称为插入意向锁

3.3 多线程插入优化

通过并发执行多个插入操作来提高数据插入效率:

  • 并发执行:利用多核处理器的优势,通过多个线程并发执行插入操作,提高系统的吞吐量;
  • 减少锁竞争:多个线程批量插入,类似分段的思想,不同线程只会操作不同的数据段,减少不同线程的锁竞争;

3.4 数据库连接池的选择

至于数据库连接池的选择,这里提供一份大佬写的文章,里面详细比较了常见数据库连接池的性能测试。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/686453.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cool Node后端 中实现中间件的书写

1.需求 在node后端中&#xff0c;想实现一个专门鉴权的文件配置&#xff0c;可以这样来解释 就是 有些接口需要token调用接口&#xff0c;有些接口不需要使用token 调用 这期来详细说明一下 什么是中间件中间件顾名思义是指在请求和响应中间,进行请求数据的拦截处理&#xf…

如何用AI绘画工具最好最省时省事的方法制作个性化头像框?

原文章链接&#xff1a;如何根据游戏素材制作主题头像框&#xff1f;实战教程来了&#xff01; - 优设网 - 学设计上优设 教程专区&#xff1a;AI绘画&#xff0c;AI视频&#xff0c;AI写作等软件类型AI教程&#xff0c; AI工具专区&#xff1a;AI工具-喜好儿aigc 在 APP 的…

「算法」二分查找1:理论细节

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;算法详解 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 二分查找算法简介 这个算法的特点就是&#xff1a;细节多&#xff0c;出错率高&#xff0c;很容易就写成死循环有模板&#xff0c;但…

如何在UI自动化测试中加入REST API的操作

1、问题 当我们描述一个“好的自动化测试用例”时&#xff0c;经常出现标准是&#xff1a; 精确 自动化测试用例应该测试一件事&#xff0c;只有一件事。与测试用例无关的应用程序的某个部分中的错误不应导致测试用例失败。 独立 自动化测试用例不应该受测试套件中任何其他…

PyTorch-线性回归

已经进入大模微调的时代&#xff0c;但是学习pytorch&#xff0c;对后续学习rasa框架有一定帮助吧。 <!-- 给出一系列的点作为线性回归的数据&#xff0c;使用numpy来存储这些点。 --> x_train np.array([[3.3], [4.4], [5.5], [6.71], [6.93], [4.168],[9.779], [6.1…

win32汇编获取系统信息

.data fmt db "页尺寸&#xff1a;%d",0 db "" lpsystem SYSTEM_INFO <?> szbuf db 200 dup(0) .const szCaption db 系统信息,0 .code start: invoke GetSystemInfo,addr lpsystem …

Java编程在工资信息管理中的最佳实践

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

用Java实现简单的图书管理系统

目录 1.总体框架 2.book包 Books类 booklist类 3.operation包 IO接口&#xff1a; addbooks类&#xff1a; borrowbooks类&#xff1a; delbooks类&#xff1a; returnbooks类&#xff1a; exit类&#xff1a; 4.user包 user类 Adminuser类&#xff08;难点&#…

嵌入式linux驱动开发篇之设备树

什么是设备树&#xff1f; 设备树&#xff08;Device Tree&#xff09;是一种用于描述嵌入式系统硬件组件及其连接关系的数据结构。它被广泛用于嵌入式 Linux 系统&#xff0c;尤其是针对使用多种不同架构和平台的嵌入式系统。它是一种与硬件描述相关的中间表示形式&#xff0c…

如何生成狗血短剧

如何生成狗血短剧 狗血短剧剧本将上述剧本转成对话 狗血短剧剧本 标题&#xff1a;《爱的轮回》 类型&#xff1a;现代都市爱情短剧 角色&#xff1a; 1. 林晓雪 - 女&#xff0c;25岁&#xff0c;职场小白&#xff0c;善良单纯 2. 陆子轩 - 男&#xff0c;28岁&#xff0c;公…

WINCC如何新增下单菜单,切换显示页面

杭州工控赖工 首先我们先看一下&#xff0c;显示的效果&#xff0c;通过下拉菜单&#xff0c;切换主显示页面。如图一&#xff1a; 图1 显示效果 第一步&#xff1a; 通过元件新增一个组合框&#xff0c;见图2&#xff1b; 组合框的设置&#xff0c;设置下拉框的长宽及组合数…

Rust 数据结构与算法:1算法分析之乱序字符串检查

Rust 数据结构与算法 一、算法分析 算法是通用的旨在解决某种问题的指令列表。 算法分析是基于算法使用的资源量来进行比较的。之所以说一个算法比另一个算法好,原因就在于前者在使用资源方面更有效率,或者说前者使用了更少的资源。 ●算法使用的空间指的是内存消耗。算法…

基于springboot智慧外贸平台源码和论文

网络的广泛应用给生活带来了十分的便利。所以把智慧外贸管理与现在网络相结合&#xff0c;利用java技术建设智慧外贸平台&#xff0c;实现智慧外贸的信息化。则对于进一步提高智慧外贸管理发展&#xff0c;丰富智慧外贸管理经验能起到不少的促进作用。 智慧外贸平台能够通过互…

神经网络算法原理

目录 得分函数 数学表示 计算方法 损失函数 ​编辑 前向传播 反向传播 ​编辑 整体架构 正则化的作用 数据预处理 ​过拟合解决方法 得分函数 得分函数是在机器学习和自然语言处理中常用的一种函数&#xff0c;用于评估模型对输入数据的预测结果的准确性或匹配程度。…

【Python---六大数据结构】

&#x1f680; 作者 &#xff1a;“码上有前” &#x1f680; 文章简介 &#xff1a;Python &#x1f680; 欢迎小伙伴们 点赞&#x1f44d;、收藏⭐、留言&#x1f4ac; Python---六大数据结构 往期内容前言概述一下可变与不可变 Number四种不同的数值类型Number类型的创建i…

2024年【天津市安全员B证】新版试题及天津市安全员B证复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 天津市安全员B证新版试题参考答案及天津市安全员B证考试试题解析是安全生产模拟考试一点通题库老师及天津市安全员B证操作证已考过的学员汇总&#xff0c;相对有效帮助天津市安全员B证复审考试学员顺利通过考试。 1、…

人工智能学习与实训笔记(七):神经网络之模型压缩与知识蒸馏

人工智能专栏文章汇总&#xff1a;人工智能学习专栏文章汇总-CSDN博客 本篇目录 七、模型压缩与知识蒸馏 7.1 模型压缩 7.2 知识蒸馏 7.2.1 知识蒸馏的原理 7.2.2 知识蒸馏的种类 7.2.3 知识蒸馏的作用 七、模型压缩与知识蒸馏 出于对响应速度&#xff0c;存储大小和能…

(07)Hive——窗口函数详解

一、 窗口函数知识点 1.1 窗户函数的定义 窗口函数可以拆分为【窗口函数】。窗口函数官网指路&#xff1a; LanguageManual WindowingAndAnalytics - Apache Hive - Apache Software Foundationhttps://cwiki.apache.org/confluence/display/Hive/LanguageManual%20Windowing…

【Redis实战】有MQ为啥不用?用Redis作消息队列!?Redis作消息队列使用方法及底层原理高级进阶

&#x1f389;&#x1f389;欢迎光临&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;特别推荐给大家我的最新专栏《Redis实战与进阶》 本专栏纯属为爱发电永久免费&#xff01;&a…

致敬新春“不回家”的厨师,李锦记让厨师的年味更有滋味

“新春饭市万家团圆&#xff0c;致敬千万坚守岗位的厨师” 新春团圆饭向来是餐饮行业最为关注的节点&#xff0c;但过去几年&#xff0c;在疫情与后疫情时期&#xff0c;新年团圆饭市不免冷清。而今年餐饮行业真正迎来“龙抬头”&#xff0c;龙年除夕夜的团圆饭市终于重迎来了…