---
title: "MySQL 문자셋과 Collation 기초: utf8mb4, 정렬 규칙, 인덱스 영향"
description: "MySQL에서 문자셋과 collation이 저장, 비교, 정렬, 인덱스 선택성에 미치는 영향을 운영 관점에서 정리한다."
tags: [ MySQL, 운영, 성능최적화, 인덱스, DBA ]
image: "mysql-report-bg.png"
published: "2026-05-19"
updated: "2026-05-19"
author: "MySQL 기술 노트"
source_url: ""
---

## 1. 운영자가 문자셋과 collation을 이해해야 하는 이유

MySQL에서 문자셋(character set)은 문자열을 어떤 바이트 표현으로 저장할지를 결정하고, collation은 저장된 문자열을 어떤 규칙으로 비교하고 정렬할지를 결정한다. 두 설정은 단순히 “한글이 깨지지 않게 하는 옵션”이 아니다. 같은 문자열이라도 문자셋과 collation에 따라 저장 가능한 문자 범위, 비교 결과, `ORDER BY` 결과, `LIKE` 검색 동작, 유니크 인덱스 충돌 여부, 인덱스 크기, 쿼리 실행 계획이 달라질 수 있다.

운영 환경에서는 다음과 같은 문제가 문자셋과 collation에서 시작되는 경우가 많다.

- 이모지 또는 일부 다국어 문자가 저장되지 않고 `Incorrect string value` 오류가 발생한다.
- 개발 환경에서는 중복이 아니던 값이 운영 환경의 유니크 인덱스에서는 중복으로 판단된다.
- `WHERE name = 'abc'` 조건이 예상과 다르게 대소문자를 구분하지 않는다.
- `ORDER BY` 결과가 바이너리 코드 순서와 다르게 보이며, 애플리케이션 정렬 결과와 일치하지 않는다.
- 서로 다른 collation 컬럼을 조인하거나 비교할 때 `Illegal mix of collations` 오류가 발생한다.
- 인덱스 길이 제한, 정렬 버퍼 사용량, 임시 테이블 사용량이 예상보다 커진다.

따라서 문자셋과 collation은 스키마 설계 초기에 정해야 하는 기본 정책이며, 이미 운영 중인 시스템에서는 변경 영향도를 충분히 평가한 뒤 단계적으로 적용해야 하는 구조적 속성이다.

## 2. 문자셋과 collation의 역할 분리

문자셋과 collation은 함께 지정되는 경우가 많지만 역할은 다르다.

| 구분 | 의미 | 대표 예 | 운영 영향 |
|---|---|---|---|
| 문자셋 | 문자를 저장하는 바이트 인코딩 | `utf8mb4`, `latin1`, `euckr` | 저장 가능한 문자, 컬럼 바이트 길이, 인덱스 크기 |
| collation | 문자열 비교와 정렬 규칙 | `utf8mb4_0900_ai_ci`, `utf8mb4_bin` | 대소문자/악센트 구분, 유니크 판정, 정렬 결과, 인덱스 탐색 |

예를 들어 `utf8mb4`는 Unicode 문자를 최대 4바이트로 저장할 수 있는 MySQL의 표준적인 Unicode 문자셋이다. 반면 `utf8mb4_0900_ai_ci`는 Unicode 9.0 기반의 accent-insensitive, case-insensitive 비교 규칙이다. 즉 `utf8mb4`가 “무엇을 저장할 수 있는가”를 정한다면, `utf8mb4_0900_ai_ci`는 “저장된 문자열을 어떻게 같다고 보고 어떤 순서로 정렬할 것인가”를 정한다.

MySQL의 `utf8`이라는 이름은 주의해야 한다. 오래된 MySQL에서 `utf8`은 실제로 최대 3바이트 문자만 저장하는 `utf8mb3` 별칭으로 사용되어 왔다. 이 설정은 이모지, 일부 보조 평면 문자, 특정 희귀 한자 등을 저장하지 못한다. 신규 시스템에서는 특별한 이유가 없다면 `utf8mb4`를 기본 문자셋으로 선택하는 것이 안전하다.

## 3. 기본값의 계층 구조와 상속

MySQL은 문자셋과 collation을 여러 계층에서 지정한다.

1. 서버 기본값
2. 데이터베이스 기본값
3. 테이블 기본값
4. 컬럼별 설정
5. 연결 세션의 character set 설정
6. SQL 표현식 또는 literal의 collation

