SQL*Loader是Oracle自带的批量数据加载工具,它通过一个文本格式的控制文件来描述数据来源、目标表结构以及字段映射关系。很多人装好Oracle之后发现sqlldr命令用起来报错连连,问题十有八九出在控制文件的写法上。控制文件写对了,几百万行数据几分钟就能进库;写错了,不是报字段拒绝就是数据被截断。这篇文章就把控制文件的编写要点掰开揉碎讲清楚,帮你少走弯路。

控制文件的基本结构:先看懂整体框架
一个最简单的控制文件由几个部分组成:LOAD DATA语句开头,INFILE指定数据文件,INTO TABLE指定目标表,然后逐个列出字段映射。下面这个例子是加载一个逗号分隔的CSV文件:
LOAD DATA INFILE 'order_data.csv' BADFILE 'order_bad.bad' INTO TABLE orders FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' TRAILING NULLCOLS ( order_id INTEGER EXTERNAL, customer_name CHAR(50), order_date DATE "YYYY-MM-DD HH24:MI:SS", amount DECIMAL EXTERNAL, remark CHAR(200) )
第一行的LOAD DATA是固定写法,表示一次加载任务的开始。INFILE后面跟数据文件的路径,如果不写路径,sqlldr会在当前目录下找。BADFILE用来记录加载失败被拒绝的行,排查问题时特别有用,建议每次都显式指定,别依赖默认命名。
OPTIONALLY ENCLOSED BY这个子句值得注意,它处理的是CSV文件中字段值被双引号包裹的情况。比如数据里出现 张三,这种带引号的值,配置了这个子句后引号会被自动剥掉,否则引号会跟着数据一起进表,查询出来的名字前面总带着两个引号。TRAILING NULLCOLS则允许数据文件末尾的字段缺失时按NULL处理,不加这个配置,行尾字段少一个就会导致整行被拒绝。
字段分隔与定位:定界符方式和位置方式的选择
控制文件里描述字段位置有两种主流方式。第一种是定界符方式,也就是上面例子中FIELDS TERMINATED BY的写法,适合CSV、制表符分隔这类不规则长度的数据。分隔符不一定是逗号,制表符可以写成TERMINATED BY X'09',管道符可以直接写TERMINATED BY '|',多字符分隔符比如'|||'也是允许的。
第二种是固定位置方式,适合每个字段宽度固定的数据文件,典型场景就是银行、电信行业下发的定长报文。写法如下:
LOAD DATA INFILE 'fixed_len.txt' INTO TABLE account_info ( account_id POSITION(1:10) CHAR, user_name POSITION(11:30) CHAR, balance POSITION(31:42) DECIMAL EXTERNAL, open_date POSITION(43:52) DATE "YYYYMMDD" )
POSITION(1:10)表示从第1个字符到第10个字符截取为account_id字段。定长方式的优点是不依赖分隔符,即使数据本身含有逗号也不会引发字段错位;缺点是数据文件必须严格按约定宽度生成,哪怕一个字节的偏差都会导致数据错乱。实际项目里如果遇到中文字段,还要注意文件编码下一个汉字占的字节数,GBK编码下一个汉字占2字节,UTF-8下占3字节,位置计算必须按字节来,这是定长文件最常见的翻车点。
两种方式也可以混用,比如前面几个字段定长、后面几个字段用分隔符切分,控制文件里先写POSITION定位,剩下的字段再用TERMINATED BY处理即可。
字符集、日期格式与特殊字段的处理技巧
中文字符集问题是SQL*Loader使用中的高频故障。如果数据文件是UTF-8编码,而数据库字符集是GBK,直接加载中文会变成乱码。解决办法是在控制文件里加上CHARACTERSET子句:
LOAD DATA CHARACTERSET UTF8 INFILE 'utf8_data.txt' INTO TABLE t_news FIELDS TERMINATED BY ',' ( news_id INTEGER EXTERNAL, title CHAR(200), content CHAR(4000) )
这样SQL*Loader会先把数据从UTF8转换成数据库字符集再入库。反过来,如果数据文件是GBK而库是UTF-8,就写CHARACTERSET ZHS16GBK。判断文件编码可以用file命令或者编辑器查看,别靠猜。
日期字段是另一个容易出错的地方。数据文件里的日期格式必须和控制文件中DATE子句声明的格式完全一致,比如数据是2024-03-15这种格式,就要写DATE "YYYY-MM-DD"。如果同一份数据里有多种日期格式,可以用多个DATE子句叠加,Oracle会依次尝试解析:
order_date DATE "YYYY-MM-DD HH24:MI:SS", create_time DATE "YYYY/MM/DD HH24:MI:SS"
另外一种做法是在字段名后面加个格式列表:DATE "YYYY-MM-DD" "YYYYMMDD" "DD-MON-YYYY",SQL*Loader会逐个格式去匹配,匹配不上才报错,容错性比单一格式好不少。
遇到换行符出现在字段内容里的场景,比如备注字段包含回车换行,可以在FIELDS子句后加STR子句自定义行终止符,例如STR X'7C0A'表示以竖线加换行符作为一行的结尾,这样含换行的字段值也能正确读入,不会把一行数据拆成两行。
性能优化:常规路径与直接路径的差异
控制文件本身只描述数据格式,真正影响导入速度的是sqlldr命令的参数组合,但控制文件里也有一项配置和性能直接相关。先说两条路径的区别:常规路径走SQL INSERT语句,数据经过SQL处理引擎,会触发索引维护和redo日志生成;直接路径则绕过SQL层,直接在数据库块之上组装数据,速度快得多,尤其是大表首次加载能快出好几倍。
启用直接路径是在命令行加direct=true,但直接路径有几个限制需要清楚:一是加载期间目标表的索引会被置为DIRECT PATH状态需要维护,二是触发器不会触发,三是默认不写redo日志(归档模式下可以通过UNRECOVERABLE控制),四是并发直接路径加载同一张表会锁冲突。控制文件里与之相关的配置是UNRECOVERABLE,写上它之后加载操作不产生归档日志,非归档模式的测试环境可以放心用:
LOAD DATA INFILE 'big_table.dat' UNRECOVERABLE INTO TABLE big_table FIELDS TERMINATED BY '|' ( col1 INTEGER EXTERNAL, col2 CHAR(30), col3 DATE "YYYYMMDD" )
除了路径选择,还有几个实用参数组合:SKIP=n可以跳过前n行,适合带表头的数据文件;ERRORS=1000允许最多1000行出错而不中断任务;ROWS和BINDS参数控制常规路径下的批量提交大小,直接路径下则用ROWS控制每次数据保存的行数。一个典型的生产环境调用命令如下:
sqlldr userid=scott/tiger@orcl control=load.ctl log=load.log \ bad=load.bad direct=true errors=1000 skip=1
最后补充一个数据替换的技巧。默认的INSERT方式要求目标表必须是空表,否则报错。如果想覆盖已有数据,用TRUNCATE INTO TABLE替代INTO TABLE,加载前先清空表,比先手动truncate再加载省一步操作;如果想保留旧数据追加,则用APPEND INTO TABLE。这三种加载模式的区别记牢了,控制文件的骨架基本就齐了。
SQL*Loader控制文件Oracle数据导入修改时间:2026-09-08 19:09:07