서버에서 조회하면 한글이 멀쩡한데 특정 클라이언트 도구에서만 물음표나 깨진 글자로 보인다. 같은 DB 를 다른 도구로 보면 정상인 경우도 있다. 데이터가 망가진 것이 아니라 클라이언트가 문자셋을 잘못 알린 것이다.
Oracle 은 서버의 데이터베이스 문자셋과 클라이언트가 선언한 문자셋이 다르면 자동으로 변환해서 넘긴다. 클라이언트가 자기 문자셋을 무엇이라고 선언하는지가 NLS_LANG 이다.
NLS_LANG = <언어>_<지역>.<문자셋>
예를 들면 다음과 같다.
KOREAN_KOREA.KO16MSWIN949
KOREAN_KOREA.AL32UTF8
AMERICAN_AMERICA.AL32UTF8
핵심은 NLS_LANG 의 문자셋이 클라이언트 프로그램이 실제로 다루는 인코딩 과 같아야 한다는 것이다. 서버 문자셋과 같게 맞추는 것이 아니다. 여기서 대부분 헷갈린다.
.AL32UTF8.KO16MSWIN949선언이 실제와 다르면 Oracle 이 변환할 필요가 없다고 판단해 그대로 넘기거나, 엉뚱한 변환을 해서 깨진다.
SELECT parameter, value
FROM nls_database_parameters
WHERE parameter IN ('NLS_CHARACTERSET', 'NLS_NCHAR_CHARACTERSET');
KO16MSWIN949 인 DB 에 UTF-8 클라이언트로 붙으면 Oracle 이 변환한다. 이때 949 에 없는 문자는 복원할 수 없는 손실이 생긴다. 반대로 AL32UTF8 DB 에 949 클라이언트로 붙으면 949 로 표현되지 않는 문자가 물음표가 된다.
세션이 실제로 무엇으로 붙었는지는 다음으로 본다.
SELECT * FROM nls_session_parameters;
리눅스와 유닉스는 환경변수다. 애플리케이션을 띄우는 셸에서 설정해야 한다.
export NLS_LANG=KOREAN_KOREA.AL32UTF8
Windows 는 환경변수와 레지스트리 둘 다 본다. 환경변수가 우선한다.
HKLM\SOFTWARE\ORACLE\KEY_<HOME_NAME>\NLS_LANG
서비스로 도는 프로그램은 로그인 세션의 환경변수를 읽지 못한다. 시스템 환경변수로 넣거나 서비스 정의에 직접 넣는다.
JDBC 드라이버는 NLS_LANG 을 쓰지 않는다. Java 가 문자열을 유니코드로 다루고 드라이버가 직접 변환하므로, DBeaver 같은 JDBC 도구는 대체로 문제가 없다. 이 때문에 "다른 도구는 되는데 이 도구만 깨진다" 는 상황이 생긴다. 깨지는 쪽은 대개 OCI 기반(ODBC, sqlplus, 각종 네이티브 클라이언트) 이다.
따라서 문제가 특정 도구에서만 난다면, 그 도구가 OCI 를 쓰는지부터 확인하고 그 프로세스의 NLS_LANG 을 본다.
ODBC 드라이버를 고를 때는 ANSI 판이 아니라 Unicode 판을 쓴다. 드라이버 이름이나 DSN 설정에 그 구분이 있다.
문자셋 문제는 Oracle 만의 것이 아니다. 클라이언트가 선언한 인코딩과 실제 인코딩이 어긋나면 어디서든 같은 증상이 난다.
| 대상 | 클라이언트 인코딩 지정 |
|---|---|
| PostgreSQL · Redshift | client_encoding, ODBC DSN 의 문자셋 항목 |
| MySQL · MariaDB | character_set_client, 연결 문자열의 charset |
| Oracle | NLS_LANG |
어느 쪽이든 "서버는 무엇으로 저장하는가" 와 "클라이언트는 무엇으로 주고받겠다고 선언했는가" 를 따로 확인하는 것이 순서다.
ALTER DATABASE CHARACTER SET 을 임의로 실행하면 기존 데이터가 깨진다. Oracle 이 제공하는 마이그레이션 도구를 쓴다.NLS_LANG 의 언어와 지역 부분은 날짜 형식과 정렬 순서에도 영향을 준다. 문자셋만 생각하고 바꾸면 쿼리 결과의 날짜 표기가 달라질 수 있다.