컬럼에 문자셋과 collation을 명시하지 않으면 테이블 기본값을 상속하고, 테이블에 명시하지 않으면 데이터베이스 기본값을 상속한다. 이 상속 구조 때문에 같은 인스턴스 안에서도 과거에 생성된 테이블과 최근에 생성된 테이블의 collation이 다를 수 있다. 운영자가 “우리 DB는 `utf8mb4`를 쓴다”고 생각해도 실제 컬럼 단위에서는 `latin1`, `utf8mb3`, `utf8mb4_general_ci`, `utf8mb4_unicode_ci`, `utf8mb4_0900_ai_ci`가 섞여 있을 수 있다.

현재 설정은 다음 쿼리로 확인할 수 있다.

```sql
SHOW VARIABLES LIKE 'character_set%';
SHOW VARIABLES LIKE 'collation%';

SELECT
    schema_name,
    default_character_set_name,
    default_collation_name
FROM information_schema.schemata
WHERE schema_name NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema')
ORDER BY schema_name;
```

실행 결과(MySQL 8.0.46):

```text
+--------------------------+--------------------------------+
| Variable_name            | Value                          |
+--------------------------+--------------------------------+
| character_set_client     | latin1                         |
| character_set_connection | latin1                         |
| character_set_database   | utf8mb4                        |
| character_set_filesystem | binary                         |
| character_set_results    | latin1                         |
| character_set_server     | utf8mb4                        |
| character_set_system     | utf8mb3                        |
| character_sets_dir       | /usr/share/mysql-8.0/charsets/ |
+--------------------------+--------------------------------+
+----------------------+--------------------+
| Variable_name        | Value              |
+----------------------+--------------------+
| collation_connection | latin1_swedish_ci  |
| collation_database   | utf8mb4_0900_ai_ci |
| collation_server     | utf8mb4_0900_ai_ci |
+----------------------+--------------------+
+-----------------+----------------------------+------------------------+
| SCHEMA_NAME     | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COL
... <truncated 235 chars>
```

테이블과 컬럼 단위의 실제 설정은 다음처럼 점검한다.

```sql
SELECT
    table_schema,
    table_name,
    table_collation
FROM information_schema.tables
WHERE table_schema = 'appdb'
  AND table_type = 'BASE TABLE'
ORDER BY table_name;

SELECT
    table_schema,
    table_name,
    column_name,
    character_set_name,
    collation_name,
    data_type,
    character_maximum_length
FROM information_schema.columns
WHERE table_schema = 'appdb'
  AND character_set_name IS NOT NULL
ORDER BY table_name, ordinal_position;
```

문자셋 문제를 조사할 때는 서버 변수만 보지 말고 반드시 컬럼 단위까지 내려가야 한다. 실제 비교와 저장은 결국 컬럼 정의를 기준으로 수행되기 때문이다.

## 4. `utf8mb4`를 기본으로 선택해야 하는 이유

현대 MySQL 운영에서 신규 스키마의 기본 문자셋은 대체로 `utf8mb4`가 적절하다. 이유는 명확하다.

- Unicode 전체 범위에 가까운 문자를 저장할 수 있다.
- 이모지, 다국어 이름, 외부 서비스에서 유입되는 사용자 입력을 안전하게 처리한다.
- MySQL 8.0의 기본 방향과 맞다.
- 애플리케이션, API, JSON 문서, 로그성 데이터와의 호환성이 좋다.

`utf8mb3` 또는 오래된 `utf8` 기반 시스템은 당장 장애가 없더라도 데이터 유입 경로가 다양해질수록 위험해진다. 사용자명, 게시글, 외부 연동 메시지, 모바일 입력값은 이모지와 보조 평면 문자를 포함할 수 있다. 이때 컬럼이 `utf8mb3`이면 다음과 같은 오류가 발생할 수 있다.

```text
ERROR 1366 (HY000): Incorrect string value: '\xF0\x9F\x98\x80' for column 'title' at row 1
```

이 오류는 애플리케이션 코드의 문자열 처리 문제가 아니라, 컬럼이 해당 문자를 저장할 수 없는 문자셋으로 정의되어 있다는 신호일 수 있다.

다만 `utf8mb4`는 문자당 최대 4바이트를 사용할 수 있으므로 인덱스와 정렬 작업의 메모리 사용량을 고려해야 한다. `VARCHAR(255)`는 글자 수 기준으로 255자지만, `utf8mb4`에서는 최대 1020바이트가 될 수 있다. MySQL 5.7 이후의 InnoDB는 큰 인덱스 prefix를 더 잘 지원하지만, 오래된 버전이나 특수한 row format에서는 인덱스 길이 제한을 만날 수 있다.

