Mysql

关注公众号 jb51net

关闭
首页 > 数据库 > Mysql > MySQL大数据插入

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

作者: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();
        }
    }
}

关键注意:

示例 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;

注意:

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

代码层面优化后,如果还想更快,可以调整 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 的刷盘策略,对插入速度影响极大:

建议值

[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

解决方案

坑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

优化优先级:

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

您可能感兴趣的文章:
阅读全文