这是一个非常好的问题!我来详细解释为什么这样就是"批量删除",以及它是如何做到的。
1. 从最终生成的 SQL 看本质
当传入的 alarmIds 数组是 [1001, 1002, 1003, 1004] 时,MyBatis 会生成一条完整的 SQL 语句:
sql
delete from nec_alarm_log where alarm_id in (1001, 1002, 1003, 1004)
关键点 :虽然业务上叫"批量删除",但在数据库层面,这是一条 SQL 语句,一次执行删除了多条记录。
2. <foreach> 标签的工作原理
xml
<delete id="deleteNecAlarmLogByAlarmIds" parameterType="String">
delete from nec_alarm_log where alarm_id in
<foreach item="alarmId" collection="array" open="(" separator="," close=")">
#{alarmId}
</foreach>
</delete>
| 属性 | 值 | 作用 |
|---|---|---|
item |
alarmId |
循环中的每个元素,在循环体内用 #{alarmId} 引用 |
collection |
array |
要遍历的集合(因为参数是 Long[] 数组,MyBatis 默认把它当作 array) |
open |
( |
循环开始时拼接的字符 |
separator |
, |
每个元素之间的分隔符 |
close |
) |
循环结束时拼接的字符 |
拼接过程:
- 遍历
alarmIds数组 - 第一个元素:
(1001 - 后续元素:
, 1002→, 1003→, 1004 - 最后补上:
) - 完整拼接成
(1001, 1002, 1003, 1004)
3. 为什么这算"批量"删除?
"批量"的含义有两种理解:
理解一:多次数据库交互(真正的批量)
java
// 循环执行多条 delete 语句(不推荐)
for (Long id : alarmIds) {
mapper.deleteById(id); // 每次一条 SQL
}
→ 4 条记录 = 4 次网络往返 + 4 次 SQL 解析执行
理解二:一次数据库交互删除多条(你的实现)
sql
delete from nec_alarm_log where alarm_id in (1,2,3,4)
→ 4 条记录 = 1 次网络往返 + 1 次 SQL 解析执行
你的实现属于"批量删除"的第二种理解,虽然只有一条 SQL,但效果等同于一次删除多条,性能远优于循环删除。
4. 什么是真正的"批量删除"(Batch)?
如果使用 JDBC 的 PreparedStatement.addBatch(),才是真正的批量操作:
java
// 使用 BATCH 执行器
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH)) {
NecAlarmLogMapper mapper = sqlSession.getMapper(NecAlarmLogMapper.class);
for (Long id : alarmIds) {
mapper.deleteById(id); // 每个 delete 被 addBatch() 缓存
}
sqlSession.commit(); // 一次性发送所有 delete 语句
}
这会生成多条独立的 delete 语句,但一次网络请求发送到数据库,减少了网络开销。
5. 两种"批量"方式的对比
| 维度 | IN 方式(你的实现) |
addBatch() 方式 |
|---|---|---|
| SQL 条数 | 1 条 | N 条 |
| 网络往返 | 1 次 | 1 次(但 SQL 更长) |
| 数据库解析 | 1 次 | N 次 |
| 事务 | 单条 SQL,自动原子 | 多条 SQL,需要事务保证原子性 |
| 适用场景 | 删除/查询/更新 | 插入/更新(每条数据不同) |
| 性能 | 非常高(尤其数据量大时) | 较高 |
| SQL 长度限制 | 受 max_allowed_packet 限制 |
无限制(分批发送) |
6. 你的代码注意事项
参数类型写成 parameterType="String"
xml
<delete id="deleteNecAlarmLogByAlarmIds" parameterType="String">
这里写 String 是错误的,因为实际传入的是 Long[]。虽然 MyBatis 不强制校验这个类型,但最好修正:
xml
<delete id="deleteNecAlarmLogByAlarmIds" parameterType="java.lang.Long[]">
<!-- 或者直接省略 parameterType -->
</delete>
collection 的取值规则
| 参数类型 | collection 值 |
|---|---|
数组 (如 Long[]) |
array |
List (如 List<Long>) |
list |
| Map | map 中的 key |
使用 @Param 注解 |
注解的值 |
如果你在 Mapper 方法中加了 @Param:
java
int deleteNecAlarmLogByAlarmIds(@Param("ids") Long[] alarmIds);
则 collection 应该改为 ids。
7. 适用场景限制
IN 方式虽然高效,但有两个限制:
- SQL 长度限制 :MySQL 默认
max_allowed_packet=4M,如果alarmIds有几十万个,生成的 SQL 会超长。 - IN 子句性能 :当
IN列表非常大(如数万)时,数据库优化器可能不会走索引,性能下降。
解决方案 :大数据量时,可以分批使用 IN:
java
// 每 1000 条一批
List<Long> ids = Arrays.asList(alarmIds);
int batchSize = 1000;
for (int i = 0; i < ids.size(); i += batchSize) {
List<Long> subList = ids.subList(i, Math.min(i + batchSize, ids.size()));
mapper.deleteNecAlarmLogByAlarmIds(subList.toArray(new Long[0]));
}
8. 总结
你的代码为什么是批量删除?
- 因为
foreach标签把多个alarmId拼接到IN子句中 - 最终生成一条
delete ... in (id1, id2, id3, ...)的 SQL - 一条 SQL 删除了多条记录,从业务角度看就是"批量删除"
- 相比循环删除,性能提升巨大(减少 N-1 次网络交互)
本质上 :这不是 JDBC 的批量操作,而是 SQL 层面的批量操作,是 MyBatis 动态 SQL 的经典用法之一。😊