분명히 잘 들어가던 한글 데이터가 어느 날 갑자기 ???한글 같은 외계어로 바뀌어 보인 적 있으신가요. 게시판에 글을 쓰면 멀쩡한데, 터미널에서 덤프를 떴다가 다시 넣으면 죄다 깨지는 상황. 많은 개발자가 여기서 "테이블 문자셋을 utf8로 바꿨는데 왜 안 되지?"라며 몇 시간을 날립니다.

한글 깨짐의 90%는 파일이 잘못된 게 아니라, 데이터가 오가는 길목의 문자셋이 어긋난 탓입니다.

오늘은 이 지긋지긋한 인코딩 문제를 길목별로 추적하는 방법을 정리합니다.

왜 utf8이 아니라 utf8mb4인가

MySQL의 utf8은 사실 진짜 UTF-8이 아닙니다. 한 글자를 최대 3바이트까지만 저장하는 반쪽짜리 구현이라, 이모지(😀)나 일부 한자처럼 4바이트가 필요한 문자를 넣으면 그 자리에서 잘리거나 에러가 납니다.

진짜 UTF-8은 utf8mb4입니다. 요즘 만드는 테이블은 예외 없이 이걸 써야 합니다. 정렬·비교 규칙인 콜레이션은 utf8mb4_unicode_ci를 권장합니다.

SET NAMES utf8mb4;

CREATE TABLE board (
  id     INT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '글 번호',
  title  VARCHAR(200) NOT NULL COMMENT '제목',
  body   TEXT NOT NULL COMMENT '본문',
  PRIMARY KEY (id)
) ENGINE=InnoDB
  DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_unicode_ci
  COMMENT='게시판';

테이블만 utf8mb4로 만들어 놓고 끝냈다면, 절반만 한 겁니다. 진짜 함정은 그다음에 있습니다.

진짜 범인은 '연결(connection) 문자셋'

데이터는 애플리케이션 → 커넥션 → 테이블 순으로 흘러갑니다. 테이블이 utf8mb4여도, PHP와 MySQL 사이의 연결이 latin1이면 그 길목에서 한글이 깨집니다. 컬럼은 멀쩡한데 화면만 깨져 보이는 전형적인 증상이 바로 이것입니다.

PHP의 mysqli를 쓴다면, 연결 직후 단 한 줄로 해결됩니다.

<?php
$conn = mysqli_connect("localhost", "user", "pw", "mydb");

// 이 한 줄이 핵심 — 커넥션 문자셋을 맞춘다
mysqli_set_charset($conn, "utf8mb4");

$sql = "SELECT title FROM board WHERE id = 1";
$res = mysqli_query($conn, $sql);
?>

mysqli_set_charset()는 내부적으로 SET NAMES utf8mb4를 안전하게 실행해 줍니다. 이 한 줄을 빼먹어서 생기는 깨짐이 현장에서 가장 흔합니다.

터미널에서 임포트할 때 또 깨지는 이유

웹에서는 멀쩡한데 mysql < dump.sql로 넣으면 깨진다면, 이번엔 임포트 클라이언트의 문자셋이 문제입니다. 파일은 UTF-8인데, 클라이언트가 기본값(latin1 등)으로 읽어 들이면서 또 한 번 어긋나는 거죠.

# 반드시 문자셋을 명시해서 임포트
mysql --default-character-set=utf8mb4 -u root -p mydb < dump.sql

덤프를 뜰 때도 마찬가지로 지정해 줍니다.

mysqldump --default-character-set=utf8mb4 -u root -p mydb > dump.sql

여기서 기억할 원칙 하나. 한글이 깨졌을 때 파일을 의심하기 전에 '데이터가 지나간 길목의 문자셋'을 먼저 점검하세요. 파일을 아무리 다시 저장해도, 임포트 명령의 문자셋이 틀려 있으면 결과는 똑같습니다.

길목별 점검 체크리스트

깨짐이 보일 때 아래 네 군데를 순서대로 확인하면 거의 다 잡힙니다.

길목확인할 것올바른 값
DB/테이블/컬럼CHARSET, COLLATEutf8mb4 / utf8mb4_unicode_ci
커넥션mysqli_set_charsetutf8mb4
소스 파일파일 저장 인코딩UTF-8 (BOM 없음)
출력 헤더HTML meta / Content-Typecharset=utf-8

네 길목의 문자셋이 모두 utf8mb4로 일치하면, 한글은 절대 깨지지 않습니다. 반대로 단 한 군데라도 어긋나면, 나머지를 아무리 손봐도 증상이 사라지지 않습니다.

이미 깨진 데이터는 어떻게 복구하나

이미 잘못 저장된 데이터는 자동으로 고쳐지지 않습니다. 운이 좋으면 latin1로 잘못 해석된 바이트가 보존돼 있어 변환으로 살릴 수 있지만, 이미 ?로 치환돼 버린 글자는 원본 정보가 사라져 복구가 불가능합니다.

그래서 가장 현실적인 답은 예방입니다. 새 프로젝트를 시작할 때 테이블·커넥션·파일·출력 헤더를 처음부터 utf8mb4로 통일해 두면, 이 문제로 시간을 버릴 일이 없습니다.


한글 깨짐은 신비한 현상이 아니라, 문자셋이 어긋난 길목을 찾는 추적 게임입니다. 다음에 ???를 만나면 당황하지 말고 테이블 → 커넥션 → 파일 → 출력 순서로 한 칸씩 점검해 보세요. 대부분은 mysqli_set_charset 한 줄이나 임포트 옵션 하나에서 끝납니다. 오늘의 삽질이 내일의 누군가에게는 5분 만에 끝나는 일이 되기를 바랍니다.