根本原因是数据库字符集与客户端字符集不一致,解决关键是统一会话级NLS_CHARACTERSET:查数据库字符集用SELECT value FROM nls_database_parameters WHERE parameter='NLS_CHARACTERSET',查会话字符集用SELECT value FROM nls_session_parameters WHERE parameter='NLS_CHARACTERSET',JDBC需在Properties中显式设props.put("charset", "AL32UTF8"),PL/SQL Developer等工具须手动指定匹配字符集,REF CURSOR完全依赖会话字符集解码。
oracle 存储过程返回结果集乱码,根本原因是数据库字符集与客户端(如 jdbc、pl/sql developer、sql*plus 或 .net 环境)的字符集不一致,导致中文在传输或渲染时被错误解码。解决的关键不是改存储过程逻辑,而是对齐两端的字符集协商机制。
查清数据库实际字符集和会话字符集
很多问题卡在“以为设对了”,其实没生效。必须用实际查询确认:
-
SELECT value FROM nls_database_parameters WHERE parameter = 'NLS_CHARACTERSET';—— 查数据库级字符集(如AL32UTF8或ZHS16GBK),这是底座,不能靠会话覆盖 -
SELECT value FROM nls_session_parameters WHERE parameter = 'NLS_CHARACTERSET';—— 查当前会话字符集,它可能被客户端环境变量或连接参数覆盖 - 注意:
NLS_LANG环境变量决定会话默认字符集,但它的值格式必须是LANGUAGE_TERRITORY.CHARACTERSET(如SIMPLIFIED CHINESE_CHINA.AL32UTF8),缺一不可
JDBC 连接必须显式指定 charset 参数
Oracle JDBC 驱动(ojdbc8.jar 及以上)默认不读取系统 NLS_LANG,必须在连接 URL 或 Properties 中硬编码 charset:
- URL 方式:
jdbc:oracle:thin:@host:port:SID?useUnicode=true&characterEncoding=UTF-8—— 但注意:Oracle 官方不认characterEncoding,此参数对 Oracle 无效 - 正确方式:用
oracle.jdbc.defaultNChar+oracle.jdbc.useFetchSizeWithLongColumn不起作用;真正有效的是在Properties中传"charset"键:props.put("charset", "AL32UTF8"); - 若用 Spring Boot,
spring.datasource.hikari.data-source-properties.charset=AL32UTF8是可靠写法 - 错例:
props.put("NLS_LANG", "AMERICAN_AMERICA.AL32UTF8")—— JDBC 不解析这个,纯无效
PL/SQL Developer 或 SQL*Plus 的字符集设置陷阱
图形工具常“自动猜测”字符集,反而掩盖问题:
- PL/SQL Developer:菜单 Tools → Preferences → Oracle → Connection → Character set,必须手动选成和数据库一致的值(如
AL32UTF8),不能留空或选Auto Detect - SQL*Plus:启动前必须设环境变量,Windows 下执行:
set NLS_LANG=SIMPLIFIED CHINESE_CHINA.AL32UTF8,Linux 下:export NLS_LANG=AMERICAN_AMERICA.AL32UTF8;设完再启动 sqlplus,否则 session 字符集仍是US7ASCII - 验证是否生效:连上后立即查
SELECT * FROM nls_session_parameters WHERE parameter = 'NLS_CHARACTERSET';,结果必须和数据库一致
存储过程中无需修改 SQL,但要注意 UTL_RAW 和 NCHAR 类型
乱码通常不出现在存储过程内部,而出现在结果集传出环节。但两类情况要特别小心:
- 如果过程里用了
UTL_RAW.CAST_TO_RAW()(如 C# 场景中为绕过乱码而手动转 raw),那应用端必须用对应编码反解,比如Encoding.UTF8.GetString((byte[])dr["name"])—— 这属于“带病运行”,不是根治 - 避免混用
VARCHAR2和NVARCHAR2:前者走数据库字符集(NLS_CHARACTERSET),后者走国家字符集(NLS_NCHAR_CHARACTERSET)。查SELECT value FROM nls_database_parameters WHERE parameter = 'NLS_NCHAR_CHARACTERSET';,确保它也是AL32UTF8,否则NVARCHAR2字段单独乱码 - 函数返回
REF CURSOR时,游标本身不携带字符集信息,完全依赖会话级NLS_CHARACTERSET解码 —— 所以统一会话字符集比改过程更重要
最容易被忽略的点是:数据库字符集一旦建库就固定,而客户端字符集设置(尤其是 JDBC 的 charset 属性)必须与之严格匹配,差一个字母(如 UTF8 vs AL32UTF8)都会失败。AL32UTF8 是 Oracle 对 UTF-8 的实现名,不是别名 —— 写错就等于没设。


















