5. 데이터 타입 정의 (공통)
5-1. Boolean
- 컬럼명은 3-1 의
is_/has_규칙을 따른다. - 선언은
BOOLEAN으로 쓴다. 양 엔진에서 같은 DDL 이 통과한다. - MySQL 쪽에는
CHECK (col IN (0, 1))를 함께 걸어 값 도메인을 맞춘다. - 조회는
WHERE is_active = TRUE가 아니라WHERE is_active로 쓴다.
| 항목 | MySQL 8.4 · 9.7 | PostgreSQL 18 |
|---|---|---|
| 타입 실체 | TINYINT(1) 의 동의어 |
표준 SQL boolean, 1바이트 |
| 저장 가능 값 | TINYINT 전 범위 (-128 ~ 127) |
true · false · NULL |
| 리터럴 | TRUE = 1, FALSE = 0 |
true/yes/on/1 등 |
| 출력 | 0 / 1 |
항상 t / f |
MySQL 의 BOOL·BOOLEAN 은 TINYINT(1) 의 동의어다. 0 이 false, 0 이 아닌 값이 true 다. 그런데 TRUE 와 FALSE 는 1 과 0 의 별칭일 뿐이라서 2 = TRUE 도 2 = FALSE 도 false 다. is_active 에 2 가 들어간 행은 WHERE is_active = TRUE 에서 누락되고 WHERE is_active 에서는 잡힌다. 2·7·-1 을 경고 없이 저장할 수 있으므로 값 도메인을 맞추려면 CHECK 가 필요하다. 선언을 BOOLEAN 으로 쓰는 이유는 TINYINT(1) 의 (1) 이 display width 이고 deprecated 되었기 때문이다(부록 A-1).
PostgreSQL 은 표준 SQL 의 boolean 타입을 제공하고, true·false 와 SQL null 로 표현되는 unknown 세 상태를 명시적으로 규정한다. 입력은 true/yes/on/1 과 false/no/off/0, 그리고 이들의 고유 접두사를 대소문자 구분 없이 받는다. 출력은 항상 t 와 f 다.
5-2. 문자열
- 경계가 있는 문자열 →
VARCHAR(n)(양 엔진 공통,n은 실제 최대 길이 기준으로 산정) - 경계가 없는 긴 텍스트 → 엔진별 대용량 타입 (부록 A-2 / B-2)
CHAR(n)은 진짜 고정 자릿수(예: 국가 코드CHAR(2))에만 제한적으로 쓴다.
PostgreSQL 문서는 character(n) 을 쓰지 말라고 직접 권고한다 — 다른 데이터베이스 시스템에서는 character(n) 에 성능 이점이 있지만 PostgreSQL 에는 그런 이점이 없고, 추가 저장 비용 때문에 오히려 세 타입 중 가장 느린 경우가 많으며, 대부분의 상황에서 text 나 character varying 을 써야 한다는 내용이다. 같은 문서가 세 타입 사이에 성능 차이가 없다고도 밝힌다 — 공백을 채우는 타입에서 저장 공간이 늘고 길이 검사에 CPU 사이클이 조금 더 드는 것을 빼면 차이가 없다.
MySQL 의 VARCHAR 최대 길이는 컬럼 하나가 아니라 행 전체에 걸린 65,535바이트 제약에서 나온다. utf8mb3 을 쓰면 최대 21,844자다. PostgreSQL 의 varchar(n) 은 n 이 문자 단위이고 선언 상한이 10,485,760, 저장 가능한 최대 문자열이 약 1GB 다.
5-3. 금액 / 정산 데이터
- 부동소수점(
FLOAT/DOUBLE) 금지. 반드시DECIMAL(= 표준NUMERIC)을 쓴다. 양 엔진 모두 고정소수점 정확 연산을 제공한다. precision·scale은 통화·용도별로 산정한다.(10,2)고정은 금지.
| 용도 | 권장 타입 | 비고 |
|---|---|---|
| 원화(KRW) 금액 | DECIMAL(15, 0) |
원화는 소수부 없음 → scale 0 |
| 다국적 일반 금액 | DECIMAL(19, 4) |
대부분 통화(2~4자리) 수용 |
| 환율·단가 | DECIMAL(19, 6) 등 |
정밀도 요구에 맞춰 |
TIP — precision 산정
DECIMAL(10, 2)는 약 1억 미만까지만 표현한다. 누적 정산액·거래 총액엔 부족할 수 있으니precision을 넉넉히 잡는다. 두 엔진의precision상한은 서로 다를 수 있으므로, 상한에 가까운 값을 쓸 계획이면 대상 버전 문서에서 값을 확인한 뒤 확정한다.
5-4. JOIN 키 / 서러게이트 PK
- JOIN 키는 정수 계열로 하고, 조인되는 컬럼 간 타입을 동일하게 맞춘다.
- 서러게이트 PK 의 기본값은
BIGINT로 한다. PostgreSQLserial의 상한이 2,147,483,647,bigserial이 9,223,372,036,854,775,807 이므로 32비트 정수는 대형 테이블에서 소진 위험이 있다. - 자동 증가 문법은 부록 A-3(MySQL
AUTO_INCREMENT)과 부록 B-3(PostgreSQLGENERATED ... AS IDENTITY)에 있다.
문법보다 먼저 확인할 이식 리스크는 사용자 값을 직접 넣었을 때의 동작과 유일성 보장 여부다.
| 항목 | MySQL AUTO_INCREMENT |
PostgreSQL identity |
|---|---|---|
| 사용자 값 주입 | 항상 허용, 시퀀스가 리셋된다 | ALWAYS 는 거부, BY DEFAULT 는 허용 |
| 유일성 | 인덱스가 필수 | 보장하지 않음 → PK·UNIQUE 필요 |
| 테이블당 개수 | 1개 | 여러 개 허용 (표준은 1개) |
| 이름을 갖는 부가 객체 | 없음 | 시퀀스 (부록 B-6) |
MySQL 에서 AUTO_INCREMENT 컬럼에 다른 값을 넣으면 그 값이 저장되고 시퀀스가 리셋되어 다음 자동 값이 가장 큰 컬럼 값 뒤를 잇는다. 기존 값을 UPDATE 해도 리셋된다. PostgreSQL 의 identity 컬럼은 자동으로 NOT NULL 이 되지만 유일성을 보장하지 않으므로 PRIMARY KEY 나 UNIQUE 로 강제해야 한다. 두 엔진 모두 값에 빈 구간이 생긴다.
UUID 를 PK 로 쓸 때의 이식 전략은 타입 대응이 비대칭이라는 점에서 출발한다.
- PostgreSQL 은 네이티브
uuid타입을 제공한다. RFC 9562 가 정의하는 128비트 값이고, 입력은 대문자·중괄호·하이픈 변형을 관대하게 받지만 출력은 항상 표준형이다. 생성은uuidv4()·gen_random_uuid()(랜덤)와uuidv7()(시간순)을 네이티브로 지원한다. - MySQL 에는 UUID 전용 타입이 없다.
UUID_TO_BIN()·BIN_TO_UUID()로 변환해 쓰며,UUID_TO_BIN()의 반환형은VARBINARY(16)이다. 저장 컬럼을BINARY(16)으로 고정 선언하는 것은 이 팀의 선택이다. swap_flag = 1은 인덱스 지역성을 위해 time-low 와 time-high 부분을 뒤바꾼다. 버전 1 UUID 를 전제하는 옵션이라 다른 방식으로 만든 UUID 에는 이득이 없고, 스왑한 값을 표준 UUID 문자열로 되돌리면 다른 값이 된다. 변환은 같은 플래그로 왕복해야 한다.- 따라서 이식 가능한 조합은 애플리케이션에서 UUIDv7 을 생성해 PostgreSQL 은
uuid, MySQL 은 스왑 없는BINARY(16)에 넣는 것이다. - MySQL 의
UUID()에는 값이 반드시 추측 불가능하거나 예측 불가능한 것은 아니다라는 경고가 붙어 있고 statement-based replication 에 안전하지 않다. 보안 토큰 용도로 쓰지 않는다.
5-5. 음수 금지 컬럼
음수가 나올 수 없는 컬럼은 CHECK (col >= 0) 제약으로 보장한다. MySQL 의 UNSIGNED 는 PostgreSQL 에 대응이 없으므로 이식 대상 컬럼에 쓰지 않는다(부록 A-4).
NOTE — 이 규칙은 MySQL 공식 권고와 충돌한다
MySQL 문서는
AUTO_INCREMENT컬럼에 필요한 최대 시퀀스 값을 담을 수 있는 가장 작은 정수 타입을 쓰고 가능하면UNSIGNED속성을 써서 범위를 넓히라고 권한다. 이 문서는 이식성을 우선해BIGINT+CHECK를 택한다 — MySQL 단독 운영이라면 공식 권고가 맞고, 두 엔진을 함께 타깃한다면UNSIGNED가 있는 DDL 은 PostgreSQL 에서 그대로 실행되지 않는다.한편 MySQL 문서는
DECIMAL·FLOAT·DOUBLE의UNSIGNED에 대해서는 대신 단순한CHECK제약을 쓰는 것을 고려하라고 권하므로, 이 규칙과 같은 방향이다.
5-6. NULL 사용 지양
- 인덱스가 잡히는 컬럼은 되도록
NOT NULL로 설계하고, 선택적 속성은 정규화된 테이블에서 JOIN 으로 푼다. - 사이즈가 작거나 사용처가 적은 데이터에 한해 NULL 을 허용한다.
5-7. 문자셋 / 콜레이션
UTF-8 로 통일한다. MySQL 은 utf8mb4, PostgreSQL 은 UTF8 을 사용한다. 콜레이션은 부록 A-6 을 따른다.
WARNING — 한글 비교가 필요한 컬럼
MySQL 의
utf8mb4_0900_ai_ci는 호환 자모 분해형과 완성형을 같은 값으로 판정한다. Oracle 은 이 동작을 유니코드 콜레이션 알고리즘(UCA) 표준 동작으로 보아 버그가 아니라고 처리했다. 닉네임·계정명처럼 두 표기를 구분해야 하는 컬럼은utf8mb4_0900_as_cs나utf8mb4_bin을 컬럼 단위로 지정한다.
5-8. 시각 컬럼의 타입
1-3 이 이름을 정하고 이 절이 타입을 정한다. MySQL 의 두 타입은 범위와 시간대 처리가 모두 다르다.
| 항목 | TIMESTAMP |
DATETIME |
|---|---|---|
| 범위 시작 | 1970-01-01 00:00:01 UTC | 1000-01-01 |
| 범위 끝 | 2038-01-19 03:14:07 UTC | 9999-12-31 |
| 시간대 변환 | 있음 | 없음 |
| 소수 초 | 마이크로초 6자리 | 마이크로초 6자리 |
MySQL 은 TIMESTAMP 값을 저장할 때 현재 시간대에서 UTC 로, 읽을 때 되돌려 변환한다. DATETIME 같은 다른 타입에서는 이 변환이 일어나지 않는다. 같은 값을 두 타입에 넣고 세션 시간대를 바꿔 읽으면 결과가 달라진다.
- 과거 사건의 절대 시각(
created_at·updated_at·deleted_at)은 시간대 변환이 있는 쪽이 맞는다. - 사용자가 지정한 벽시계 시각이나 2038년을 넘길 수 있는 미래 시각은 변환이 없는 쪽을 쓴다.
- 어느 쪽을 고르든 한 스키마 안에서 같은 역할의 컬럼은 같은 타입으로 맞추고, 세션 시간대를 애플리케이션에서 고정한다.
PostgreSQL 쪽 날짜·시각 타입 선택은 이 문서에서 다루지 않는다.