## 5. 주요 collation의 성격: `_ci`, `_cs`, `_ai`, `_as`, `_bin`

MySQL collation 이름에는 비교 규칙의 성격이 들어 있다.

- `_ci`: case-insensitive. 대소문자를 구분하지 않는다.
- `_cs`: case-sensitive. 대소문자를 구분한다.
- `_ai`: accent-insensitive. 악센트 차이를 구분하지 않는다.
- `_as`: accent-sensitive. 악센트 차이를 구분한다.
- `_bin`: 문자 코드의 바이너리 값에 가깝게 비교한다.

예를 들어 `utf8mb4_0900_ai_ci`는 대소문자와 악센트 차이를 비교에서 무시한다. 반면 `utf8mb4_bin`은 코드포인트 기반의 엄격한 비교에 가깝다. 사용자 검색에는 대소문자를 구분하지 않는 collation이 편리할 수 있지만, 토큰, 외부 ID, 해시, 쿠폰 코드, 인증 관련 문자열에는 `bin` 또는 명시적인 대소문자 구분 규칙이 더 안전하다.

다음 예시는 collation에 따라 비교 결과가 달라질 수 있음을 보여준다.

```sql
SELECT
    _utf8mb4'A' = _utf8mb4'a' COLLATE utf8mb4_0900_ai_ci AS ci_equal,
    _utf8mb4'A' = _utf8mb4'a' COLLATE utf8mb4_bin        AS bin_equal;
```

실행 결과(MySQL 8.0.46):

```text
+----------+-----------+
| ci_equal | bin_equal |
+----------+-----------+
|        1 |         0 |
+----------+-----------+
```

유니크 인덱스에서도 같은 원리가 적용된다. `utf8mb4_0900_ai_ci` 컬럼에 유니크 인덱스가 있으면 `ABC`와 `abc`가 같은 값으로 취급될 수 있다. 로그인 ID나 이메일처럼 대소문자 무시가 의도라면 적절하지만, 외부 시스템의 case-sensitive 식별자를 그대로 저장하는 컬럼이라면 치명적인 설계 오류가 될 수 있다.

```sql
DROP TABLE IF EXISTS collation_demo;

CREATE TABLE collation_demo (
    code VARCHAR(50) COLLATE utf8mb4_0900_ai_ci NOT NULL,
    UNIQUE KEY uq_code (code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO collation_demo VALUES ('ABC');
-- 대소문자 무시 collation에서는 같은 값으로 판단되어 무시되고 경고가 남는다.
INSERT IGNORE INTO collation_demo VALUES ('abc');

SHOW WARNINGS;
SELECT code FROM collation_demo;

DROP TABLE collation_demo;
```

실행 결과(MySQL 8.0.46):

```text
+---------+------+--------------------------------------------------------+
| Level   | Code | Message                                                |
+---------+------+--------------------------------------------------------+
| Warning | 1062 | Duplicate entry 'abc' for key 'collation_demo.uq_code' |
+---------+------+--------------------------------------------------------+
+------+
| code |
+------+
| ABC  |
+------+
```

## 6. 비교, 정렬, 인덱스 탐색에서의 내부 동작

문자열 인덱스는 단순히 원문 바이트만을 기준으로 정렬되는 것이 아니다. collation은 문자열을 비교 가능한 sort weight로 해석한다. B-tree 인덱스에서 키를 탐색하거나 정렬할 때 MySQL은 해당 컬럼의 collation 규칙에 따라 값의 순서를 판단한다.

이 구조 때문에 다음과 같은 현상이 생긴다.

첫째, case-insensitive collation에서는 `abc`, `Abc`, `ABC`가 같은 비교 그룹에 속할 수 있다. 인덱스 탐색 자체는 가능하지만, 유니크 판정과 범위 조건의 의미가 바이너리 비교와 다르다.

둘째, `ORDER BY` 결과는 단순 ASCII 또는 Unicode 코드포인트 순서와 다를 수 있다. 언어권별 정렬 규칙은 사용자가 자연스럽다고 느끼는 순서를 만들기 위해 문자별 가중치를 적용한다. 따라서 애플리케이션 메모리 정렬과 DB 정렬 결과가 다를 수 있다.

셋째, 서로 다른 collation을 가진 컬럼을 비교하면 MySQL이 collation coercibility 규칙에 따라 하나의 collation으로 변환해 비교하려고 한다. 변환 규칙이 명확하지 않거나 호환되지 않으면 `Illegal mix of collations` 오류가 발생한다.

