한글이 들어간 데이터나 오류 메시지가 ?? · ???? 로만 출력된다. 데이터가 깨진 것이 아니라 출력 단계에서 표현하지 못한 것인 경우가 대부분이다.
pgsql, ??: "text" ???(schema) ??
세 지점이 모두 맞아야 글자가 제대로 보인다. 데이터베이스 인코딩, 세션의 client_encoding, 그리고 터미널이 실제로 그릴 수 있는 문자 집합이다.
SHOW server_encoding;
SHOW client_encoding;
SHOW lc_messages;
echo $LANG
locale
데이터가 실제로 멀쩡한지는 바이트 길이로 확인한다. UTF-8 한글은 한 글자가 3바이트다.
SELECT name, length(name) AS chars, octet_length(name) AS bytes FROM app.users LIMIT 5;
chars 와 bytes 가 3배 관계면 데이터는 정상이고 출력만 깨진 것이다.
LANG 이 C 이거나 POSIX 이면 psql 이 다중 바이트 문자를 물음표로 바꿔 출력한다. 서버 로그 메시지도 마찬가지다.
export LANG=ko_KR.UTF-8
export LC_ALL=ko_KR.UTF-8
해당 로케일이 설치돼 있어야 한다.
localectl list-locales | grep -i ko_KR
dnf install -y glibc-langpack-ko
Windows 명령 프롬프트에서 psql 을 쓰면 코드 페이지가 949 라 UTF-8 출력이 깨진다. 코드 페이지를 바꾸고 클라이언트 인코딩을 맞춘다.
chcp 65001
\encoding UTF8
서버는 UTF8 인데 클라이언트가 다른 인코딩을 요구하면 변환할 수 없는 글자가 물음표가 된다.
SET client_encoding TO 'UTF8';
psql 에서는 메타 명령으로도 된다.
\encoding
\encoding UTF8
환경 변수로 고정할 수도 있다.
export PGCLIENTENCODING=UTF8
lc_messages 가 로케일에 맞지 않으면 서버가 보내는 메시지만 깨진다. 운영에서는 오히려 메시지를 영어로 고정하는 편이 검색과 공유에 유리하다.
lc_messages = 'C'
octet_length 가 글자 수와 같다면 적재 시점에 이미 깨진 것이다. 이 경우 출력 설정을 바꿔도 복구되지 않는다. 원본을 다시 적재해야 한다. 적재 시 인코딩을 명시한다.
\copy app.users FROM 'users.csv' WITH (FORMAT csv, HEADER true, ENCODING 'EUC_KR')