oracle批量插入必须用insert all,不可用多条insert into;需用循环生成多个into子句,结尾固定加select 1 from dual,且单次不超过1000行。

Oracle批量插入必须用 INSERT ALL,foreach 直接套 INSERT INTO 会报错
Oracle 不支持 MySQL 那种多值 INSERT INTO t VALUES (...), (...) 语法,MyBatis 的 <foreach></foreach> 如果直接拼成多条 INSERT INTO,每条都带 VALUES,Oracle 会直接抛 ORA-00936: missing expression 或 ORA-00911: invalid character —— 因为它不认分号分隔的多语句,也不允许在单个 SQL 中重复写 INSERT INTO。
正确做法是把所有数据压进一条 INSERT ALL 语句里。MyBatis 的 <foreach></foreach> 要配合 INTO 子句循环生成多组 VALUES,而不是循环整个 INSERT。
-
INSERT ALL必须以SELECT 1 FROM DUAL结尾,不能省略 -
<foreach></foreach>的collection必须是非空 List,否则整个 SQL 缺少INTO行,Oracle 报错 - 字段名要显式写出,不能依赖列顺序;Oracle 对空值和类型敏感,
NULL建议显式写NULL,别留空
MyBatis XML 中 <foreach></foreach> 的写法要点
不是简单套个 <foreach></foreach> 就行,关键在 item、separator 和 SQL 结构嵌套位置。常见错误是把 INSERT ALL 写在 <foreach></foreach> 外面,结果只生成一条 INTO,或者 separator 错用成逗号导致语法断裂。
正确结构是:INSERT ALL 开头 → <foreach></foreach> 循环多个 INTO table (...) VALUES (...) → 最后固定接 SELECT 1 FROM DUAL。
-
separator必须设为(空格),不是逗号或换行,否则破坏INTO关键字格式 -
item名要和传入对象属性名一致,比如传List<user></user>,就用item="user",然后写#{user.id} - Oracle 对字符串长度敏感,
#{...}自动加单引号,但数字、日期类型也得确保传入值类型匹配,否则隐式转换失败
<insert id="batchInsertUsers">
INSERT ALL
<foreach collection="users" item="user" separator=" ">
INTO users (id, name, created_time) VALUES (#{user.id}, #{user.name}, #{user.createdTime})
</foreach>
SELECT 1 FROM DUAL
</insert>
批量数据量大时 Oracle 的限制与绕过方式
Oracle 单条 INSERT ALL 最多支持 1000 个 INTO 子句(即最多 1000 行),超了会报 ORA-01795: maximum number of expressions in a list is 1000。这不是 MyBatis 问题,是 Oracle 硬限制。
- 别硬扛,服务端做分片:把
List拆成每 1000 条一组,循环调用同一 mapper 方法 - 避免在 XML 里写逻辑分页,容易出错;分片逻辑放在 Service 层更可控
- 如果用
jdbcBatch(即ExecutorType.BATCH),MyBatis 底层走的是 JDBC Batch,但 Oracle 驱动对 Batch 的兼容性差,部分版本会退化成单条执行,性能反而不如INSERT ALL分片
字段含 SEQUENCE.NEXTVAL 或 SYSDATE 怎么处理
Oracle 批量插入时不能在 VALUES 里直接写 SEQ_USER.NEXTVAL,因为每个 INTO 子句是独立上下文,序列会多次触发,但你可能只想要一个自增逻辑 —— 实际上,只要写对,它是能正常工作的;真正容易错的是混用 Java 生成 ID 和数据库生成 ID。
- 想用序列,就在
VALUES里写SEQ_USER.NEXTVAL,不用#{},MyBatis 不解析纯 SQL 片段 - 想用当前时间,写
SYSDATE或TO_DATE(#{...}, 'yyyy-mm-dd hh24:mi:ss'),但注意传入字符串格式必须严格匹配 - 最常踩的坑:Java 对象字段为
null,XML 里又没判空,结果拼出VALUES (NULL, , ...),Oracle 报ORA-00936—— 所有字段要么显式写NULL,要么用<if test="user.name != null">#{user.name}</if>包裹
INSERT ALL 的语法结构、1000 行上限、空值处理、序列写法,这几个点只要漏一个,SQL 就跑不起来。实际写的时候,先手写一条能执行的 INSERT ALL,再把它拆进 MyBatis XML,比反过来调试快得多。