```sql
DROP TABLE IF EXISTS table_a;
DROP TABLE IF EXISTS table_b;

CREATE TABLE table_a (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50) COLLATE utf8mb4_0900_ai_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE table_b (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50) COLLATE utf8mb4_bin NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

SELECT table_name, column_name, collation_name
FROM information_schema.columns
WHERE table_schema = DATABASE()
  AND table_name IN ('table_a', 'table_b')
  AND column_name = 'name'
ORDER BY table_name;
```

실행 결과(MySQL 8.0.46):

```text
+------------+-------------+--------------------+
| TABLE_NAME | COLUMN_NAME | COLLATION_NAME     |
+------------+-------------+--------------------+
| table_a    | name        | utf8mb4_0900_ai_ci |
| table_b    | name        | utf8mb4_bin        |
+------------+-------------+--------------------+
```

이때 임시로 `COLLATE`를 지정해 오류를 피할 수는 있지만, 운영 관점에서는 스키마의 collation 혼재를 줄이는 것이 우선이다.

```sql
INSERT INTO table_a VALUES (1, 'ABC');
INSERT INTO table_b VALUES (10, 'abc');

SELECT a.id, b.id
FROM table_a AS a
JOIN table_b AS b
  ON a.name COLLATE utf8mb4_0900_ai_ci = b.name COLLATE utf8mb4_0900_ai_ci;

DROP TABLE table_a;
DROP TABLE table_b;
```

실행 결과(MySQL 8.0.46):

```text
+----+----+
| id | id |
+----+----+
|  1 | 10 |
+----+----+
```

단, 컬럼에 함수를 적용하거나 비교 과정에서 변환이 필요해지면 인덱스 사용성이 떨어질 수 있다. 자주 조인되는 컬럼은 같은 문자셋과 같은 collation을 갖도록 설계하는 편이 가장 안정적이다.

## 7. 인덱스 길이와 prefix 설계

`utf8mb4` 컬럼은 문자 하나가 최대 4바이트를 사용할 수 있다. 따라서 문자열 컬럼 인덱스는 실제 글자 수보다 더 큰 바이트 공간을 요구할 수 있다. InnoDB의 인덱스 키 길이 한도는 MySQL 버전, row format, 페이지 크기 등에 영향을 받는다. 현대 MySQL 8.0에서는 과거보다 제약이 완화되었지만, 매우 긴 `VARCHAR` 또는 여러 문자열 컬럼을 조합한 복합 인덱스에서는 여전히 바이트 길이를 고려해야 한다.

예를 들어 다음 인덱스는 데이터 특성에 따라 과도할 수 있다.

```sql
DROP TABLE IF EXISTS user_event;

CREATE TABLE user_event (
    tenant_id BIGINT NOT NULL,
    external_key VARCHAR(255) NOT NULL,
    event_type VARCHAR(50) NOT NULL,
    created_at DATETIME NOT NULL,
    KEY ix_lookup (tenant_id, external_key, event_type, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO user_event VALUES
(1, 'external-key-001', 'login', '2026-05-19 10:00:00'),
(1, 'external-key-002', 'click', '2026-05-19 10:05:00');
```

`external_key`가 실제로는 80자 이내라면 컬럼 길이를 줄이는 것이 더 낫다. 길이를 줄일 수 없다면 prefix index를 검토할 수 있다.

```sql
CREATE INDEX ix_external_key_prefix
ON user_event (tenant_id, external_key(100), created_at);
```

하지만 prefix index는 전체 값을 완전히 식별하지 못한다. `external_key(100)`까지만 인덱스에 들어가므로, 같은 prefix를 가진 행이 많으면 인덱스 선택성이 떨어지고 추가 필터링이 늘어난다. 유니크 보장에도 주의해야 한다. prefix 유니크 인덱스는 prefix 기준으로만 중복을 판단하므로 실제 전체 문자열이 달라도 같은 prefix라면 충돌할 수 있다.

문자열 인덱스를 설계할 때는 다음을 함께 확인한다.

```sql
-- 컬럼 길이와 실제 데이터 길이 분포 확인
SELECT
    MAX(CHAR_LENGTH(external_key)) AS max_chars,
    MAX(OCTET_LENGTH(external_key)) AS max_bytes,
    AVG(CHAR_LENGTH(external_key)) AS avg_chars,
    AVG(OCTET_LENGTH(external_key)) AS avg_bytes
FROM user_event;

-- prefix 선택성 대략 비교
SELECT
    COUNT(*) AS total_rows,
    COUNT(DISTINCT external_key) AS distinct_full,
    COUNT(DISTINCT LEFT(external_key, 50)) AS distinct_prefix_50,
    COUNT(DISTINCT LEFT(external_key, 100)) AS distinct_prefix_100
FROM user_event;
```

