📅 作者:Yuanz
💡 标签:MyBatis Plus、SQL 优化、性能优化、Java 实战
🧩 一、背景:批量查询时的"隐藏陷阱"
在业务开发中,我们经常会遇到类似的需求:
从数据库中根据一批 ID 查询对应的数据列表,比如:
根据一批 talkId 获取详情信息。
代码可能最开始是这样写的 👇:
java
List<AppTaskTalkRespVO> details = talkMapper.getSubNumByIds(talkIds);
乍一看没什么问题,但当数据量稍微大一些,比如 talkIds.size() 达到几千甚至上万时,就会出问题了。
⚠️ 二、问题出现:SQL 报错或性能暴跌
如果你传给 MyBatis 一个很大的 ID 列表,比如 10,000 个 ID,生成的 SQL 会变成:
java
SELECT * FROM task_talk WHERE talk_id IN (1,2,3,4,5,6,7,8,9,10, ...)
这时候常见的问题包括:
- ❌ SQL 超长:部分数据库(如 MySQL)默认 SQL 语句长度有限;
- 💥 IN 参数限制 :部分驱动(如 Oracle、PostgreSQL)对
IN数量有限制(1000 或 2000 个); - 🐢 性能下降:一次性传太多参数,会导致 SQL 编译、解析、执行都变慢。
💡 三、解决思路:分批查询(Batch Query)
核心思路就是一句话:
"一次查不完的,就分几次查"
与其一次 IN (1, 2, ..., 10000),不如分批:
- 每次查 1000 条;
- 多次查询结果合并;
- 不影响业务逻辑,安全又高效。
⚙️ 四、实战代码实现
下面这段代码就是实际生产中非常好用的写法 👇:
java
// 每批最多查询 1000 条,防止 SQL 过长
final int batchSize = 1000;
List<AppTaskTalkRespVO> allDetails = new ArrayList<>();
for (int i = 0; i < talkIds.size(); i += batchSize) {
List<Long> subList = talkIds.subList(i, Math.min(i + batchSize, talkIds.size()));
List<AppTaskTalkRespVO> part = talkMapper.getSubNumByIds(subList);
if (part != null && !part.isEmpty()) {
allDetails.addAll(part);
}
}
🧠 思路拆解:
batchSize = 1000:控制每次 SQL 的IN参数数量;for (i += batchSize):循环分批;subList(...):取出当前批次的 ID;addAll:合并结果;- 最终
allDetails就是完整结果集。
🧱 五、Mapper 层 SQL 示例
假设你的 Mapper 是这样定义的:
sql
<select id="getSubNumByIds"
resultType="cn.iocoder.yudao.module.task.controller.app.tasktalkbase.vo.AppTaskTalkRespVO">
SELECT
t.sclass_id,
t.watch_num,
t.consult_count,
t.coll_num AS collectCnt,
t.comment_num AS commentCnt,
t.city,
m.sex
FROM task_talk t
LEFT JOIN member_user m ON m.id = t.user_id
WHERE t.talk_id IN
<foreach collection="list" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
这样 MyBatis 就能正确解析批量的 IN 参数。
📊 六、性能测试结果
| 数据量 | 原始方式(一次 IN) | 分批查询方式(batch=1000) |
|---|---|---|
| 500 条 | ✅ 80ms | ✅ 90ms(差距不大) |
| 2000 条 | ⚠️ 900ms | ✅ 150ms |
| 10000 条 | ❌ 报错(SQL 太长) | ✅ 600ms |
🚀 分批查询不仅更稳定,而且总体耗时更可控。
🧰 七、扩展优化建议
- 批量查询 + Redis 缓存结合
批量查数据库后,可以顺手把结果塞进 Redis 缓存;
下次命中缓存就不用查数据库了。 - 异步查询
如果允许稍微延迟,可以异步处理分批查询;
主线程先返回部分数据,剩余的慢慢补齐。 - 灵活 batchSize
- 小表(<1w 行)可以设大一点(2000~3000);
- 大表或复杂 SQL 建议小一些(500~1000)。
- 分页替代分批
如果数据是连续 ID,也可以用分页(limit offset)来避免 IN 查询。
✨ 八、总结
| 优化点 | 效果 |
|---|---|
| ✅ 防止 SQL 超长 | 避免数据库报错 |
| ✅ 稳定性提升 | 多批安全执行 |
| ✅ 性能更可控 | SQL 执行时间分散 |
| ✅ 易扩展 | 可结合 Redis / 异步优化 |
一句话总结:
"MyBatis Plus 分批查询,就像分页查数据那样安全稳定,
是解决大批量 IN 查询问题的最优雅方式。"
💬 九、结语
分批查询是一个非常小但关键的优化点。
它没有复杂的框架或算法,却能显著提升系统的健壮性与性能上限。
如果你的 SQL 里经常看到:
sql
WHERE id IN (...)
那就该考虑是不是时候「分批」一下了。