MySQL utf8mb4의 두 Collation 차이와 한글 자소 분리 문제를 비교 분석합니다.
Unicode(유니코드)
유니코드는 전 세계의 모든 문자, 기호, 이모지 등을 컴퓨터에서 일관되게 표현하고 다룰 수 있게 만든 국제 표준 규약 이다.
세상의 모든 글자에 고유한 ID를 하나씩 부여한 거대한 문자 백과사전 이라고도 볼 수 있다.
1. 탄생 배경
과거에는 나라 또는 시스템마다 문자를 표현하는 방식(인코딩)이 제각각이었다.
때문에 데이터를 주고 받으면 글자가 깨지는 현상이 비일비재했다.
이를 해결하기 위해서 전 세계적으로 유일한 코드 번호를 부여한 것이다.
2. UTF-8
Unicode Transformasion Format-8-bit 의 준 말로, 유니코드의 코드 포인트를 실제 컴퓨터의 바이트 형태로 저장하는 가장 대중적인 인코딩 방식이다.
2-1. 핵심 : 가변 길이 인코딩
가장 큰 특징은, 문자에 부여된 범위에 따라 저장에 사용하는 바이트 수를 효율적으로 조절하는 가변 길이 인코딩 이라는 점이다.
1바이트 : 영어, 숫자 등 (ASCII 코드와 동일)
2바이트 : 유럽 문자, 아랍어 등
3바이트 : 한글, 한자 등 대부분의 현대 문자
4바이트 : 고대 문자, 수학 기호, 대부분의 이모지 등
utf8mb4_0900_ai_ci 🆚 utf8mb4_general_ci
1. utf8mb4
utf8 : 문자를 저장하는 기반 기술이, UTF-8 인코딩 방식임을 의미한다.
mb4 : most bytes 4 의 줄임말로, 하나의 문자를 표현하기 위해 최대 4바이트까지 사용할 수 있다는 의미이다.
이를 Character Set 이라고 한다.
1-1. utf8mb4가 탄생한 이유
과거 MySQL의 utf8 캐릭터 셋은 UTF-8 표준을 일부만 구현하여 최대 3바이트까지만 지원하였다.
때문에 4바이트가 필요한 문자들을 저장할 수 없었다.
이를 해결하기 위해서 utf8mb4가 탄생하였고, 그 결과 모든 문자를 문제 없이 저장할 수 있게 되었다.
2. 0900_ai_ci
현재 MySQL 8.0의 기본 설정 값이다.
0900 : 기반이 되는 유니코드 표준 버전이 9.0.0 임을 의미한다.
ai(Accent Insensitive) : 악센트 부호(á, à 등)를 원본 문자와 동일하게 취급한다.
ex) a = á
ci(Case Insensitive) : 대소문자를 동일하게 취급한다.
ex) A = a
이러한 설정을 Collation 이라고 한다.
3. general_ci
과거에 속도를 중시하여 널리 사용되었던 Collation이다.
general : 특정 언어 규칙을 엄격하게 따르지 않는 일반적인(General) ****비교 규칙을 사용한다.
즉, 속도를 높이기 위해 정렬 규칙을 단순화한 방식이다.
ex) 독일어에서 ß는 ss와 동일하게 취급되어야 하나, 다르게 취급된다.
ci(Case Insensitive) : 대소문자를 동일하게 취급한다.
ex) A = a
4. 핵심 차이점 요약
구분
utf8mb4_0900_ai_ci (최신 표준)
utf8mb4_general_ci (구버전)
정확성
매우 높음. 다국어와 특수 문자를 언어 규칙에 맞게 정확하게 정렬.
낮음. 단순화된 규칙을 사용해 특정 문자나 언어에서 정렬 오류가 발생할 수 있음
기반 표준
Unicode 9.0.0 (UCA)
구버전의 단순화된 유니코드 규칙
지원 범위
악센트, 대소문자, 이모지, 특수 기호를 폭넓고 정확하게 처리
기본적인 문자 처리에 중점
성능
최신 CPU에 최적화되어 general_ci와 성능 차이가 거의 없음
구버전 MySQL에서는 약간 더 빨랐지만, 이는 정확성을 희생한 결과
기본값
MySQL 8.0 이상의 기본 정렬 규칙
MySQL 8.0 미만 버전에서 널리 사용
utf8mb4_0900_ai_ci의 한글 사용에 대한 문제점
MySQL 8.0 이상 버전부터 기본 콜레이션으로 설정된 utf8mb4_0900_ai_ci 는 한글이나 동아시아 계열의 문자를 사용하는 나라에서는 치명적인 문제가 있다.
한글의 자음 모음, 일본어 가타카나/히라가나를 같은 문자열로 인식하여 처리 하기 때문에 글로벌 서비스인 경우 콜레이션 설정을 반드시 주의해야 한다.
1. utf8mb4_general_ci 에서 한글 데이터를 조건으로 검색한 결과
mysql> select no, name, length(name), hex(name) from test1 t1 -> where name = '가나다';+----+-----------+--------------+--------------------+| no | name | length(name) | hex(name) |+----+-----------+--------------+--------------------+| 1 | 가나다 | 9 | EAB080EB8298EB8BA4 |+----+-----------+--------------+--------------------+1 row in set (0.00 sec)mysql> select no, name, length(name), hex(name) from test1 t1 -> where name = 'ㄱㅏ나다';+----+--------------+--------------+--------------------------+| no | name | length(name) | hex(name) |+----+--------------+--------------+--------------------------+| 2 | ㄱㅏ나다 | 12 | E384B1E3858FEB8298EB8BA4 |+----+--------------+--------------+--------------------------+1 row in set (0.00 sec)mysql> select no, name, length(name), hex(name) from test1 t1 -> where name = 'ㄱㅏㄴㅏㄷㅏ';+----+--------------------+--------------+--------------------------------------+| no | name | length(name) | hex(name) |+----+--------------------+--------------+--------------------------------------+| 3 | ㄱㅏㄴㅏㄷㅏ | 18 | E384B1E3858FE384B4E3858FE384B7E3858F |+----+--------------------+--------------+--------------------------------------+1 row in set (0.00 sec)
정확히 동일한 문자열만 조회한다.
2. utf8mb4_0900_ai_ci에서 한글 데이터를 조건으로 검색한 결과
mysql> select no, name, length(name), hex(name) from test2 t2 -> where name = '가나다';+----+--------------------+--------------+--------------------------------------+| no | name | length(name) | hex(name) |+----+--------------------+--------------+--------------------------------------+| 1 | 가나다 | 9 | EAB080EB8298EB8BA4 || 2 | ㄱㅏ나다 | 12 | E384B1E3858FEB8298EB8BA4 || 3 | ㄱㅏㄴㅏㄷㅏ | 18 | E384B1E3858FE384B4E3858FE384B7E3858F |+----+--------------------+--------------+--------------------------------------+3 rows in set (0.00 sec)mysql> select no, name, length(name), hex(name) from test2 t2 -> where name = 'ㄱㅏ나다';+----+--------------------+--------------+--------------------------------------+| no | name | length(name) | hex(name) |+----+--------------------+--------------+--------------------------------------+| 1 | 가나다 | 9 | EAB080EB8298EB8BA4 || 2 | ㄱㅏ나다 | 12 | E384B1E3858FEB8298EB8BA4 || 3 | ㄱㅏㄴㅏㄷㅏ | 18 | E384B1E3858FE384B4E3858FE384B7E3858F |+----+--------------------+--------------+--------------------------------------+3 rows in set (0.00 sec)mysql> select no, name, length(name), hex(name) from test2 t2 -> where name = 'ㄱㅏㄴㅏㄷㅏ';+----+--------------------+--------------+--------------------------------------+| no | name | length(name) | hex(name) |+----+--------------------+--------------+--------------------------------------+| 1 | 가나다 | 9 | EAB080EB8298EB8BA4 || 2 | ㄱㅏ나다 | 12 | E384B1E3858FEB8298EB8BA4 || 3 | ㄱㅏㄴㅏㄷㅏ | 18 | E384B1E3858FE384B4E3858FE384B7E3858F |+----+--------------------+--------------+--------------------------------------+3 rows in set (0.00 sec)
길이나 hex 값이 다름에도, 모두 동일한 글자로 인식하고 결과값으로 모두 반환한다.
3. 후행 공백(Trailing Spaces) 처리 방식의 차이
두 콜레이션은 문자열 끝에 있는 공백을 처리하는 규칙이 다르다.
utf8mb4_general_ci : 문자열 끝의 공백을 무시하고 비교한다. (PAD SPACE 규칙)
utf8mb4_0900_ai_ci : 문자열 끝의 공백을 의미 있는 다른 문자로 인식한다. (NO PAD 규칙)
3-1. general_ci: 공백을 무시하여 2개 모두 검색됨
mysql> SELECT * FROM test3 WHERE name = 'test';+--------+| name |+--------+| test || test |+--------+
3-2. 0900_ai_ci: 공백을 인식하여 정확히 1개만 검색됨
mysql> SELECT * FROM test4 WHERE name = 'test';+------+| name |+------+| test |+------+
결론 : 상황별 추천 콜레이션
1. utf8mb4_0900_ai_ci
다양한 언어의 텍스트를 다루는 글로벌 서비스 또는 일반적인 웹 애플리케이션을 구축할 때
언어학적 규칙에 맞는 정확한 정렬이 중요할 때
ex) 사용자들이 Müller와 Muller, cafe와 café를 입력했을 때, 이를 유사한 단어로 인식하고 검색하거나 정렬해야 하는 경우
2. utf8mb4_general_ci
한글 데이터가 중심이며, 완성형 글자(가)와 자소 분리 글자(ㄱ+ㅏ)를 의도적으로 다르게 취급하고 싶을 때
UNIQUE 키 등에서 두 형태가 명확히 구분되어야 할 때
후행 공백을 무시하고 비교하고 싶을 때
3. utf8mb4_bin
대소문자를 반드시 구분해야 하는 데이터일 때
가장 빠르게 비교 및 정렬하고 싶을 때
4. 요약
Collation
핵심 특징
주요 사용 사례
utf8mb4_0900_ai_ci
정확성, 국제 표준, 언어학적 규칙
글로벌 서비스 또는 대부분의 현대 웹 애플리케이션 (예: cafe = café 처리가 필요할 때)
utf8mb4_general_ci
단순/직관적 비교, 한글 자소 분리
한글 중심 서비스에서 완성형/조합형 글자를 명확히 구분하고 싶을 때 (예: 가 ≠ ㄱ+ㅏ)
utf8mb4_bin
바이너리(바이트) 값 비교, 대소문자 구분
API 키, 비밀번호, 인증 토큰 등 대소문자를 포함한 모든 문자를 정확히 구분해야 할 때