실행 결과(MySQL 8.0.46):

```text
+-----------+-----------+-----------+-----------+
| max_chars | max_bytes | avg_chars | avg_bytes |
+-----------+-----------+-----------+-----------+
|        16 |        16 |   16.0000 |   16.0000 |
+-----------+-----------+-----------+-----------+
+------------+---------------+--------------------+---------------------+
| total_rows | distinct_full | distinct_prefix_50 | distinct_prefix_100 |
+------------+---------------+--------------------+---------------------+
|          2 |             2 |                  2 |                   2 |
+------------+---------------+--------------------+---------------------+
```

`CHAR_LENGTH()`는 문자 수를, `OCTET_LENGTH()`는 바이트 수를 반환한다. 문자셋 변경이나 인덱스 크기 평가에서는 두 값을 구분해야 한다.

## 8. `LIKE`, 범위 조건, 정렬 비용

문자셋과 collation은 문자열 조건의 비용에도 영향을 준다. `LIKE 'abc%'`는 일반적으로 인덱스 range scan 후보가 될 수 있지만, `LIKE '%abc%'`는 선행 와일드카드 때문에 일반 B-tree 인덱스를 효율적으로 사용하기 어렵다. 여기에 collation의 대소문자/악센트 무시 규칙이 더해지면 비교 연산의 의미가 더 복잡해진다.

```sql
DROP TABLE IF EXISTS article;

CREATE TABLE article (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    title VARCHAR(200) NOT NULL,
    KEY ix_title (title)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

INSERT INTO article (title) VALUES ('mysql collation'), ('mysql index'), ('innodb lock');

EXPLAIN FORMAT=TREE
SELECT id, title
FROM article
WHERE title LIKE 'mysql%'
ORDER BY title
LIMIT 20;
```

`ORDER BY`가 같은 collation의 인덱스 순서를 그대로 사용할 수 있으면 정렬 비용이 줄어든다. 반대로 표현식, 다른 collation 변환, 함수 적용, 불일치한 정렬 방향이 있으면 `filesort` 또는 임시 테이블이 필요할 수 있다.

```sql
-- 컬럼에 COLLATE 변환을 강제로 적용하면 인덱스 정렬 활용이 제한될 수 있다.
EXPLAIN
SELECT id, title
FROM article
ORDER BY title COLLATE utf8mb4_bin
LIMIT 20;
```

실행 결과(MySQL 8.0.46):

```text
+----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------------+
| id | select_type | table   | partitions | type  | possible_keys | key      | key_len | ref  | rows | filtered | Extra                       |
+----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------------+
|  1 | SIMPLE      | article | NULL       | index | NULL          | ix_title | 802     | NULL |    3 |   100.00 | Using index; Using filesort |
+----+-------------+---------+------------+-------+---------------+----------+---------+------+------+----------+-----------------------------+
```

운영 중 정렬 쿼리가 느릴 때는 단순히 `sort_buffer_size`를 키우기 전에 다음을 확인해야 한다.

- `ORDER BY` 컬럼의 문자셋과 collation이 쿼리에서 요구하는 정렬과 일치하는가?
- 정렬에 사용할 수 있는 적절한 인덱스가 있는가?
- `COLLATE`, `LOWER()`, `CONVERT()` 같은 표현식이 인덱스 사용을 방해하고 있지 않은가?
- 정렬 대상 문자열 컬럼이 지나치게 길어 임시 테이블과 메모리 사용량을 키우고 있지 않은가?

## 9. 연결 문자셋과 애플리케이션 드라이버

컬럼이 `utf8mb4`여도 클라이언트 연결이 잘못 설정되어 있으면 데이터가 깨지거나 저장 오류가 발생할 수 있다. MySQL은 클라이언트가 보내는 문자열을 `character_set_client`, `character_set_connection`, `character_set_results` 설정에 따라 해석한다.

현재 세션의 연결 문자셋은 다음으로 확인한다.

```sql
SHOW VARIABLES LIKE 'character_set_client';
SHOW VARIABLES LIKE 'character_set_connection';
SHOW VARIABLES LIKE 'character_set_results';
SHOW VARIABLES LIKE 'collation_connection';
```

실행 결과(MySQL 8.0.46):

