테이블을 만들 때마다 "이 컬럼은 INT로 할까 BIGINT로 할까", "VARCHAR 길이는 얼마로 잡지" 하고 매번 문서를 뒤지게 됩니다. 자주 쓰는 MySQL 자료형의 크기와 범위를 한곳에 모아 두고, 고를 때 제가 보는 기준을 같이 적었습니다.

타입을 굳이 작게 고르는 이유는 단순합니다. 행 하나의 크기가 줄면 한 페이지에 더 많은 행이 들어가고, 인덱스도 작아져서 메모리에 더 많이 올라갑니다. 행이 몇 천 개일 때는 차이를 못 느끼지만, 로그 테이블처럼 계속 쌓이는 테이블은 컬럼 하나의 바이트 수가 곧 디스크와 조회 속도로 이어집니다. 그렇다고 너무 빡빡하게 잡으면 나중에 범위를 넘겨 ALTER TABLE을 해야 하니, "데이터가 앞으로 어디까지 갈 수 있나"를 먼저 생각하고 그 안에서 가장 작은 걸 고르는 게 요령입니다.

광고

정수형

자료형 크기 SIGNED 범위 UNSIGNED 범위
TINYINT 1바이트 -128 ~ 127 0 ~ 255
SMALLINT 2바이트 -32768 ~ 32767 0 ~ 65535
MEDIUMINT 3바이트 -8388608 ~ 8388607 0 ~ 16777215
INT 4바이트 -2147483648 ~ 2147483647 0 ~ 4294967295
BIGINT 8바이트 -9223372036854775808 ~ 9223372036854775807 0 ~ 18446744073709551615

정의할 때는 INT[(M)] [UNSIGNED] [ZEROFILL] 형태로 씁니다. 괄호 안의 M은 저장 크기와는 관계없는 "표시 폭"이라서, INT(11)이나 INT(3)이나 4바이트에 같은 범위입니다. 이걸 글자 수 제한으로 오해하는 경우가 많아요. ZEROFILL을 줬을 때 앞자리를 0으로 채우는 폭으로만 쓰입니다.

자동 증가 키처럼 음수가 나올 일이 없는 컬럼은 UNSIGNED를 붙이면 같은 크기로 양수 범위가 두 배가 됩니다. 상태값이나 구분 코드처럼 몇 가지 값만 들어가는 컬럼은 TINYINT로 충분합니다.

MySQL은 정수 연산을 내부적으로 부호 있는 BIGINT 기준으로 처리합니다. 큰 값끼리 곱하거나 더해서 그 범위를 넘으면 결과가 틀어지거나 오류가 나니, 금액 합계처럼 커질 수 있는 계산은 미리 범위를 따져 봐야 합니다.

실수형

자료형 범위
FLOAT[(M,D)] -3.402823466E+38 ~ -1.175494351E-38, 0, 1.175494351E-38 ~ 3.402823466E+38
DOUBLE[(M,D)] -1.7976931348623157E+308 ~ -2.2250738585072014E-308, 0, 2.2250738585072014E-308 ~ 1.7976931348623157E+308
REAL[(M,D)] DOUBLE과 같음

FLOAT·DOUBLE에 UNSIGNED를 붙이는 건 문법상 허용되지만 음수만 막을 뿐 범위가 늘어나지 않고, MySQL 8.0.17부터는 사용 중단 예정으로 표시됩니다. 그리고 이 둘은 근사값이라 0.1 + 0.2 같은 계산에서 미세한 오차가 생깁니다. 돈이나 수량처럼 정확해야 하는 값은 실수형 말고 DECIMAL을 쓰세요. 금액 컬럼을 DOUBLE로 만들어 놨다가 합계가 1원씩 안 맞는 일이 생기는 게 대표적인 실수입니다.

날짜와 시간

자료형 범위
DATE '1000-01-01' ~ '9999-12-31'
DATETIME '1000-01-01 00:00:00' ~ '9999-12-31 23:59:59'
TIMESTAMP '1970-01-01 00:00:01' UTC ~ '2038-01-19 03:14:07' UTC

DATETIME과 TIMESTAMP는 둘 다 날짜와 시각을 담지만 성격이 다릅니다. TIMESTAMP는 저장할 때 UTC로 바꿨다가 읽을 때 세션 시간대로 되돌려 주고, DATETIME은 넣은 값 그대로 보관합니다. 그리고 TIMESTAMP는 2038년에서 끝나니 생년월일이나 만료일처럼 먼 미래·과거가 들어갈 수 있는 컬럼에는 맞지 않습니다. 등록일·수정일 같은 기록용이라면 어느 쪽이든 괜찮지만, 저는 시간대 변환 때문에 헷갈리지 않도록 보통 DATETIME을 씁니다.

문자열

자료형 최대 길이 비고
CHAR(M) 1 ~ 255 글자 고정 길이, 짧은 값은 오른쪽을 공백으로 채워 저장
VARCHAR(M) 최대 65535 바이트(행 전체 기준) 가변 길이, 길이 정보 1~2바이트 추가
TINYTEXT 255 바이트
TEXT 65535 바이트(약 64KB) utf8(3바이트) 기준 약 21844자
MEDIUMTEXT 16777215 바이트(약 16MB)
LONGTEXT 4294967295 바이트(약 4GB)

CHAR와 VARCHAR의 M은 바이트가 아니라 글자 수입니다. 실제 차지하는 바이트는 문자셋에 따라 달라져서, utf8mb4라면 한 글자에 최대 4바이트를 잡습니다. 그래서 CHAR(10)에 utf8mb4면 최대 40바이트, VARCHAR(10)이면 실제 데이터 바이트에 길이 정보 1바이트가 더 붙습니다.

값의 길이가 늘 같다면(우편번호, 고정 자릿수 코드 등) CHAR가 아주 약간 유리하고, 그 외에는 VARCHAR를 쓰는 게 무난합니다. VARCHAR의 최대치 65535바이트는 컬럼 하나가 아니라 행 전체에서 나눠 쓰는 한도라서, 긴 VARCHAR 컬럼 여러 개를 만들면 "Row size too large" 오류가 납니다. 본문처럼 길이가 들쭉날쭉하고 긴 내용은 TEXT 계열로 빼는 편이 낫습니다.

대소문자 구분은 컬럼의 콜레이션이 정합니다. 기본값인 _ci 콜레이션에서는 'abc'와 'ABC'를 같은 값으로 검색하고, BINARY 속성이나 _bin 콜레이션을 주면 구분합니다. 아이디나 토큰처럼 정확히 비교해야 하는 컬럼이라면 이 부분을 꼭 확인하세요.

ENUM과 SET

자료형 입력 가능한 값 최대 개수
ENUM('value1','value2',...) 목록 중 하나 또는 NULL 65535개
SET('value1','value2',...) 목록 중 0개 이상의 조합 또는 NULL 64개

ENUM은 목록 중 하나만, SET은 여러 개를 동시에 고를 수 있습니다. 저장 공간이 작고 잘못된 값을 DB 단에서 막아 준다는 장점이 있지만, 항목을 추가하려면 테이블 구조를 바꿔야 합니다. 값 종류가 자주 바뀔 것 같으면 별도 코드 테이블을 두고 정수로 참조하는 쪽이 관리하기 편합니다.

참고: MySQL 데이터 자료형 정리 - blog.lael.be