데이터베이스 개념 · 관계형 모델 · 이론
관계형 모델 — 표 하나가 사실 하나를 말하게 하라
한 줄 요약
관계형 모델은 데이터를 행과 열의 표로 표현하고, 행을 유일하게 식별하는 키와 표 사이의 참조를 제약으로 강제해 데이터가 스스로 규칙을 지키게 만든다.
왜 이게 필요했나
관계형 모델 이전에는 데이터가 파일 구조나 포인터로 엮여 있었고, 조회 방법이 저장 구조에 묶여 있었다. 저장 방식을 바꾸면 프로그램을 다 고쳐야 했다.
관계형 모델의 결정적 아이디어는 무엇을 원하는지만 적고 어떻게 찾을지는 데이터베이스가 정하게 하자는 것이다. SQL 이 선언형인 이유가 여기 있다. 우리가 WHERE status = 'paid' 라고만 쓰면, 인덱스를 탈지 전체를 훑을지는 옵티마이저가 그때의 통계를 보고 정한다.
어떻게 동작하나
용어부터 정리하면, 표는 릴레이션, 행은 튜플, 열은 속성이다. 여기에 세 종류의 키가 붙는다.
- 기본 키 — 행을 유일하게 식별한다. NULL 일 수 없다.
- 후보 키 — 기본 키가 될 수 있었던 다른 유일 속성. 대리 키를 쓰더라도 자연 키가 있다면 유일 제약으로 반드시 남겨야 한다.
- 외래 키 — 다른 표의 키를 참조한다. 참조 무결성을 데이터베이스가 강제한다.
NULL 은 관계형 모델에서 가장 많이 오해되는 부분이다. NULL 은 "값이 없다"가 아니라 "값을 모른다"에 가깝다. 그래서 NULL = NULL 은 참이 아니라 미지(unknown) 이고, WHERE email = NULL 은 아무 행도 반환하지 않는다. IS NULL 을 써야 하는 이유다. 이 성질은 곳곳에 파급된다. count(*) 는 행을 세지만 count(email) 은 NULL 이 아닌 값만 센다. NOT IN 목록에 NULL 이 하나라도 있으면 결과가 전부 사라진다.
제약을 데이터베이스에 두는 이유도 짚고 넘어갈 만하다. "이 값은 항상 0보다 크다"는 규칙을 애플리케이션에만 두면 세 경로로 깨진다. 운영 중 수동 SQL, 배치 스크립트, 나중에 붙은 다른 서비스다. 제약은 이 셋을 모두 막는다.
현장에서 만나는 모습
타입 선택은 되돌리기 가장 어려운 결정이다. PostgreSQL 에서 컬럼 타입 변경은 대개 테이블 전체 재작성이고 그동안 강한 잠금이 걸린다. 반면 인덱스는 언제든 추가하고 지울 수 있다. 그래서 타입과 키에는 시간을 쓰고 인덱스는 실제 쿼리 패턴을 보고 나중에 정하는 순서가 합리적이다.
자주 틀리는 두 가지만 짚는다. 금액에 부동소수점을 쓰면 합계가 1원씩 어긋난다. 정확한 계산이 필요하면 numeric 을 쓴다. 그리고 시각 컬럼에 아무 생각 없이 timestamp 라고 쓰면 시간대 없는 타입이 된다. 어떤 순간을 가리키는 값에는 timestamptz 를 써야 하며, 두 타입의 저장 크기는 8바이트로 같으므로 공간을 아끼려고 시간대 없는 타입을 고를 이유는 없다.
다음 실습에서 할 것
진짜 PostgreSQL 16 이 뜬 파드에서 이커머스 스키마를 조회한다. 특히 이메일이 NULL 인 고객을 찾는 단계에서 = NULL 이 왜 통하지 않는지 손으로 확인하게 된다.