```text
+----------------------+--------+
| Variable_name        | Value  |
+----------------------+--------+
| character_set_client | latin1 |
+----------------------+--------+
+--------------------------+--------+
| Variable_name            | Value  |
+--------------------------+--------+
| character_set_connection | latin1 |
+--------------------------+--------+
+-----------------------+--------+
| Variable_name         | Value  |
+-----------------------+--------+
| character_set_results | latin1 |
+-----------------------+--------+
+----------------------+-------------------+
| Variable_name        | Value             |
+----------------------+-------------------+
| collation_connection | latin1_swedish_ci |
+----------------------+-------------------+
```

애플리케이션 드라이버에서는 DSN 또는 연결 옵션에서 `utf8mb4`를 명시하는 것이 좋다. 예를 들어 JDBC에서는 다음과 같은 형태를 검토한다.

```text
jdbc:mysql://db.example.invalid:3306/appdb?useUnicode=true&characterEncoding=utf8&connectionCollation=utf8mb4_0900_ai_ci
```

드라이버와 MySQL 버전에 따라 권장 옵션은 달라질 수 있으므로, 실제 운영에서는 사용하는 드라이버 문서와 현재 MySQL 버전을 기준으로 확인해야 한다. 중요한 원칙은 서버와 컬럼만 보지 말고 “클라이언트가 어떤 인코딩으로 문자열을 보내고 받는가”까지 점검해야 한다는 점이다.

## 10. 기존 스키마를 `utf8mb4`로 전환할 때의 절차

운영 중인 테이블의 문자셋과 collation을 변경하는 작업은 단순 DDL이 아니다. 테이블 재작성, 인덱스 재구성, 메타데이터 락, 복제 지연, 애플리케이션 호환성 문제가 함께 발생할 수 있다. 특히 대형 테이블에서는 `ALTER TABLE ... CONVERT TO CHARACTER SET`가 긴 시간 동안 실행될 수 있고, MySQL 버전과 DDL 알고리즘에 따라 쓰기 차단 또는 성능 저하가 나타날 수 있다.

기본 문법은 다음과 같다.

```sql
ALTER TABLE article
CONVERT TO CHARACTER SET utf8mb4
COLLATE utf8mb4_0900_ai_ci;

DROP TABLE article;
```

그러나 실제 운영 적용 전에는 다음 순서가 필요하다.

1. 현재 데이터베이스, 테이블, 컬럼의 문자셋과 collation 분포를 수집한다.
2. 애플리케이션 코드가 대소문자 구분 여부에 의존하는지 확인한다.
3. 유니크 인덱스 컬럼에서 collation 변경 후 중복으로 판단될 값이 있는지 사전 검사한다.
4. 인덱스 길이 제한과 prefix index 영향을 확인한다.
5. 스테이징 환경에서 DDL 시간, 락, 복제 지연을 측정한다.
6. 대형 테이블은 온라인 스키마 변경 도구 또는 gh-ost, pt-online-schema-change 같은 접근을 검토한다.
7. 백업과 롤백 계획을 준비한다.
8. 변경 후 검색, 정렬, 로그인, 외부 ID 매칭, API 응답을 검증한다.

collation 변경 후 유니크 충돌 가능성을 점검하는 간단한 예시는 다음과 같다.

```sql
DROP TABLE IF EXISTS app_user;

CREATE TABLE app_user (
    user_code VARCHAR(50) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

INSERT INTO app_user VALUES ('ABC'), ('abc'), ('user-001');

-- 대소문자 무시 collation으로 바꿀 때 충돌 후보를 찾는 예시
SELECT LOWER(user_code) AS normalized_code, COUNT(*) AS cnt
FROM app_user
GROUP BY LOWER(user_code)
HAVING COUNT(*) > 1
ORDER BY cnt DESC;

DROP TABLE app_user;
```

실행 결과(MySQL 8.0.46):

```text
+-----------------+-----+
| normalized_code | cnt |
+-----------------+-----+
| abc             |   2 |
+-----------------+-----+
```

이 쿼리는 완전한 collation 동일성 검사는 아니지만, 대소문자 구분 차이로 인한 대표적인 위험을 찾는 데 도움이 된다. 악센트, 언어별 동등성까지 고려해야 하는 경우에는 대상 collation을 명시한 비교나 임시 테이블 검증을 수행하는 편이 안전하다.

## 11. Aurora MySQL에서의 고려 사항

Aurora MySQL도 MySQL 호환 엔진이므로 문자셋과 collation의 기본 원리는 동일하다. 다만 운영 방식에서는 몇 가지 차이를 고려해야 한다.

