컬럼 타입 고르기

어느 정수, 어느 문자열, 돈과 시각을 어떻게 담을지 — MySQL·PostgreSQL·Oracle·SQL Server 대응표와 함께.

정수, 그리고 발등을 찍는 하나

정수 타입은 앞으로 담을 가장 큰 수에 대한 약속입니다. 가장 자주 깨지는 약속이 smallint 인데, 32,767 은 정렬 순서나 페이지 수나 바쁜 테이블의 수량이 되기 전까지만 커 보입니다.

타입범위(부호 있음)바이트쓸 곳
smallint−32,768 … 32,7672열거형 코드, 작고 고정된 집합
integer / int±21억4거의 전부. 정렬 순서 포함
bigint±922경8자라는 것의 기본키, 최소 단위로 담는 금액
smallint 로 아끼는 2바이트는 운영 장애 한 번보다 쌉니다.

정렬·표시 순서 컬럼이 늘 희생됩니다. 작은 수만 담을 것처럼 보이는데, 누군가 100 단위로 간격을 두고 재정렬하기 시작하면 1년이면 한계를 넘습니다. integer 로 두고 더 생각하지 마세요.

문자열

PostgreSQL 에서 varchar(n) 과 text 는 길이 검사만 덧붙인 같은 타입이고, 그 검사는 공짜입니다. 길이가 진짜 규칙일 때만 varchar 를 쓰세요 — 국가 코드는 두 글자이고 그렇게 적혀야 합니다. 상품명에는 자연스러운 한계가 없고 거기 붙은 255 는 틀릴 짐작입니다.

MySQL 에서는 선택이 더 무겁습니다. 긴 varchar 의 인덱스에 키 길이 제한이 있고 text 컬럼은 저장 방식이 다릅니다. 거기서는 길이 지정이 문서가 아니라 실제 결정입니다.

필요PostgreSQLMySQLOracleSQL Server
고정 길이 코드char(2)char(2)char(2)char(2)
길이 제한 있는 글varchar(255)varchar(255)varchar2(255)nvarchar(255)
제한 없는 글texttext / longtextclobnvarchar(max)
식별자uuidbinary(16) / char(36)raw(16)uniqueidentifier

돈은 절대 부동소수가 아니다

부동소수는 0.1 을 표현하지 못해서 금액을 더하면 어긋납니다. 테스트에서는 보이지 않다가 월간 보고서의 1원 차이로 나타나고, 누군가 그것을 설명해야 합니다. 맞는 선택은 둘입니다 — 고정소수점, 또는 최소 단위를 세는 정수.

방식PostgreSQLMySQL적합주의
고정소수점numeric(19,4)decimal(19,4)가격, 합계, 세금정수보다 연산이 느림
최소 단위bigintbigint원장, 결제 대행사읽고 쓰는 모든 곳이 자릿수를 합의해야 함
비율numeric(7,4)decimal(7,4)할인율, 수수료율반올림 규칙에 필요한 자릿수를 확보해야 함

자릿수는 통화가 아니라 반올림 규칙에서 정하세요. 12.5% 할인은 numeric(5,2) 로도 담기지만, 입력이 아니라 계산으로 나오는 비율 — 셋으로 나눈 수수료 — 은 자릿수가 모자라면 엉뚱한 단계에서 반올림됩니다.

시각과 시간대

순간에는 두 종류가 있고 서로 다른 타입이 필요합니다. 일어난 사건 — created_at, paid_at, logged_in_at — 은 세계 시간선 위의 한 점이라 시간대를 갖는 타입에 들어가야 합니다. 벽시계로 표현된 의도 — 가게가 9시에 연다, 알림을 현지 8시에 — 는 어디인지 알기 전까지 그 시간선 위의 점이 아니고, 시간대를 붙여 저장하면 조용히 서버의 시간대로 확정됩니다.

PostgreSQLMySQLOracleSQL Server
일어난 사건timestamptztimestamptimestamp with time zonedatetimeoffset
벽시계timestampdatetimetimestampdatetime2
날짜만datedatedatedate
기간interval—interval—
MySQL 의 timestamp 와 datetime 은 이름이 주는 인상과 반대입니다 — timestamp 가 UTC 로 변환하고 datetime 은 하지 않습니다.

기본값은 시간대를 갖는 타입이어야 합니다. 평범한 스키마의 거의 모든 컬럼이 일어난 일을 기록하고, 틀렸을 때의 비용은 다른 나라 사용자에게만 또는 서머타임 경계에서 1년에 두 번만 나타나는 버그입니다.

불리언, 열거형, 그리고 세 번째 상태

값이 둘인 불리언 컬럼은 세 번째가 필요해지기 전까지 괜찮습니다. 댓글의 is_approved 는 참/거짓으로 시작하고, 누군가 "보류" 를 요청하면 is_approved 와 is_pending 이 되는데 둘 다 참일 수 있습니다.

  • 정말로 영원히 예/아니오라면 — is_active, is_deleted — 불리언을 쓰세요.
  • 생애주기의 상태라면 — 초안·공개·보관 — 값이 둘뿐이어도 처음부터 status 컬럼으로 두세요.
  • 상태는 DB 열거형보다 체크 제약이 걸린 문자열로 담으세요. Postgres 열거형에 값을 더하는 것도 마이그레이션이고 체크 제약도 마이그레이션이지만, 뒤쪽이 읽기 좋고 떼어내기도 쉽습니다.
  • 세 가지를 뜻하려고 nullable 불리언을 쓰지 마세요. null 은 "모름" 이고, 그 컬럼을 거르는 모든 질의가 그것을 기억해야 합니다.

자주 묻는 것

기본키에 int 와 bigint 중 무엇을 쓰나요?
자랄 수 있는 것이라면 bigint 입니다. 21억은 충분해 보이지만 이벤트·로그·품목 테이블은 도달합니다. 외래키가 가리키고 있는 운영 테이블의 기본키 타입을 바꾸는 일은 가장 고통스러운 마이그레이션 축에 듭니다.
varchar(255) 인가요 text 인가요?
PostgreSQL 에서는 255 가 진짜 규칙이 아닌 한 text 입니다. 그 숫자는 옛 MySQL 인덱스 제한에서 왔고 Postgres 에서는 아무 뜻이 없습니다. MySQL 에서는 인덱스 키 길이 때문에 진짜 결정입니다.
돈은 어떻게 저장하나요?
반올림 규칙에서 자릿수를 정한 numeric/decimal, 또는 최소 단위를 세는 정수입니다. float 나 double 은 절대 안 됩니다 — 오차가 정산 문제가 되기 전까지 보이지 않습니다.
timestamptz 인가요 timestamp 인가요?
일어난 일에는 timestamptz 입니다. 영업시간처럼 시간대가 순간이 아니라 장소에 속하는 벽시계 의도에는 시간대 없는 timestamp 를 씁니다.
DB 열거형이 체크 제약보다 나은가요?
대개 아닙니다. 둘 다 바꾸려면 마이그레이션이 필요합니다. 문자열 컬럼에 건 체크 제약은 질의 결과에서 바로 읽히고, 모든 방언에서 같게 동작하며, 컬럼을 다시 쓰지 않고 제거할 수 있습니다.
SQL 데이터 타입 비교 — MySQL·PostgreSQL·Oracle·SQL Server