메인 콘텐츠로 건너뛰기

VillageSQL is a drop-in replacement for MySQL with extensions.

All examples on this page work on VillageSQL. Install Now →
기본 키 결정은 되돌리기 어렵습니다. 초기의 잘못된 선택 — 예를 들어 외래 키가 열두 개 테이블에 흩어진 뒤에 INT AUTO_INCREMENT에서 벗어나는 스키마 마이그레이션 — 은 고통스럽습니다. 이 가이드는 세 가지 주요 접근 방식, 각각이 올바른 도구인 경우, 그리고 VillageSQL이 여기에 더하는 것을 다룹니다.

MySQL의 기본값: AUTO_INCREMENT

AUTO_INCREMENT 정수가 기본값인 데는 이유가 있습니다. 작고(4 또는 8바이트), 삽입이 빠르고, 자연스럽게 정렬되며, 디버거나 로그 파일에서 바로 사람이 읽을 수 있습니다.
InnoDB의 클러스터형 인덱스는 행을 기본 키 순서로 저장합니다. 자동 증가 키에서는 모든 새 행이 B-트리의 끝에 들어갑니다. 즉 페이지 분할도, 인덱스 단편화도 없고, 순차 삽입이 빠릅니다. 대부분의 단일 데이터베이스 애플리케이션에서는 이것이 올바른 선택입니다. AUTO_INCREMENT의 문제는 확장하거나 통합할 때 드러납니다:
  • 행 수 노출: 순차 ID는 SELECT COUNT(*)를 실행한 것처럼 API 클라이언트에게 주문이 몇 개 있는지 알려줍니다. URL의 순차 ID는 사용자가 레코드를 열거할 수 있게 합니다.
  • 분산 생성: 여러 쓰기 노드에서 고유 ID를 생성하려면 조정이 필요합니다 — 시퀀스 서버, 지정된 쓰기 노드, 또는 수동 ID 범위.
  • 데이터 병합: 서로 다른 데이터베이스의 두 테이블을 병합하면 ID 충돌이 생깁니다. 번호를 다시 매겨야 합니다.
이 중 아무것도 해당하지 않으면 AUTO_INCREMENT가 아마 올바른 답입니다. 그대로 두세요.

전역적으로 고유한 ID가 필요할 때

표준 해법은 UUID입니다: 어떤 노드에서든 조정 없이 독립적으로 생성할 수 있고 실제로는 충돌하지 않는 128비트 무작위 식별자입니다. MySQL 내장 UUID()는 v1 UUID를 CHAR(36) 문자열로 반환합니다. 그대로 저장할 수 있지만 비용이 있습니다: CHAR(36)은 바이너리 등가물의 16바이트에 비해 36바이트이고, 무작위 UUID(v4)는 새 행이 인덱스 끝에 들어가지 않고 — 아무 데나 들어가서 페이지 분할을 강제하므로 — InnoDB 인덱스 단편화를 일으킵니다. 표준 우회책은 수동 변환을 동반한 BINARY(16)입니다:
UUID_TO_BIN()의 1 플래그는 v1 UUID가 시간순으로 정렬되도록 타임스탬프 비트를 재배열합니다. 이는 특히 v1 UUID의 단편화를 줄이지만, 수동 인코딩/디코딩이 모든 쿼리에 마찰을 더합니다.

VillageSQL에서: 네이티브 UUID 타입과 정렬 가능한 버전

VillageSQL의 vsql_uuid 확장은 네이티브 UUID 열 타입과 UUID v1부터 v7까지의 생성 함수를 추가합니다. 수동 인코딩/디코딩이 없고, UUID v7은 삽입 단편화 문제를 깔끔하게 해결합니다.
UUID 타입은 디스크에 16바이트를 저장하고 표준 36자 문자열 형식으로 자동 표시됩니다. 쿼리에 UUID_TO_BIN이나 BIN_TO_UUID가 없습니다. 결정론적 ID — 같은 입력이 항상 같은 UUID를 만드는 — 가 필요하면 v3 또는 v5를 사용하세요:
UUID 저장 메커니즘과 삽입 단편화 문제에 대한 자세한 내용은 MySQL의 UUID를 보세요.

UUID 버전 참조

추가 함수: UUID_IS_VALID(string)은 NULL이 아닌 문자열이 올바른 형식의 UUID인지에 따라 1 또는 0을 반환하고, 인수 자체가 NULL이면 NULL을 반환합니다. 텍스트를 받고 절대 오류를 일으키지 않으므로, 외부 시스템에서 들어오는 ID에 바로 원하는 것입니다. UUID_VERSION(uuid)은 버전 번호를 반환하며, 이미 UUID로 저장된 ID를 라우팅할 때 쓰입니다. UUID_COMPARE(uuid1, uuid2)는 명시적 사전순 비교를 위해 -1, 0, 또는 1을 반환합니다.

전략 선택하기

새 프로젝트를 시작하는 대부분의 팀에게는: 내부 테이블에는 BIGINT AUTO_INCREMENT, 공개 ID가 있거나 시스템 간에 병합될 수 있는 데이터가 있는 테이블에는 UUID_V7()을 쓰는 네이티브 UUID입니다.

자주 묻는 질문

UUID_V7과 UUID_V4 중 언제 무엇을 사용해야 하나요?

쓰기가 많은 테이블의 기본 키에는 UUID_V7()을 사용하세요. 상위 비트에 타임스탬프를 포함하므로 새 UUID는 항상 기존 것 뒤에 정렬됩니다 — 이것이 InnoDB가 삽입 시 인덱스 페이지를 분할하지 않게 합니다. UUID_V4()는 순서 의미가 없는 무작위 ID가 필요할 때만(예: 일회용 토큰) 사용하세요.

UUID를 DEFAULT 표현식으로 사용할 수 있나요?

MySQL 자체는 UUID(), RAND(), NOW() 같은 비결정론적 함수를 DEFAULT 표현식에 허용합니다. 진짜 제한은 VillageSQL 고유의 것입니다: 비결정론적으로 등록된 확장 함수 — UUID_V7() 같은 — 는 DEFAULT 표현식에서 차단되지만, UUID_V3()/UUID_V5() 같은 결정론적 함수는 허용됩니다. 그러나 그 허용은 여기서 기본 키에 유용하지 않습니다: UUID_V3()/UUID_V5()는 DEFAULT 표현식에서 고정된 인수를 받으므로(DEFAULT는 다른 열을 읽을 수 없음), 모든 행이 동일한 UUID를 받습니다 — 두 번째 INSERT가 기본 키에서 첫 번째와 충돌합니다. 대신 각 INSERT 문에서 UUID_V7()을 생성하세요.

외래 키에도 UUID를 사용해야 하나요?

예, 하지만 저장 비용을 알아 두세요: UUID 외래 키는 참조하는 테이블에서 행당 16바이트로, 정수의 4–8바이트와 비교됩니다. 수백만 행과 여러 UUID 외래 키가 있는 테이블에서는 이것이 쌓입니다. 순수한 내부 참조에는 AUTO_INCREMENT + 정수 외래 키가 더 가볍습니다.

UUID_V3과 UUID_V5의 차이는 무엇인가요?

둘 다 네임스페이스와 이름에서 결정론적 UUID를 생성합니다 — 같은 입력은 항상 같은 출력을 만듭니다. v3은 MD5를, v5는 SHA-1을 사용합니다. SHA-1이 조금 더 강하므로 새 코드에는 v5를 선호하세요. v3을 사용하는 시스템이 생성한 ID와 맞춰야 할 때만 v3을 사용하세요.

문제 해결

함께 보기