MySQL 大量数据插入优化

效率最好的方式是:批量插入 + 开启事务。

1、数据批量插入相比数据逐条插入的运行效率得到极大提升;

## 批量插入
INSERT INTO `table` (`field1`, `field12`,...) VALUES ('valuea1', 'valuea2',...), ('valueb1', 'valueb2',...),...;

当数据逐条插入时,每条插入操作都需要进行一次数据库连接和一次磁盘写入操作,这会导致频繁的网络通信和磁盘 I/O 开销。如果有大量的数据需要插入,这些额外的开销会导致插入速度变慢,降低整体的运行效率。

相比之下,批量插入将多条数据合并为一个批次进行插入。通过一次数据库连接和一次磁盘写入操作,可以将多条数据一次性插入到数据库中。这样可以减少网络通信次数和磁盘 I/O 操作次数,大大提高了数据插入的效率。

批量插入的效率提升主要有以下几个方面的原因:

  1. 减少网络通信开销:批量插入可以通过一次数据库连接和一次传输操作将多条数据发送给数据库,减少了网络通信的次数和开销。
  2. 减少磁盘 I/O 操作:批量插入将多条数据合并为一个写入操作,减少了磁盘的读写次数,降低了磁盘 I/O 的开销。
  3. 优化事务管理:批量插入可以将多条插入操作合并为一个事务,减少了事务的开启和提交次数,提高了事务管理的效率。

2、数据逐条插入时,显示开启事务相比无事务的运行效率得到极大提升;

MySQL 每条插入操作,都会在内部建立一个隐式事务,在这个事务内进行真正的插入操作,所以逐条插入需要不停的创建事务和提交事务,造成较大的开销;显示开启事务,将多条插入操作放在同一个事务内,等都执行完再提交事务,可以减少创建和提交事务的次数,从而降低消耗。

## 没有开启事务
INSERT INTO `table` (`field1`, `field12`,...) VALUES ('value1', 'value2',...);
INSERT INTO `table` (`field1`, `field12`,...) VALUES ('value1', 'value2',...);

## 开启事务
START TRANSACTION;
INSERT INTO `table` (`field1`, `field12`,...) VALUES ('value1', 'value2',...);
INSERT INTO `table` (`field1`, `field12`,...) VALUES ('value1', 'value2',...);
COMMIT;

当数据逐条插入时,使用事务可以显著提升运行效率。这是因为事务的特性可以减少磁盘 I/O 操作,从而减少数据库引擎与磁盘之间的交互次数,提高数据插入的性能。

在无事务的情况下,每次插入一条数据都会立即写入磁盘,这导致了频繁的磁盘 I/O 操作。每次写入磁盘都包括了寻址、数据传输和磁盘写入等操作,这些操作的开销很大,会显著降低数据插入的速度。

而在开启事务的情况下,可以将多条插入操作打包成一个事务,然后一次性提交到数据库。这样可以将多个插入操作合并为一个写入磁盘的过程,减少了磁盘 I/O 操作的次数。数据库引擎可以优化事务的提交过程,将数据缓存在内存中并批量写入磁盘,从而减少了磁盘访问的次数,提高了插入操作的效率。

因此,对于大量逐条插入数据的场景,使用事务可以极大地提升运行效率,减少磁盘 I/O 操作,加快数据的插入速度。

3、INSERT 操作涉及磁盘 I/O 操作的原因主要有两个方面

数据持久化:INSERT 操作的目的是将新的数据插入到数据库中,以保持数据的持久性。为了实现持久化,数据库引擎需要将插入的数据写入磁盘,将其保存在物理存储介质上(如硬盘或固态硬盘)。这涉及到将数据从内存中的缓冲区或日志文件写入到磁盘的过程,即磁盘 I/O 操作。

索引更新:如果表中定义了索引,那么在执行 INSERT 操作时,数据库引擎还需要相应地更新索引数据结构。索引是用于提高数据库查询性能的数据结构,它们存储在磁盘上并与表的数据分开存储。当插入新的数据时,数据库引擎需要更新索引,以确保索引的正确性和查询的准确性。这也涉及到磁盘 I/O 操作。

4、在MySQL中,默认情况下,每个SQL语句都自动开启一个事务

