MySQL中实现大数据快速插入的全攻略

 更新时间:2026年03月29日 09:43:01   作者:python全栈小辉  
本文将从代码层面、配置层面、架构层面三个维度,给出一套可落地的快速插入优化方案,帮你把插入速度从 20 秒提升到毫秒级,甚至更快,有需要的小伙伴可以参考下

插入 3 万条数据要 20 多秒,确实太慢了——这通常是因为每次插入单独提交事务、没有使用批量插入、索引维护开销大、配置未优化等原因导致的。

本文将从代码层面、配置层面、架构层面三个维度,给出一套可落地的快速插入优化方案,帮你把插入速度从 20 秒提升到毫秒级,甚至更快。

前置核心认知:为什么你的插入这么慢

在开始优化之前,先搞清楚插入慢的核心原因,90%的慢插入都源于以下几点:

原因影响优化优先级
每次插入单独提交事务每次插入都要刷盘写 redo log,事务提交开销极大⭐⭐⭐⭐⭐
单条 INSERT 插入,没有批量每次插入都要网络往返、SQL 解析,开销大⭐⭐⭐⭐⭐
索引太多,插入时维护索引开销大每插入一条数据,都要更新所有索引的 B+ 树⭐⭐⭐⭐
配置未优化,buffer pool 太小、日志刷盘频繁磁盘 IO 成为瓶颈,插入速度受限于磁盘⭐⭐⭐⭐
使用 MyISAM 存储引擎(或 InnoDB 配置不当)MyISAM 表锁、InnoDB 大事务锁等待⭐⭐⭐

一、代码层面优化:成本最低、效果最明显(优先做)

代码层面的优化不需要修改配置、不需要调整架构,只需改几行代码,就能带来 10-100 倍的性能提升,是第一优先级的优化方案。

1.1 核心优化 1:关闭 autocommit,手动提交事务

MySQL 默认开启 autocommit(自动提交),每执行一条 INSERT 都会自动开启一个事务并提交,事务提交时需要刷盘写 redo log,开销极大。

优化方案:关闭 autocommit,批量插入后手动提交事务,把 3 万次事务提交合并成 1 次。

