MySQL中实现大数据快速插入的全攻略
插入 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; - 非生产环境(日志、统计等):可以改成
0或2,性能提升 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 INFILE | 0.2 秒 | 125 倍 |
| LOAD DATA INFILE + 禁用非唯一索引 | 0.1 秒 | 250 倍 |
总结
MySQL 快速插入的核心优化思路是:减少事务提交次数、减少网络往返、减少索引维护开销、减少磁盘 IO。
优化优先级:
- 第一优先级:代码层面优化——批量插入 + 手动提交事务,成本最低,效果最明显;
- 第二优先级:使用 LOAD DATA INFILE,适合从文件导入大量数据;
- 第三优先级:配置层面优化——调整 buffer pool、日志大小、刷盘策略;
- 第四优先级:架构层面优化——分库分表、消息队列、读写分离,适合海量数据场景。
到此这篇关于MySQL中实现大数据快速插入的全攻略的文章就介绍到这了,更多相关MySQL大数据插入内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Mysql主从同步Last_IO_Errno:1236错误解决方法
最近遇到Mysql主从同步的Last_IO_Errno:1236错误问题,然后在网上查找相关解决方案,这里分享给大家,供参考。2017-10-10
MySQL删除数据用法及区别详解(DELETE、TRUNCATE 和 DROP)
MySQL中删除数据表是非常容易操作的, 但是你再进行删除表操作时要非常小心,因为执行删除命令后所有数据都会消失,这篇文章主要介绍了MySQL删除数据用法及区别(DELETE、TRUNCATE 和 DROP)的相关资料,需要的朋友可以参考下2025-11-11


最新评论