在MySQL中,默认情况下,每个 SQL 语句被视为一个单独的事务,即每个 SQL 语句都会自动开启事务并在执行完成后立即提交。这种自动提交模式是MySQL的默认模式,也称为自动提交模式。当自动提交模式开启时,每个 SQL 语句都会被视为一个独立的事务,并且在执行后立即提交,使得更改立即持久化到数据库中。

自动提交模式可以通过设置来控制。默认情况下,MySQL 的自动提交模式是开启的,即每个 SQL 语句都自动成为一个事务。可以使用 SET AUTOCOMMIT=0 命令来关闭自动提交模式。如果要将多个 SQL 语句组合在一个显式的事务中执行,需要在组合语句之前显示使用START TRANSACTION或BEGIN语句,然后在所有语句都执行完毕后显示执行COMMIT或ROLLBACK语句提交或回滚事务。

当自动提交模式关闭时,需要手动控制事务的边界,并显式地进行事务的提交或回滚,以确保数据的一致性和原子性。

批量插入需要考虑限制 sql 语句的长度,不能超过 MySQL 对 SQL 语句的长度限制

max_allowed_packet 是一个MySQL服务器参数,用于控制单个网络数据包的最大大小。它指定了MySQL服务器接收或发送的最大数据包大小限制,以字节为单位。

该参数对于处理大型查询或传输大量数据非常重要。如果试图发送一个超过 max_allowed_packet 大小限制的数据包,MySQL服务器将会拒绝该数据包,并且可能会导致通信错误或截断数据。

使用 show VARIABLES like ‘%max_allowed_packet%’; 可以查看网络包大小限制:
在这里插入图片描述

使用事务可以提高数据的插入效率,但事务需要控制大小,事务太大可能也会影响执行的效率。

innodb_log_buffer_size 是用于配置 InnoDB 存储引擎的日志缓冲区大小的变量。它指定了用于存储事务日志的内存缓冲区的大小。

InnoDB 存储引擎使用事务日志(也称为 redo log)来确保事务的持久性和恢复能力。在事务提交之前,相关的修改操作被写入到日志缓冲区中,而不是直接写入到磁盘上的数据文件,然后再由后台线程将日志刷新到磁盘上的 InnoDB 日志文件中。这样可以提高事务的性能,因为写入内存比写入磁盘要快得多。

事务需要控制大小,若事务大小超过上限设置,Innodb 会把数据刷到磁盘中,这时会降低效率。

较大的缓冲区可以容纳更多的日志记录,从而减少了频繁的磁盘写入操作,提高了事务的性能。然而,如果设置过大,可能会占用过多的内存资源。需要在性能和内存消耗之间进行权衡,以获得最佳的事务处理性能。

事务日志被刷新到磁盘上的日志文件,并不代表该事务已经提交,只是为了确保事务的持久性和恢复能力。即使发生故障或异常情况,数据库系统也可以通过事务日志进行恢复和回滚操作。

事务的提交是一个独立的操作,它将会持久化事务对数据库的修改,并使这些修改对其他会话可见。如果在事务日志刷盘后没有执行提交操作,那么这个事务的修改将不会被持久化到数据文件中,也不会对其他会话可见。在数据库系统的崩溃或重启情况下,这个事务的修改将会被回滚或者丢失。

使用 show variables like ‘%innodb_log_buffer_size%’; 可以查看日志缓存大小:
在这里插入图片描述

5、批量插入的优势

MySQL插入动作,主要有连接,传输,执行,提交/回滚等的动作

  1. 网络传输空间,每个插入操作都需要发送一个独立的网络请求,在请求中包含数据的内容和相关的协议头信息。批量插入多条数据,减少了请求头部的重复发送和处理,从而节省了网络带宽和传输时间。
  2. 网络连接,批量插入使用一个连接,减少网络连接次数,从而减少连接建立和断开开销。
  3. 通过合并SQL语句,减少SQL语句解析的次数;合并后的 SQL 语句可以共享解析结果和执行计划,避免了重复的解析过程;合并的 SQL 语句也需要考虑查询的并发性、锁的粒度等因素,以避免潜在的并发冲突或性能问题。
  4. 事务方面,逐条插入每次都会新建一个事务,批量插入只会使用一个事务。批量插入可以减少事务的启动和提交次数,降低开销。
  5. 磁盘IO操作,合并插入可以减少事务日志的数量,降低事务日志的总量,减少了对磁盘的写入操作,降低日志刷盘的数据量和频率,从而提高效率。(对于插入操作而言,每次逐条插入都会生成一条日志记录,包括事务开始、插入语句执行和事务提交等信息)

