
先看真实面试场景:
👨 面试官: 你们项目批量插入大概多少条一批?
👦 候选者: 嗯……5 万一批吧。
👨 面试官: 5 万确定吗?这条 SQL 多大、max_allowed_packet 够装吗?
👦 候选者: 嗯……我们之前一直这么用,没出过问题。
👨 面试官: 5 万这个数字怎么算出来的?为什么不是 500、不是 5000?资源、事务、锁——你考虑过哪个维度?
👦 候选者: 这……是之前一位资深工程师定的。
👨 面试官: 回去等通知吧。
这道题筛三件事:
- 数字背后的推导能力 ——能不能从硬件、记录大小、事务约束算出来一个数字
- 理解 InnoDB 的存储原理 ——页、redo log、事务边界、
max_allowed_packet - 会用 MyBatis 实现 ——
vs ExecutorType.BATCH、什么时候用哪个

下面三段就是 L1/L2/L3 标准答案——按段位评估自己卡在哪一档。
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/ruoyi-vue-pro
- 视频教程:https://doc.iocoder.cn/video/
被问到能直接给一个生产可用的默认值 :
500 - 1000 条一批 ——OLTP 业务(高频小事务)走这个范围,事务粒度可控、不会撑爆 max_allowed_packet、锁占用时间短。
5000 - 10000 条一批 ——离线导数/批处理场景,可以放大批量减少总往返开销。
一句话注释 :业务系统选 500-1000,离线任务选 5000-10000,绝对不要超过 5 万 。
⚠️
L1 的边界 :背一个数字 = 30 分及格 。如果面试官问"为什么是 500-1000?大了会怎样?"——你答不上来就只能停在 30 分 。
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
- 项目地址:https://github.com/YunaiV/yudao-cloud
- 视频教程:https://doc.iocoder.cn/video/
L2 答法的核心:讲清三个限制因素 ——单批太大会触发的硬性约束。
每条 INSERT INTO t VALUES (...), (...), ... 是一个 SQL packet 发给 MySQL Server——有最大长度上限 :
超出会报错 :Packet for query is too large。一行 100 字节的话,1MB packet 大约能装 1 万条——超过了就拆批。
InnoDB 写入会先写 redo log(WAL)+ 锁住相关行。单批越大 :
- redo log 膨胀 ——大事务可能触发 redo log file 切换甚至 checkpoint
- 失败回滚成本高 ——10 万条插到 9.5 万崩了,前面 9.5 万全要 rollback
生产经验 :单批超过 1 万条,回滚一次的代价就开始让人难受
了。
数据先进 buffer pool,再异步刷盘:

批量的甜区 :
- 甜区 (500-5000) → 单事务内合并多行写、IO 次数和事务次数都最优
- 太大 → buffer pool 被这一批占满 → 触发刷盘 → IO 飙升 → 影响其它查询
单条 vs 批量的差距 ——主要在事务开销:
L2 的边界 :能讲清三个限制因素 = 60 分及格 。如果问"那具体怎么算?给我一个能用的公式"——L3 来了。
L3 给两件东西:一套可推导的公式 + MyBatis 实战代码 ——这一档是 90 分。
给定字段结构:
- 变长字符串 (varchar) :平均 50 字节,最大 255 字节
平均一条记录大小:

考虑 varchar 最大长度的极端情况:

8GB 内存、预留 20%(给 OS 和其它进程),可用 ≈ 6.4 GB:

按平均记录大小算,内存能撑下的最大记录数 :

512 GB 硬盘能存的最大记录数:

这只是上限——实战还要预留磁盘 20-30% 给 binlog、redo log、临时空间,并考虑增长速度 。
最常见的做法——一条 SQL 多个 VALUES:
<insert id="insertMultiple" parameterType="list">
INSERT INTO orders (order_no, user_id, amount, create_time)
VALUES
<foreach
collection="list" item="record" separator=",">
(#{record.orderNo}, #{record.userId}, #{record.amount}, #{record.createTime})
foreach>
insert>
- ❌ 受
max_allowed_packet 限制 ——批量不能无限大
让 MyBatis 帮你用 JDBC 批处理(addBatch / executeBatch)攒到一定数量再发 ——内存可控、批量大小可灵活配置:
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
try {
OrderMapper mapper = session.getMapper(OrderMapper.class);
int count = 0;
for (Order order : orders) {
mapper.insert(order);
// 每 1000 条 flush 一次,避免内存爆炸
if (++count % 1000 == 0) {
session.flushStatements();
}
}
session.commit();
} catch (Exception e) {
session.rollback();
throw e;
} finally {
session.close();
}
- ✅ DAO 层强制走 BATCH,能防住新人在 for 里写单条 insert 把性能玩崩
⚠️ 关于"网络往返次数"的常见误解 :
- BATCH 模式不是永远只 1 次往返 ——你
flushStatements() 多少次,就分多少批发送 - MySQL 驱动默认仍是逐条 INSERT 发包(只是用了 JDBC batch 协议);只有加 URL 参数
rewriteBatchedStatements=true 时 ,驱动才会把多条 INSERT 重写成单条多 VALUES 的大 SQL——这时才接近 的效率 - 重写后的大 SQL 仍受
max_allowed_packet 限制 ——所以即使开了 rewrite,单批不能无限大,建议保持 1000-5000 这个甜区
每条都 commit = 每次都触发刷盘 + checkpoint ——批量插入最忌讳:
// ❌ 错的姿势:循环里 commit
for (Order o : orders) {
insert(o);
commit(); // 性能爆炸
}
// ✅ 对的姿势:循环里只 add,全部完了再 commit
for (Order o : orders) {
insert(o);
}
commit();
| |
|---|
| 大概率超 max_allowed_packet ——4MB 默认值,100 字节/行只能装 4 万;100 字节单行 + 索引列稍多就爆 |
| 错 |
| |
| 基础不牢 |
| MyBatis 都没玩透 |
| | ExecutorType.BATCH |
|---|
| SQL 形态 | 一条 INSERT VALUES (...),(...) | N 条独立 INSERT,走 JDBC addBatch 协议攒发 |
| 网络往返 | | 每次 flushStatements() 一批 |
max_allowed_packet 限制 | | 默认逐条发不会超 ;开 rewriteBatchedStatements=true 重写为大 SQL 后仍会触发限制 |
| 可读性 | | |
| 推荐场景 | | |
核心问题 :分库分表后,一批 10000 条数据可能分散到 16 个库,每个库实际只有 ~625 条 ——不能用一条 SQL 解决。
两种方案 :
- 按分片键分组 :先按分片规则把 10000 条分组到 16 个库,对每个库分别走批量
- 借助 ShardingSphere / MyCAT :让中间件帮你做这个分组,业务层依然像单库一样写
bulk_insert_buffer_size:MyISAM 专用——InnoDB 不用看innodb_flush_log_at_trx_commit:默认 1 = 每事务刷盘,改成 2 可以批量插入快很多(但崩了可能丢 1 秒数据)sync_binlog:默认 1 = 每事务刷 binlog,同上 trade-off
生产里别乱改 ——这些参数影响数据安全,DBA 没点头之前别动。
回到开头那个面试场景——候选者答错的不是"5 万"这个数字,而是讲不出选这个数字的理由 。

成为优秀工程师的关键:不是知道更多技巧,而是对每个数字都讲得出来源 。