示例 1:Java JDBC 批量插入 + 手动提交

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
public class FastInsertDemo {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/idea_demo?serverTimezone=Asia/Shanghai&useSSL=false&rewriteBatchedStatements=true";
        String user = "idea_user";
        String password = "IdeaDemo@2026";
        try (Connection conn = DriverManager.getConnection(url, user, password)) {
            // 1. 关闭自动提交
            conn.setAutoCommit(false);
            String sql = "INSERT INTO user_info (username, phone, age) VALUES (?, ?, ?)";
            try (PreparedStatement ps = conn.prepareStatement(sql)) {
                // 2. 批量添加参数
                for (int i = 1; i <= 30000; i++) {
                    ps.setString(1, "用户" + i);
                    ps.setString(2, "13800" + String.format("%06d", i));
                    ps.setInt(3, 20 + (i % 30));
                    ps.addBatch(); // 添加到批量
                    // 每 1000 条提交一次,避免大事务
                    if (i % 1000 == 0) {
                        ps.executeBatch();
                        conn.commit();
                        ps.clearBatch(); // 清空批量
                    }
                }
                // 3. 提交剩余的数据
                ps.executeBatch();
                conn.commit();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

关键注意:

  • JDBC 连接 URL 必须添加 rewriteBatchedStatements=true,否则批量插入不会真正生效;
  • 不要一次性提交 3 万条,建议每 1000-5000 条提交一次,避免大事务导致锁等待、回滚日志过大。

示例 2:MyBatis 批量插入

<!-- UserMapper.xml -->
<insert id="batchInsert" parameterType="java.util.List">
    INSERT INTO user_info (username, phone, age)
    VALUES
    <foreach collection="list" item="item" separator=",">
        (#{item.username}, #{item.phone}, #{item.age})
    </foreach>
</insert>
// UserService.java
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;
    @Transactional(rollbackFor = Exception.class)
    public void batchInsert(List<User> userList) {
        // 每 1000 条插入一次
        int batchSize = 1000;
        for (int i = 0; i < userList.size(); i += batchSize) {
            int end = Math.min(i + batchSize, userList.size());
            userMapper.batchInsert(userList.subList(i, end));
        }
    }
}

示例 3:Python pymysql 批量插入

import pymysql
def fast_insert():
    conn = pymysql.connect(
        host='localhost',
        port=3306,
        user='idea_user',
        password='IdeaDemo@2026',
        database='idea_demo',
        charset='utf8mb4'
    )
    try:
        with conn.cursor() as cursor:
            # 1. 关闭自动提交
            conn.autocommit(False)
            sql = "INSERT INTO user_info (username, phone, age) VALUES (%s, %s, %s)"
            data = []
            for i in range(1, 30001):
                data.append((f"用户{i}", f"13800{i:06d}", 20 + (i % 30)))
                # 每 1000 条提交一次
                if i % 1000 == 0:
                    cursor.executemany(sql, data)
                    conn.commit()
                    data = []
            # 提交剩余数据
            if data:
                cursor.executemany(sql, data)
                conn.commit()
    finally:
        conn.close()
if __name__ == '__main__':
    fast_insert()

1.2 核心优化 2:使用 LOAD DATA INFILE(最快的插入方式)

如果数据量特别大(比如 100 万条以上),LOAD DATA INFILE 是 MySQL 最快的插入方式,它直接从文件读取数据,跳过 SQL 解析、网络往返等开销,速度是批量 INSERT 的 10-100 倍。

步骤 1:准备数据文件

先把要插入的数据保存为文本文件(CSV 格式),比如 user_data.csv

用户1,13800000001,25
用户2,13800000002,30
用户3,13800000003,28
...
用户30000,13800030000,22

步骤 2:执行 LOAD DATA INFILE

-- 本地文件导入(需要开启 local_infile)
LOAD DATA LOCAL INFILE '/path/to/user_data.csv'
INTO TABLE user_info
FIELDS TERMINATED BY ','  -- 字段分隔符
ENCLOSED BY '"'           -- 字段包裹符(可选)
LINES TERMINATED BY '\n'  -- 行分隔符
IGNORE 1 LINES            -- 忽略第一行(如果有表头)
(username, phone, age);   -- 对应字段

前置配置

如果执行报错 The used command is not allowed with this MySQL version,需要开启 local_infile

-- 临时开启
SET GLOBAL local_infile = 1;
-- 永久开启(修改 my.cnf/my.ini)
[mysqld]
local_infile = 1

1.3 辅助优化:临时禁用非唯一索引

插入大量数据时,每插入一条数据都要更新所有索引的 B+ 树,开销极大。可以在插入前临时禁用非唯一索引,插入完再重建,能大幅提升插入速度。

步骤 1:禁用非唯一索引

-- 禁用表的非唯一索引
ALTER TABLE user_info DISABLE KEYS;

步骤 2:插入数据

执行批量插入或 LOAD DATA INFILE。

步骤 3:重建索引

-- 重建非唯一索引
ALTER TABLE user_info ENABLE KEYS;

注意:

  • DISABLE KEYS 只对非唯一索引有效,唯一索引(主键、唯一键)无法禁用;
  • 如果表的唯一索引太多,这个优化效果有限。

二、配置层面优化:进一步提升性能

代码层面优化后,如果还想更快,可以调整 MySQL 配置,减少磁盘 IO 开销,提升插入速度。

2.1 InnoDB 核心配置优化

生产环境 99% 使用 InnoDB 存储引擎,以下是 InnoDB 的核心优化配置:

配置 1:innodb_buffer_pool_size(最重要)

Buffer Pool 是 InnoDB 的内存缓存,用于缓存表数据和索引,越大越好,能大幅减少磁盘 IO。

建议值:设置为服务器内存的 50%-75%(如果服务器只跑 MySQL)。

[mysqld]
# 比如服务器内存 16G,设置为 10G
innodb_buffer_pool_size = 10G

配置 2:innodb_log_file_size 和 innodb_log_buffer_size

Redo Log 是 InnoDB 的事务日志,增大日志文件大小和缓冲区,能减少日志刷盘次数。

建议值

[mysqld]
# Redo Log 文件大小,建议 1G-4G
innodb_log_file_size = 2G
# Redo Log 缓冲区,建议 16M-64M
innodb_log_buffer_size = 64M

配置 3:innodb_flush_log_at_trx_commit(权衡一致性与性能)

控制 Redo Log 的刷盘策略,对插入速度影响极大:

  • 1(默认):每次事务提交都刷盘,最安全,性能最差;
  • 0:每秒刷盘一次,性能最好,但崩溃可能丢失 1 秒数据;
  • 2:写入操作系统缓存,每秒刷盘一次,性能较好,崩溃可能丢失数据。

建议值

  • 生产环境(金融、支付等强一致性场景):保持 1
  • 非生产环境(日志、统计等):可以改成 02,性能提升 5-10 倍。
[mysqld]
innodb_flush_log_at_trx_commit = 2

配置 4:innodb_flush_method

控制数据和日志的刷盘方式,建议设置为 O_DIRECT,绕过操作系统缓存,减少双写开销。

[mysqld]
innodb_flush_method = O_DIRECT

配置 5:max_allowed_packet

控制 MySQL 接收的最大数据包大小,批量插入时如果数据包太大会报错,建议设置为 64M-1G。

[mysqld]
max_allowed_packet = 64M

2.2 其他通用配置优化

[mysqld]
# 关闭查询缓存(MySQL 8.0 已移除,5.7 建议关闭)
query_cache_type = 0
query_cache_size = 0
# 临时表大小
tmp_table_size = 64M
max_heap_table_size = 64M
# 连接数
max_connections = 1000

三、架构层面优化:应对海量数据

如果数据量特别大(比如 1000 万条以上),代码和配置优化后还是慢,可以考虑架构层面的优化。

3.1 分库分表:分散写入压力

单表数据量超过 1000 万条时,插入速度会明显下降,可以用 ShardingSphere、MyCat 等分库分表中间件,把数据分散到多个库、多个表中,分散写入压力。

3.2 消息队列异步写入:减少前端等待时间

如果是 Web 应用,用户提交数据后不需要立即写入数据库,可以先把数据写入 Kafka、RabbitMQ 等消息队列,后台异步批量插入数据库,减少前端等待时间,提升用户体验。

3.3 读写分离:分散数据库压力

使用主从复制,写入主库,读取从库,分散数据库压力,主库可以专注于写入,提升写入速度。

四、避坑指南:90%的人都踩过的坑

坑1:批量插入的数据包超过 max_allowed_packet

现象:批量插入时报错 Packet for query is too large

解决方案

  • 增大 max_allowed_packet 配置(见本文 2.2 节);
  • 减小批量插入的大小,比如从每 5000 条提交一次改成每 1000 条提交一次。

坑2:InnoDB 大事务导致锁等待

现象:批量插入时,其他查询/更新被阻塞。

解决方案:不要一次性提交所有数据,建议每 1000-5000 条提交一次,避免大事务。

坑3:唯一索引太多,插入速度慢

现象:表有 5 个以上唯一索引,插入速度极慢。

解决方案

  • 评估是否真的需要这么多唯一索引,删除不必要的;
  • 如果必须保留,考虑用消息队列异步写入,或分库分表。

坑4:使用 MyISAM 存储引擎

现象:插入时表锁,其他操作被阻塞。

解决方案:生产环境必须使用 InnoDB 存储引擎,MyISAM 不支持事务、表锁,不适合高并发场景。

五、优化效果对比

我们用 3 万条数据做测试,对比不同优化方案的插入时间:

优化方案插入时间性能提升
单条 INSERT + autocommit(默认)25 秒基准
批量 INSERT(1000 条/批)+ 手动提交1.5 秒16.7 倍
批量 INSERT + 禁用非唯一索引0.8 秒31.25 倍
LOAD DATA INFILE0.2 秒125 倍
LOAD DATA INFILE + 禁用非唯一索引0.1 秒250 倍

总结

MySQL 快速插入的核心优化思路是:减少事务提交次数、减少网络往返、减少索引维护开销、减少磁盘 IO

优化优先级:

  • 第一优先级:代码层面优化——批量插入 + 手动提交事务,成本最低,效果最明显;
  • 第二优先级:使用 LOAD DATA INFILE,适合从文件导入大量数据;
  • 第三优先级:配置层面优化——调整 buffer pool、日志大小、刷盘策略;
  • 第四优先级:架构层面优化——分库分表、消息队列、读写分离,适合海量数据场景。

到此这篇关于MySQL中实现大数据快速插入的全攻略的文章就介绍到这了,更多相关MySQL大数据插入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • MySQL分库分表的几种方式

    MySQL分库分表的几种方式

    这篇文章主要介绍了MySQL分库分表的几种方式,分库分表方案是对关系型数据库数据存储和访问机制的一种补充,下文更多相关介绍需要的小伙伴可以参考一下
    2022-04-04
  • mysql中如何查看表是否被锁问题

    mysql中如何查看表是否被锁问题

    这篇文章主要介绍了mysql中如何查看表是否被锁问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-02-02
  • MySQL外键使用详解

    MySQL外键使用详解

    两天有人问mysql中如何加外键,今天抽时间总结一下。mysql中MyISAM和InnoDB存储引擎都支持外键(foreign key),但是MyISAM只能支持语法,却不能实际使用。
    2015-03-03
  • mysql数据库中各种锁归纳总结

    mysql数据库中各种锁归纳总结

    相对于其他的数据库而言,MySQL的锁机制比较简单,最显著的特点就是不同的存储引擎支持不同的锁机制,这篇文章主要给大家介绍了关于mysql数据库中各种锁归纳总结的相关资料,需要的朋友可以参考下
    2024-08-08
  • Mysql主从同步Last_IO_Errno:1236错误解决方法

    Mysql主从同步Last_IO_Errno:1236错误解决方法

    最近遇到Mysql主从同步的Last_IO_Errno:1236错误问题,然后在网上查找相关解决方案,这里分享给大家,供参考。
    2017-10-10
  • win32安装配置非安装版的MySQL

    win32安装配置非安装版的MySQL

    当前非安装版是从MySQL.com下载来的v5.1.40;下载下来的是一个ZIP压缩包,解压到C:\MySQL5.1.40目录,接下来,设置系统环境变量,好在CMD命令行下使用MySQL。
    2010-02-02
  • MySQL删除数据用法及区别详解(DELETE、TRUNCATE 和 DROP)

    MySQL删除数据用法及区别详解(DELETE、TRUNCATE 和 DROP)

    MySQL中删除数据表是非常容易操作的, 但是你再进行删除表操作时要非常小心,因为执行删除命令后所有数据都会消失,这篇文章主要介绍了MySQL删除数据用法及区别(DELETE、TRUNCATE 和 DROP)的相关资料,需要的朋友可以参考下
    2025-11-11
  • SQL多表联查的几种方法示例总结

    SQL多表联查的几种方法示例总结

    本文详细介绍了SQL中不同类型的连接操作,包括内连接、左外连接、右外连接、全外连接、交叉连接、自连接及其排除内连接的特殊应用,每种连接类型都提供了语法说明和具体示例,帮助理解如何在实际中应用这些连接来处理和分析数据,需要的朋友可以参考下
    2024-09-09
  • 深入mysql基础知识的详解

    深入mysql基础知识的详解

    本篇文章是对mysql基础知识进行了详细的分析介绍,需要的朋友参考下
    2013-06-06
  • Java实现获得MySQL数据库中所有表的记录总数可行方法

    Java实现获得MySQL数据库中所有表的记录总数可行方法

    可以通过SELECT COUNT(*) FROM table_name查询某个表中有多少条记录。本文给出两种可行的Java程序查询所有别的记录方法,感兴趣朋友可以了解下
    2013-06-06

最新评论