mybatis-plus 的 saveBatch 方法源码分析

进入 mybatis-plus 的 saveBatch 方法,可以看到是由 ServiceImpl 实现的,进入核心方法:

    @Transactional(
        rollbackFor = {Exception.class}
    )
    public boolean saveBatch(Collection<T> entityList, int batchSize) {
        String sqlStatement = this.getSqlStatement(SqlMethod.INSERT_ONE);
        return this.executeBatch(entityList, batchSize, (sqlSession, entity) -> {
            sqlSession.insert(sqlStatement, entity);
        });
    }
    
    public static <E> boolean executeBatch(Class<?> entityClass, Log log, Collection<E> list, int batchSize, BiConsumer<SqlSession, E> consumer) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one", new Object[0]);
        return !CollectionUtils.isEmpty(list) && executeBatch(entityClass, log, (sqlSession) -> {
            int size = list.size();
            int i = 1;

            for(Iterator var6 = list.iterator(); var6.hasNext(); ++i) {
                E element = var6.next();
                consumer.accept(sqlSession, element);
                if (i % batchSize == 0 || i == size) {
                    sqlSession.flushStatements();
                }
            }

        });
    }

分析这段代码,executeBatch 方法种的 consumer.accept(sqlSession, element) 执行的就是 saveBatch 的 sqlSession.insert(sqlStatement, entity),可以看到确实是 for 循环一条一条执行 insert 操作。在 executeBatch 使用了 @Transactional 开启了事务,在循环插入后再提交事务。

mybatis plus 使用了开启事务的方式提升插入效率,是否还会使用批量插入来提升效率。继续点进代码看,一直到 MySQL 驱动(mysql-connector-java:8)

   protected long[] executeBatchInternal() throws SQLException {
        synchronized(this.checkClosed().getConnectionMutex()) {
            if (this.connection.isReadOnly()) {
                throw new SQLException(Messages.getString("PreparedStatement.25") + Messages.getString("PreparedStatement.26"), "S1009");
            } else if (this.query.getBatchedArgs() != null && this.query.getBatchedArgs().size() != 0) {
                int batchTimeout = this.getTimeoutInMillis();
                this.setTimeoutInMillis(0);
                this.resetCancelledState();

                try {
                    this.statementBegins();
                    this.clearWarnings();
                    long[] var3;
                    if (!this.batchHasPlainStatements && (Boolean)this.rewriteBatchedStatements.getValue()) {
                        // 启用 rewriteBatchedStatements = true,使用多值语句进行重写,拼接 sql 批量插入数据。
                        // INSERT INTO `table` (`field1`, `field12`,...) VALUES ('valuea1', 'valuea2',...), ('valueb1', 'valueb2',...),...; 方式;
                        if (this.getQueryInfo().isRewritableWithMultiValuesClause()) {
                            var3 = this.executeBatchWithMultiValuesClause(batchTimeout);
                            return var3;
                        }
						// 如果批处理中包含多个非简单语句,并且批量参数个数大于3,则使用该方法执行批处理。
						// INSERT INTO `table` (`field1`, `field12`,...) VALUES ('value1', 'value2',...);
						// UPDATE my_table SET field1= ?, field12= ?, field13= ?, ... WHERE field14= ?";
                        if (!this.batchHasPlainStatements && this.query.getBatchedArgs() != null && this.query.getBatchedArgs().size() > 3) {
                            var3 = this.executePreparedBatchAsMultiStatement(batchTimeout);
                            return var3;
                        }
                    }
					// 否则,使用该方法按顺序执行处理每条sql语句。简单语句或参数个数不超过3个。
                    var3 = this.executeBatchSerially(batchTimeout);
                    return var3;
                } finally {
                    this.query.getStatementExecuting().set(false);
                    this.clearBatch();
                }
            } else {
                return new long[0];
            }
        }
    }

需要在 MySQL 的 jdbcUrl 中设置 rewriteBatchedStatements = true:

jdbc:mysql://localhost:3306?rewriteBatchedStatements=true