첫째, 파라미터 그룹에서 서버 기본 문자셋과 collation 관련 변수를 관리한다. 신규 클러스터 또는 신규 데이터베이스 생성 시 기본값을 통일하려면 DB cluster parameter group과 DB parameter group 설정을 함께 확인해야 한다.

둘째, Aurora의 분산 스토리지 구조는 일반적인 자체 운영 MySQL과 다르지만, `ALTER TABLE ... CONVERT TO CHARACTER SET`가 스키마 변경과 인덱스 재구성 비용을 유발한다는 사실은 변하지 않는다. 대형 테이블 변경은 writer 인스턴스의 부하, replica lag, 애플리케이션 타임아웃을 유발할 수 있으므로 유지보수 창과 모니터링이 필요하다.

셋째, Aurora MySQL 버전에 따라 지원되는 collation 집합이 다를 수 있다. MySQL 5.7 호환 계열과 MySQL 8.0 호환 계열은 기본 collation과 사용 가능한 Unicode collation이 다르다. 마이그레이션 전에는 반드시 대상 클러스터에서 다음을 확인한다.

```sql
SHOW COLLATION WHERE Charset = 'utf8mb4';
SELECT VERSION();
```

넷째, Aurora에서 장애 조치가 발생하더라도 문자셋과 collation 설정은 데이터와 파라미터 그룹에 의해 유지된다. 그러나 애플리케이션 연결 문자열, 드라이버 옵션, 커넥션 풀 초기화 SQL이 인스턴스 교체 후에도 동일하게 적용되는지는 별도로 확인해야 한다.

## 12. 장애 모드와 흔한 오해

### 12.1 “문자셋만 맞으면 collation은 중요하지 않다”는 오해

문자셋이 저장 가능 범위를 정한다면 collation은 비교 의미를 정한다. 유니크 인덱스, 조인, 검색, 정렬은 모두 비교 의미에 의존한다. 특히 계정명, 코드, 토큰, 외부 식별자처럼 정확한 일치가 중요한 컬럼에서는 collation 정책이 데이터 무결성 정책이다.

### 12.2 “모든 컬럼을 `utf8mb4_bin`으로 하면 안전하다”는 오해

`utf8mb4_bin`은 엄격한 비교가 필요할 때 유용하지만, 사용자 검색과 자연어 정렬에는 불편할 수 있다. 사용자가 `kim`과 `Kim`을 같은 이름으로 검색하길 기대하는 화면에서 바이너리 비교만 적용하면 검색 품질이 나빠진다. 컬럼의 의미에 따라 collation을 선택해야 한다.

### 12.3 “DDL 한 번이면 무중단 전환된다”는 오해

문자셋 전환은 데이터 재해석과 인덱스 재구성을 동반할 수 있다. 테이블 크기, MySQL 버전, 온라인 DDL 지원 범위, 외래 키, 트리거, 복제 구조에 따라 영향이 달라진다. 특히 업무 피크 시간대에 대형 테이블을 변환하면 락 대기와 복제 지연이 장애로 확대될 수 있다.

### 12.4 “애플리케이션에서 이미 UTF-8을 쓰므로 DB는 상관없다”는 오해

애플리케이션 문자열이 UTF-8이어도 MySQL 연결 문자셋, 컬럼 문자셋, 결과 문자셋이 불일치하면 저장과 조회 과정에서 변환 문제가 발생한다. DB 계층의 문자셋은 애플리케이션 설정과 별개로 검증해야 한다.

## 13. 운영 점검 쿼리 모음

다음 쿼리는 문자셋과 collation 상태를 빠르게 진단할 때 사용할 수 있다.

```sql
-- 서버와 세션 기본값
SHOW VARIABLES WHERE Variable_name IN (
  'character_set_server',
  'collation_server',
  'character_set_database',
  'collation_database',
  'character_set_client',
  'character_set_connection',
  'character_set_results',
  'collation_connection'
);

-- 스키마별 기본값
SELECT
    schema_name,
    default_character_set_name,
    default_collation_name
FROM information_schema.schemata
ORDER BY schema_name;

-- utf8mb4가 아닌 문자열 컬럼 찾기
SELECT
    table_schema,
    table_name,
    column_name,
    data_type,
    character_set_name,
    collation_name
FROM information_schema.columns
WHERE table_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema')
  AND character_set_name IS NOT NULL
  AND character_set_name <> 'utf8mb4'
ORDER BY table_schema, table_name, ordinal_position;

-- 같은 스키마 안에서 collation이 섞인 테이블 찾기
SELECT
    table_schema,
    table_name,
    COUNT(DISTINCT collation_name) AS collation_count,
    GROUP_CONCAT(DISTINCT collation_name ORDER BY collation_name SEPARATOR ', ') AS collations
FROM information_schema.columns
WHERE table_schema NOT IN ('mysql', 'sys', 'performance_schema', 'information_schema')
  AND collation_name IS NOT NULL
GROUP BY table_schema, table_name
HAVING COUNT(DISTINCT collation_name) > 1
ORDER BY table_schema, table_name;
```

