마이그레이션이나 테이블 생성 시 갑자기 나타나는 'Specified key was too long' 에러를 경험해봤을까? 인덱스나 기본키를 설정하려는데 컬럼 길이가 너무 길다며 생성이 거부되는 상황이다. 다만 대부분의 개발자들은 이 제한이 정확히 어디서 비롯되었는지, 왜 필요한지, 어떻게 해결해야 하는지 모른 채 컬럼 길이를 무작정 줄이거나 인덱스를 포기해버린다. 이번에는 MySQL의 키 길이 제한 원인, 각 스토리지 엔진별 차이, 그리고 실전 해결책을 완벽하게 정리해서 소개하겠다.
MySQL 키 길이 제한의 원인
MySQL에서는 인덱스나 기본키의 최대 길이에 제한을 둔다. 이것은 단순한 의도적 제약이 아니라 데이터베이스 아키텍처의 근본적인 설계 때문이다. InnoDB는 최대 767바이트(utf8 기준)까지만 인덱싱할 수 있으며, 이후 버전의 InnoDB 페이지 크기가 16KB인 경우 3072바이트까지 늘어났다. 만약 이 제한이 없다면 인덱스 트리 구조가 과도하게 커져서 쿼리 성능이 급락하고 메모리 낭비가 심해진다.
문제는 문자 인코딩이다. UTF-8에서 한글은 3바이트, 영문은 1바이트를 차지한다. 따라서 VARCHAR(255)라고 정의해도 실제 바이트 수는 문자 인코딩에 따라 달라진다. 예를 들어 VARCHAR(100)을 utf8로 정의하면 300바이트가 되는데, 이를 복합 인덱스의 일부로 쓰면 금세 제한을 초과한다.
스토리지 엔진별 제한 차이
✓ InnoDB(MySQL 5.7 이전): 767바이트 제한✓ InnoDB(MySQL 5.7+, innodb_large_prefix=ON): 3072바이트 제한
✓ MyISAM: 1000바이트 제한
✓ Memory: 3072바이트 제한 MySQL 5.7부터는 innodb_large_prefix가 기본값으로 활성화되어 3072바이트까지 허용하지만, 여전히 VARCHAR 컬럼의 바이트 수를 정확히 계산해야 한다.
에러 상황과 원인 진단
ERROR 1071이 발생하는 실제 상황을 보자.
✗ 잘못된 코드 (에러 발생)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
username VARCHAR(255) NOT NULL,
profile_url VARCHAR(500) NOT NULL,
UNIQUE KEY idx_combined (email, username, profile_url)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- ERROR 1071 (42000): Specified key was too long; max key length is 3072 bytes
왜 에러가 났을까? 바이트 수를 계산해보자.
- VARCHAR(255) with utf8mb4: 255 × 4 = 1020바이트
- email(1020) + username(1020) + profile_url(2000) = 4040바이트
3072바이트 제한을 968바이트나 초과했다. utf8mb4는 이모지 같은 4바이트 문자까지 지원하기 때문에 UTF-8(3바이트)보다 가중치가 높다.
✓ 올바른 해결책 1: 컬럼 길이 줄이기
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) NOT NULL,
username VARCHAR(50) NOT NULL,
profile_url VARCHAR(200) NOT NULL,
UNIQUE KEY idx_combined (email, username, profile_url)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 바이트 계산: 100×4 + 50×4 + 200×4 = 1400바이트 (허용됨)
Email 길이를 100자, username을 50자로 줄이고 profile_url을 200자로 제한했다. 실제 비즈니스 로직상 필요한 최소 길이를 정하고, 그에 맞춰 인덱스를 설계해야 한다.
✓ 올바른 해결책 2: 선택적 인덱싱 (prefix index)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
username VARCHAR(255) NOT NULL,
profile_url VARCHAR(500) NOT NULL,
UNIQUE KEY idx_email (email(50)),
KEY idx_username (username(30)),
KEY idx_profile (profile_url(50))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 각 인덱스가 prefix 길이만 사용하므로 제한 이내
-- 바이트 계산: 50×4 + 30×4 + 50×4 = 520바이트 (허용됨)
Prefix index를 사용하면 컬럼의 일부만 인덱싱한다. email의 처음 50자만 인덱싱하는 방식인데, 대부분의 검색 쿼리에서도 충분히 효율적이다. 다만 완전히 고유한 값을 보장할 수 없으므로 상황에 따라 판단해야 한다.
✓ 올바른 해결책 3: 캐릭터셋 변경 (극단적 방법)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
username VARCHAR(255) NOT NULL,
profile_url VARCHAR(500) NOT NULL,
UNIQUE KEY idx_combined (email, username, profile_url)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
-- 같은 utf8mb4이므로 이것도 문제 해결 안 됨
-- 진짜 극단적 방법: latin1로 변경 (권장 안 함)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(255) NOT NULL,
username VARCHAR(255) NOT NULL,
profile_url VARCHAR(500) NOT NULL,
UNIQUE KEY idx_combined (email, username, profile_url)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- 바이트 계산: 255 + 255 + 500 = 1010바이트 (허용됨)
-- 그러나 한글이나 이모지 저장 불가능
Character set을 변경하면 바이트 가중치가 달라진다. latin1은 모든 문자가 1바이트이므로 제약을 쉽게 통과하지만, 다국어 데이터를 저장할 수 없다. 현대의 웹 서비스에선 거의 사용하지 않는다.
실전 PHP 코드 예제
마이그레이션이나 데이터베이스 초기화 시 이 제한을 미리 고려한 스크립트를 작성하자.
✗ 잘못된 예제
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// 길이 제한을 고려하지 않은 테이블 생성
$sql = "CREATE TABLE IF NOT EXISTS articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) NOT NULL,
content LONGTEXT,
author_email VARCHAR(255) NOT NULL,
UNIQUE KEY idx_slug_author (slug, author_email),
KEY idx_title (title)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
$pdo->exec($sql);
// ERROR 1071: slug(500×4=2000) + author_email(255×4=1020) = 3020바이트 초과
?>
✓ 올바른 예제
<?php
$pdo = new PDO('mysql:host=localhost;dbname=test', 'root', '');
// MySQL 5.7+, InnoDB 기준 최대 3072바이트 제한 고려
// Prefix index를 적극 활용
$sql = "CREATE TABLE IF NOT EXISTS articles (
id INT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(500) NOT NULL,
slug VARCHAR(500) NOT NULL,
content LONGTEXT,
author_email VARCHAR(255) NOT NULL,
UNIQUE KEY idx_slug_author (slug(200), author_email(100)),
KEY idx_title_prefix (title(100))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
// 바이트 검증: 200×4 + 100×4 = 1200바이트 (안전)
try {
$pdo->exec($sql);
echo "테이블 생성 성공";
} catch (PDOException $e) {
echo "에러: " . $e->getMessage();
}
// 인덱스 바이트 크기 확인
$checkSql = "SELECT INDEX_NAME, SEQ_IN_INDEX, COLUMN_NAME
FROM INFORMATION_SCHEMA.STATISTICS
WHERE TABLE_NAME = 'articles' AND TABLE_SCHEMA = 'test'";
$result = $pdo->query($checkSql);
foreach ($result as $row) {
echo $row['INDEX_NAME'] . " - " . $row['COLUMN_NAME'] . "\n";
}
?>
실제 코드에서는 prefix index를 명시적으로 지정하고, 나중에 INFORMATION_SCHEMA를 조회해 인덱스가 제대로 생성되었는지 검증한다.
기존 테이블 수정하기
이미 생성된 테이블에서 에러가 발생했다면, ALTER TABLE로 수정해야 한다.
-- 문제가 된 인덱스 삭제
ALTER TABLE users DROP INDEX idx_combined;
-- 새로운 인덱스 추가 (prefix 사용)
ALTER TABLE users ADD UNIQUE KEY idx_combined (email(100), username(100), profile_url(100));
-- 결과 확인
SHOW CREATE TABLE users;
Prefix를 지정하면 컬럼의 처음 N개 문자만 인덱싱한다. 완전한 고유성이 필요하다면 애플리케이션 레벨에서 검증을 추가해야 한다.
흔한 실수와 주의사항
✗ 실수 1: 바이트 계산 무시하고 VARCHAR(255) 남발
많은 개발자가 "컬럼 길이"와 "바이트 길이"를 혼동한다. VARCHAR(255)는 255글자이지만, utf8mb4에선 1020바이트다. 특히 복합 인덱스에서는 모든 컬럼의 바이트를 더해야 한다.
✗ 실수 2: 테스트 환경(latin1)과 프로덕션(utf8mb4)의 불일치
로컬에선 latin1로 테스트했으니 문제가 없다가, 프로덕션 배포 시 utf8mb4로 변경하면서 갑자기 에러가 난다. 처음부터 실제 환경과 같은 문자셋을 사용해야 한다.
✓ 해결책: 개발 단계부터 utf8mb4 사용하고, CREATE TABLE 스크립트에 바이트 계산을 주석으로 남기기
-- email(100×4=400) + phone(20×4=80) + status(10×4=40) = 520바이트
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT NULL,
status VARCHAR(10) NOT NULL,
KEY idx_contact (email(80), phone(20))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
✗ 실수 3: Prefix index의 한계 간과
Prefix index는 편하지만, LIKE '%query%' 같은 뒷부분 검색에는 효과가 떨어진다. 정확한 필드 요구사항을 파악하고 prefix 길이를 결정해야 한다.
정리
MySQL ERROR 1071은 문자 인코딩과 인덱스 설계의 부조화에서 비롯된다. utf8mb4의 4바이트 문자 가중치를 이해하고, 복합 인덱스의 전체 바이트를 미리 계산하는 습관이 중요하다. 단순히 컬럼 길이를 줄이는 것뿐만 아니라 prefix index, 스토리지 엔진 선택, 문자셋 검토 등 여러 해결책을 상황에 맞게 적용해야 한다. 이번 글의 바이트 계산법과 prefix index 예제를 참고해 설계 단계부터 테이블을 구성하면, 런타임 에러 없이 깔끔한 데이터베이스를 유지할 수 있을 것이다.