executeBatchWithMultiValuesClause(batchTimeout) 方法:

  1. 该方法用于执行批处理操作,并且支持使用多值语句进行批量插入。
  2. 如果查询可以使用多值语句进行重写,且启用了 rewriteBatchedStatements,则会选择该方法来执行批处理。
  3. 多值语句是将多个值组合在一条 SQL 语句中,用于一次性插入多个记录,可以提高插入性能。

executePreparedBatchAsMultiStatement(batchTimeout) 方法:

  1. 该方法用于执行包含多个非简单语句的批处理操作。
  2. 如果批处理中的参数个数大于 3,且满足其他条件,该方法会被选择来执行批处理。

executeBatchSerially(batchTimeout) 方法:

  1. 该方法用于按顺序逐个执行批处理中的每个语句。
  2. 该方法通常在批处理中只包含简单语句(没有参数)或参数个数较少的情况下使用。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

145.二叉树的后序遍历

刷算法题&#xff1a; 第一遍&#xff1a;1.看5分钟&#xff0c;没思路看题解 2.通过题解改进自己的解法&#xff0c;并且要写每行的注释以及自己的思路。 3.思考自己做到了题解的哪一步&#xff0c;下次怎么才能做对(总结方法) 4.整理到自己的自媒体平台。 5.再刷重复的类…

【优选算法】——Leetcode——202—— 快乐数

目录 1.题目 2. 题⽬分析: 3.简单证明&#xff1a; 4. 解法&#xff08;快慢指针&#xff09;&#xff1a; 算法思路&#xff1a; 补充知识&#xff1a;如何求⼀个数n每个位置上的数字的平⽅和。 总结概括 5.代码实现 1.C语言 2.C 1.题目 202. 快乐数 编写一个算法来…

Scala、Spark SQL 常用方法

目录 数组常用方法 列表操作常用方法 Scala中常用的查看列表元素的方法有head、init、last、tail和take()。 合并两个列表还可以使用concat()方法。 集合操作常用方法 map()方法 foreach()方法 filter()方法 flatten()方法 groupBy()方法 ​编辑 从内存中读取数据创建…

【Python技术】使用akshare、pandas高效复盘每日涨停板行业分析

作为一个程序员宝爸&#xff0c;每天的时间很宝贵&#xff0c;工作之余除了辅导孩子作业&#xff0c;就是补充睡眠。 怎么快速高效的进行当天A股涨停板的复盘&#xff0c;便于第二天的跟踪。这里简单写个示例&#xff0c; 获取当天连涨数排序&#xff0c;以及所属行业排序。 …

详解依赖注入的三种方法以及遇到问题的解决

各位大佬光临寒舍&#xff0c;希望各位能赏脸给个三连&#xff0c;谢谢各位大佬了&#xff01;&#xff01;&#xff01; 目录 1.三种依赖注入的方法 1.属性注入 优点 缺点 2.构造方法注入 优点 缺点 3.Setter注入 优点 缺点 4.小结 2.依赖注入常见问题的解决 1…

人工智能中的概率魔法:解锁不确定性的智慧之钥

在人工智能&#xff08;AI&#xff09;的广阔天地中&#xff0c;概率论以其独特的魅力&#xff0c;成为了连接现实世界与智能决策的桥梁。从语音识别到图像识别&#xff0c;从自然语言处理到机器翻译&#xff0c;从智能推荐到自动驾驶&#xff0c;概率论知识在这些领域中发挥着…

ONVIF系列三:ONVIF客户端实现

ONVIF系列&#xff1a; ONVIF系列一&#xff1a;ONVIF介绍 ONVIF系列二&#xff1a;Ubuntu安装gSOAP、生成ONVIF代码框架 ONVIF系列三&#xff1a;ONVIF客户端实现 在系列二中完成了在Ubuntu上安装gSOAP并生成ONVIF代码框架&#xff0c;接下来我们利用生成的框架实现ONVIF客户端…

Spring框架核心:揭秘Java厨房的智能烹饪艺术

前情回顾&#xff1a;Spring框架深度解析&#xff1a;打造你的Java应用梦工厂 六. 实现控制反转 6.1 描述如何在Spring中实现IoC 在Spring Town的厨房里&#xff0c;实现控制反转就像是将食材的采购和准备过程外包给了一个智能系统。这个系统知道每种食材的特性&#xff0c;也…

质量保障之精准测试!