실행 결과(MySQL 8.0.46):

```text
+--------------------------+--------------------+
| Variable_name            | Value              |
+--------------------------+--------------------+
| character_set_client     | latin1             |
| character_set_connection | latin1             |
| character_set_database   | utf8mb4            |
| character_set_results    | latin1             |
| character_set_server     | utf8mb4            |
| collation_connection     | latin1_swedish_ci  |
| collation_database       | utf8mb4_0900_ai_ci |
| collation_server         | utf8mb4_0900_ai_ci |
+--------------------------+--------------------+
+--------------------+----------------------------+------------------------+
| SCHEMA_NAME        | DEFAULT_CHARACTER_SET_NAME | DEFAULT_COLLATION_NAME |
+--------------------+----------------------------+------------------------+
| information_schema | utf8mb3                    | utf8mb3_general_ci     |
| mysql              | utf8mb4                    | utf8mb4_0900_ai_ci     |
| mysql_tech_note    | utf8mb4                    | utf8mb4_0900_ai_ci     |
| performance_schema | utf8mb4                    | utf8mb4_0900_ai_ci     |
| sys                | utf8mb4                    | utf8mb4_0
... <truncated 92 chars>
```

쿼리 결과에서 `utf8mb3`, `latin1`, 오래된 `utf8mb4_general_ci`와 새로운 `utf8mb4_0900_ai_ci`가 혼재되어 있다면, 즉시 변경하기보다 우선 어떤 컬럼이 비교·조인·검색·유니크 제약에 사용되는지 분류해야 한다.

## 14. 설계 기준과 체크리스트

문자셋과 collation 정책은 다음 기준으로 정리할 수 있다.

- [ ] 신규 데이터베이스의 기본 문자셋은 특별한 사유가 없으면 `utf8mb4`로 지정한다.
- [ ] MySQL 8.0 신규 스키마에서는 기본 collation으로 `utf8mb4_0900_ai_ci` 계열을 우선 검토한다.
- [ ] 대소문자 구분이 필요한 토큰, 코드, 외부 ID, 해시성 문자열은 `utf8mb4_bin` 또는 별도 정규화 컬럼을 검토한다.
- [ ] 사용자 검색용 이름, 제목, 본문 요약 컬럼은 사용자가 기대하는 대소문자/악센트 민감도를 기준으로 collation을 선택한다.
- [ ] 자주 조인되는 문자열 컬럼은 같은 문자셋과 같은 collation을 사용한다.
- [ ] 유니크 인덱스 컬럼은 collation 변경 후 중복 판정이 달라지는지 사전 검증한다.
- [ ] 긴 `VARCHAR` 문자열 인덱스는 `CHAR_LENGTH`, `OCTET_LENGTH`, prefix 선택성을 함께 확인한다.
- [ ] 애플리케이션 드라이버의 연결 문자셋과 커넥션 풀 초기화 SQL을 점검한다.
- [ ] 기존 테이블 변환은 스테이징에서 DDL 시간, 락, 복제 지연, 롤백 절차를 검증한 뒤 수행한다.
- [ ] Aurora MySQL은 파라미터 그룹, 엔진 버전, 지원 collation, writer 부하를 함께 확인한다.

## 15. 결론

MySQL의 문자셋과 collation은 저장 형식과 비교 의미를 결정하는 기본 설계 요소다. `utf8mb4`는 현대 애플리케이션에서 안전한 기본 선택이지만, collation은 컬럼의 의미와 운영 요구에 맞게 선택해야 한다. 특히 유니크 인덱스, 문자열 조인, 검색, 정렬, 외부 식별자 저장에서는 collation이 데이터 무결성과 성능에 직접 연결된다.

운영자는 서버 기본값만 확인하는 데서 멈추지 말고 데이터베이스, 테이블, 컬럼, 연결 세션까지 계층적으로 점검해야 한다. 다음 글들에서는 문자열 컬럼의 인덱스 설계, 정렬 비용, 검색 최적화를 더 구체적으로 다루면서 문자셋과 collation 정책이 실행 계획과 운영 안정성에 어떻게 이어지는지 살펴볼 수 있다.
