存储过程不适合逐行插入千万级数据,因其单条dml开销大、易锁表、undo爆满;高效方案应为外部表+insert /+ append /组合,由存储过程仅负责调度、清洗与提交。
直接用存储过程循环 insert 千万级数据,基本等于主动卡死数据库——实测 100 万行就可能跑两小时以上,且极易触发锁表、undo 爆满、日志切换风暴。真正能扛住千万级批量入库的组合,是外部表 + 存储过程控制流程,而不是让存储过程去“干体力活”。
为什么不能在存储过程中逐行 INSERT?
Oracle 的 PL/SQL 循环执行 INSERT 本质仍是单条 DML:每条都走解析 → 执行 → 日志写入 → 一致性读检查全流程。即使加了 COMMIT 控制频率,也无法绕过行级锁、索引分裂、约束校验这些开销。
- 实测对比:50 万行 CSV,用 C# 拼 SQL 插入耗时 47 分钟;改用外部表 +
INSERT /*+ APPEND */只需 23 秒 - 存储过程里写
FOR i IN 1..1000000 LOOP INSERT ... END LOOP,不仅慢,还会把 PGA 吃爆(尤其字段多或含 LOB) - 若目标表有外键、非空约束、函数索引,每次
INSERT都要查字典、校验、维护索引结构,性能断崖下跌
外部表定义必须注意的 4 个硬性参数
外部表不是“建完就能用”,漏掉任意一个关键参数,轻则查不到数据,重则整批失败静默退出。
-
REJECT LIMIT UNLIMITED:不加这个,文件里只要有一行格式错(比如多了一个逗号),整个查询就报 ORA-29913,返回 0 行——不是没数据,是全被当“坏行”拒了 -
CHARACTERSET UTF8(或对应源文件编码):源文件是 UTF-8 却没声明,中文全变???;Windows ANSI 文件则要用WE8MSWIN1252 -
DEFAULT DIRECTORY对应的 OS 目录,必须由 DBA 运行CREATE DIRECTORY并授READ权限,且 Oracle 进程用户(如 oracle)要有 OS 层读取权限 -
FIELDS TERMINATED BY ','中的分隔符必须和实际文件严格一致;若字段含逗号,得改用OPTIONALLY ENCLOSED BY '"',否则解析必然错位
存储过程只做三件事:调度、清洗、提交
存储过程在这里的角色是“指挥官”,不是“搬运工”。它不碰数据行,只调用外部表完成批量动作。
- 先用
SELECT COUNT(*) FROM ext_table快速校验文件行数是否符合预期(避免空文件或截断文件误入) - 执行
INSERT /*+ APPEND */ INTO target_table SELECT ... FROM ext_table WHERE ...—— 关键是/*+ APPEND */,它跳过缓冲区直写数据文件,速度提升 10 倍起 - 导入后立刻
ANALYZE TABLE target_table COMPUTE STATISTICS或DBMS_STATS.GATHER_TABLE_STATS,否则后续查询可能走错执行计划 - 别在过程里写
COMMIT:APPEND是直接路径操作,本身已隐式提交;手动加COMMIT不但多余,还可能干扰并行加载
遇到 BADFILE 堆成山怎么办?
外部表报错不直接告诉你哪一行、哪个字段错了,只往 .bad 文件里扔原始行。靠人眼扫效率极低。
- 建外部表时务必配
BADFILE 'xxx.bad'和LOGFILE 'xxx.log',出错后先看 log 文件末尾的 summary,通常会标出前几处典型错误类型(如日期格式不符、数字超长) - 用
SELECT * FROM ext_table WHERE ROWNUM 查前 10 行原始数据,肉眼比对字段顺序、空值占位、引号闭合 - 如果源文件来自 Excel 导出,大概率含不可见字符(如 \r\n 混用、BOM 头),用
od -c all.csv | head在服务器上查看十六进制码 - 临时加
PREPROCESSOR调用 shell 脚本清洗(如 sed 替换非法字符),但要注意 Oracle 用户必须有执行该脚本的权限,且脚本输出必须是标准输入流
最易被忽略的一点:外部表本身不存数据,也不支持索引和约束。所有质量校验、业务逻辑判断,必须在 INSERT INTO target_table SELECT ... 这一步完成——要么用 WHERE 过滤脏数据,要么用 CASE 转换异常值,别指望外部表替你兜底。