一、背景与概念 随着软件测试行业的长足发展&#xff0c;测试理念、技术都在发生着日新月异的变化。因此一套完整的自动化测试用例对于每个软件公司都是不可或缺的&#xff0c;然而虽然有如此规模宏大的自动化案例集资源投入&#xff0c;同时也有大量人力的投入&#xff0c;但…

深入理解Python的类,实例和type函数

问题起源&#xff1a; class t():pass s1 t() s2 type("Student2",(),{}) isinstance(s1, type), isinstance(s2, type)为什么第一个是false&#xff0c;第二个是true呢 根因定位&#xff1a; 在Python中&#xff0c;一切皆对象&#xff0c;类是对象&#xff0c…

AI+新能源充电桩数据集

需要的同学私信联系&#xff0c;推荐关注上面图片右下角的订阅号平台 自取下载。 随着我国新能源汽车市场的蓬勃发展&#xff0c;充电桩的需求量日益增加&#xff0c;充电桩的智能化程度不仅影响充电站运营商的经营效益&#xff0c;也大大影响着用户的充电体验。AI技术可以涵盖…

STK12 RPO模块学习 (1)

一、背景介绍 在STK12中&#xff0c;在Astrogator的模块上开发了新的模块&#xff08;Rendezvous and proximity operations)。轨道交会接近通常来说是一个很复杂的过程。RPO实现需要对轨道动力学有一个清晰的理解&#xff0c;并且对于Astrogator模块具备很强的背景和经验&…

AI翻唱+视频剪辑全流程实战

目录 一、AI翻唱之模型训练 &#xff08;1&#xff09;模型部署 &#xff08;2&#xff09;数据集制作——搜集素材 &#xff08;3&#xff09;数据集制作——提升音频质量 方法一&#xff1a;使用RVC提供的音频处理功能。 方法二&#xff1a;可以使用音频剪辑工具Ad…

【软设】常见易错题汇总

目录 计算机系统基础 程序语言基础 数据结构 算法设计与分析 计算机网络与信息安全 软件工程基础 开发方法&#xff08;结构化与面向对象&#xff09; 数据库 操作系统 知识产权相关的法律法规 &#x1f92f;&#x1f92f;&#x1f92f;&#x1f92f;&#x1f92f;&#x1f9…

基于Springboot的实习生管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的实习生管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&a…

APP反抓包 - 客户端证书验证进阶(代码混淆)

1.关于混淆 在安卓开发中,对于第三方的包是可以进行混淆的,例如:OKHttp3.Http.Cert.check 被混淆后可以是a.f.c.b 形式。在安卓开发中,系统包是无法混淆的,例如:java.security.KeyStore不会被混淆。由于这种的情况的存在,再次审示我们之前的通用脚本,就会发现他是不通用…

基于GD32的简易数字示波器(5)- 软件_控制LED

这期记录的是项目实战&#xff0c;做一个简易的数字示波器。 教程来源于嘉立创&#xff0c;帖子主要做学习记录&#xff0c;方便以后查看。 本期主要介绍GPIO口的输入输出模式&#xff0c;使用其中的输出模式驱动LED。详细教程可观看下方链接。 2.2 LED控制实验 语雀 1、LE…

synchronized 使用及实现原理

synchronized 关键字 如何使用 synchronized 关键字的使用方式主要有下面 3 种&#xff1a; 修饰实例方法 修饰静态方法 修饰代码块 1、修饰实例方法 &#xff08;锁当前对象实例&#xff09; 给当前对象实例加锁&#xff0c;进入同步代码前要获得 当前对象实例的锁 。 …

ViewModel 完全指南:实践与背后原理全解

一、引言 在现代Android应用开发中&#xff0c;处理UI数据的有效管理和状态保持是开发者面临的重要挑战之一。Google推出的Jetpack组件库中的ViewModel已成为解决这些问题的关键工具。ViewModel旨在以生命周期意识的方式存储和管理界面相关的数据&#xff0c;从而使数据在配置…

暴力法解决最近对问题和凸包问题-实现可视化

目录 最近对问题 凸包问题 最近对问题 顾名思义就是采用蛮力法求出所有点之间的距离&#xff0c;然后进行比较找出第一个最近对&#xff0c;一个一个进行比较。 大概思路就是如图&#xff08;每个圈代表一个数对&#xff09; 第一个和其他四个比较 第二个和其他三个比较 …