insert all 在 oracle 19c 中性能劣于 insert ... select union all,因解析开销非线性增长,1000 行时耗时 0.86s(union all 仅 0.342s);后者复用执行路径更高效,但需严格匹配列数、类型,并显式处理默认值。

INSERT ALL 和 INSERT ... SELECT UNION ALL 是 Oracle 19c 中最常用、可直接在应用层发起的批量插入方案,但二者性能差异明显:**1000 行时,INSERT ... SELECT UNION ALL 比 INSERT ALL 快约 2.5 倍(0.342s vs 0.86s),且生成的 SQL 更短(46K vs 71K)**。这不是理论推演,而是实测结果。
为什么 INSERT ALL 在 Oracle 19c 中变慢了?
Oracle 对 INSERT ALL 的解析开销随行数增长非线性上升。每增加一行,语法树节点数、绑定变量处理逻辑、执行计划重用难度都显著增加。尤其当插入字段含 VARCHAR2 或表达式(如 SYSDATE)时,优化器要为每个 INTO 子句单独做类型推导和隐式转换检查。
而 SELECT UNION ALL 实质是单个查询计划 + 多个常量行源,Oracle 能复用大部分执行路径,尤其在 19c 的自适应执行计划下更稳定。
INSERT ... SELECT UNION ALL 的写法与边界条件
必须注意三点:
• UNION ALL 最后一行不能带 UNION ALL,否则报错 ORA-00907: missing right parenthesis
• 所有 SELECT 的列数、顺序、数据类型必须严格一致,否则触发 ORA-01790: expression must have same datatype as corresponding expression
• 如果目标表有 NOT NULL 列但没在 INSERT 列表中显式指定,默认值(如 SYS_GUID()、SYSDATE)不会自动生效 —— 这点和单条 INSERT INTO ... VALUES 不同
- 正确示例(插入 3 行):
INSERT INTO tb_log_test (column1, column2, column3) SELECT '1', '1', 1 FROM DUAL UNION ALL SELECT '2', '2', 2 FROM DUAL UNION ALL SELECT '3', '3', 3 FROM DUAL;
- 错误写法(漏掉最后一行的
FROM DUAL):SELECT '3','3',3→ 报ORA-00923: FROM keyword not found where expected - 若表定义含
uuid RAW(32) DEFAULT SYS_GUID(),该写法不会调用默认值,必须显式写SYS_GUID()或改用INSERT ALL
MyBatis-Plus 里怎么安全用 INSERT ALL?
直接拼 INSERT ALL 字符串风险高:SQL 注入、NULL 值插入失败(Oracle 不支持 NULL 直接进 VALUES)、长度超限(Oracle 单条 SQL 最大 2M,但实际建议控制在 100K 内)。MyBatis-Plus 默认不支持 INSERT ALL,需自定义 AbstractMethod 注入,且必须:
• 用 <if test="et.xxx != null">#{et.xxx}</if> 包裹每个字段,避免 NULL 导致整条语句失败
• 用 BEGIN ... END; 包裹,否则无法处理多条 INSERT 的事务原子性
• 禁用 NoKeyGenerator,否则主键序列可能被跳过
但更推荐绕过 MyBatis-Plus,改用原生 JDBC 的 PreparedStatement.addBatch() + executeBatch(),它底层会自动选择最优协议(如 Oracle 的 ARRAY BIND),1000 行耗时通常压到 0.1s 内,且无需手写 SQL 拼接。
真正大批量(>10 万行)别硬扛 SQL
无论 INSERT ALL 还是 UNION ALL,超过 5 万行就容易触发 ORA-04030: out of process memory 或网络缓冲区溢出。这时候必须换路子:
• 应用端分批次(如每 5000 行 commit 一次)
• 用 FORALL INSERT + PL/SQL 集合,在存储过程中执行,内存复用率高
• 终极方案:SQL*Loader 直接走 Oracle 专用通道,百万行导入常在 3 秒内完成,且不走 SQL 解析器
最容易被忽略的是:Oracle 的 INSERT ALL 语法虽然看着像“一条语句”,但它本质仍是多条独立 DML;而 FORALL 才是真·单次上下文切换的批量操作。别被语法糖迷惑。











