UTF-8编码Oracle数据库spool导出文件时的编码问题求解
我有一套运行在Windows或Unix系统Oracle数据库上的脚本,用于提取数据并输出(spool)到.txt文件中。
为保证文件未被篡改,脚本运行时会对数据做哈希计算,后续会在Web应用中重新校验哈希值。这套流程90%的情况下运行正常,但偶尔会出现哈希不匹配的问题,经排查已排除文件内容本身的差异,确定为编码问题导致。
为识别文件使用的编码,脚本会写入3个在不同编码规则下编码结果不一致的非ASCII字符到文件中,供后端做编码映射识别,对应代码如下:
--Encoding related information SPOOL &&file_desc/Encoding.txt SELECT ('€'||';'||'ƒ'||';'||'‰') FROM sys.dual; SPOOL off
预期结果
在UTF-8编码的数据库上,带非ASCII字符的数据应被正确spool导出,3个用于编码识别的非ASCII字符也应正常写入。
实际结果
当使用和数据库一致的.AL32UTF8系统字符集时,业务数据可以被正确spool,但3个编码识别字符无法正常写入,导致无法识别文件使用的编码规则。
数据库字符集配置(从database_properties视图获取):
- NLS_CHARACTESET: AL32UTF8
- NLS_NCHAR_CHARACTERSET: AL16UTF16
SQL Developer运行正常
使用SQL Developer并将编码设置为UTF8后无任何异常,日文、希腊文字符均可正常显示,编码识别用的3个字符也正常写入,后续哈希校验全部匹配。
SQL*Plus运行异常
我需要该流程在SQL*Plus中也可以正常运行,但测试中一直出现问题,测试用的数据库为Oracle 18c express edition,已尝试的配置方案如下:
SQL*Plus已测试配置
- 仅将字符代码页设置为数据库对应的UTF-8:
chcp 65001(UTF-8对应的代码页),NLS_LANG字符集设置为.WE8MSWIN1252。此时包含日文字符的表名对应的文件出现编码错误:显示为JAPANESE¿,但3个编码识别字符写入正常:€;ƒ;‰ - 不修改代码页,仅更新NLS_LANG字符集:NLS_LANG字符集设置为.AL32UTF8。此时包含日文字符的表名对应的文件显示正常:JAPANESE世,但3个编码识别字符的写入结果为空,仅显示为;;
- 同时修改NLS_LANG和代码页:执行
chcp 65001,NLS_LANG字符集设置为.AL32UTF8。此时包含日文字符的表名对应的文件显示正常:JAPANESE世,但3个编码识别字符的写入结果仍然为空,仅显示为;; - 尝试将NLS_LANG设置为NLS_NCHAR_CHARACTERSET对应值:为排查问题,还尝试将系统字符集设置为和NLS_NCHAR_CHARACTERSET一致的AL16UTF16,但触发报错:
Error 19 initializing SQL*PLUS Invalid NLS character set for this OS environment
结论与疑问
代码页仅影响命令窗口的输出,对spool结果无实质影响。使用WE8MSWIN1252字符集时非ASCII字符无法正常spool属于预期现象,因为该字符集不支持对应字符。但使用支持对应字符的AL32UTF8字符集时,从sys.dual查询的3个非ASCII字符却无法正常spool,花费数天排查仍未找到根因,希望得到大家的帮助。
补充:kfinity提供了可用的解决方案,但暂未明确根因,也欢迎知晓原理的朋友解答。将查询语句替换为以下内容后功能正常:
select unistr('\20AC;\0192;\2030') from dual;
内容的提问来源于stack exchange,提问作者JPReumerman

