잘못 설계된 데이터 모델로는 효율적으로 SQL을 작성할 수 없다. 이 장에서는 올바른 관계형 모델을 설계하는 기본 내용을 담고 있다. 데이터베이스 설계가 여기 나온 규칙을 위반한다면 잘못된 점이 무엇인지 파악해서 수정해야 한다.
BETTER WAY 1 모든 테이블에 기본키가 있는지 확인하자.
관계형 모델을 따르려면 데이터베이스 시스템이 한 테이블에 있는 특정 로우와 나머지 로우를 구별할 수 있어야 한다.
따라서 모든 테이블에는 컬럼 한 개 이상으로 구성된 기본키(Primary Key)가 있어야 한다. 기본키는 로우마다 유일해야 하며 널(Null) 값을 가질 수 없다.
테이블에 기본키가 없으면 반복적이고 일관성 없는 데이터가 쌓여 쿼리 수행 속도가 느리고, 부정확한 정보를 조회하는 등 모든 종류의 문제를 일으킬 수 있다.
기본키의 좋은 후보
- 유일한 값을 가져야 한다.
- 널 값을 가질 수 없다.
- 안정적인 값 이어야 한다. (값을 갱신할 필요가 없는 컬럼)
- 가능한 한 간단한 형태여야 한다. (문자나 부동소수점보다는 정수형이 낫고, 여러 컬럼보다는 단일 컬럼이 낫다)
이런 목표를 달성하는 일반적인 방법은 의미 없는 숫자 데이터로 자동 생성되는 컬럼을 기본키로 만드는 것이다.
BETTER WAY 2 중복으로 저장된 데이터 항목을 제거하자
데이터를 중복으로 저장하면 일관되지 않은 데이터, 비정상적인 삽입·갱신·삭제 처리, 디스크 공간 낭비 등 많은 문제를 일으킨다. 정규화(Normalization)는 중복 데이터를 저장하면서 일으키는 문제점을 없애려고 정보를 주제별로 분할하는 프로세스를 의미한다.

위의 CustomerSales 테이블처럼 한 테이블에 여러 주제의 데이터가 담겨있다면, 로우가 한 개 삭제될 때 제거하려고 하지 않은 데이터를 잃을 수도 있으며 비정상적으로 데이터를 갱신할 때도 있다. 위의 테이블은 아래와 같이 4개의 테이블로 분할할 수 있다.

데이터베이스 정규화의 목표는 중복 데이터를 제거해 데이터를 처리할 때 사용되는 자원을 최소화하는 것이다. 이처럼 중복 데이터를 제거하면 비정상적인 삽입, 갱신, 삭제를 막을 수 있으며, 일관성 없는 데이터 발생을 최소화할 수 있다.
BETTER WAY 3 반복 그룹을 제거하자

스프레드시트(엑셀)에서는 비슷한 데이터가 반복적으로 그룹을 이루는 것을 흔히 볼 수 있다. 위의 테이블에서는 Predecessor란 단일 속성(Attribute)이 반복 그룹이다. 마찬가지로 1월, 2월, 3월 등 월을 컬럼으로 하는 것도 반복 그룹이다. 반복 그룹이 단일 속성에만 국한되는 것은 아니다.
예를 들어 Quantity1, ItemDescription1, Price1, Quantity2, ItemDescription2, Price2, … QuantityN, ItemDescriptionN, PriceN이라는 컬럼이 있는 테이블이 있다면, 이 역시 반복 그룹 패턴임을 알아챌 수 있어야 한다.
반복 그룹은 쿼리를 만들어 속성별로 묶은 보고서를 생성하기가 어렵다. 만일 나중에 Predecessor 값을 추가하거나 이미 있는 Predecessor를 제거할 일이 발생한다면 컬럼을 추가하거나 제거하는 등 스키마 변경이 필요해진다. 비용 측면에서 컬럼은 비싸고 로우는 싸기 때문에, 로우를 추가하거나 제거하도록 설계하는 것이 훨씬 바람직하다.

데이터베이스 정규화의 목표는 데이터의 반복 그룹을 제거하고 스키마 변경을 최소화하는 것이다. 반복 그룹을 제거하면 인덱싱을 사용해 데이터 중복을 방지할 수 있고 쿼리도 간소화할 수 있다. 또한 테이블 설계가 더 유연해진다.
BETTER WAY 4 컬럼당 하나의 특성만 저장하자
관계는 오직 한 주제만 기술해야하며, 속성(컬럼)은 주제와 관련된 데이터를 포함한다. 또 속성은 다른 관계의 속성을 포함하는 외래키가 될 수 있고, 이 외래키는 다른 관계에 있는 일부 튜플(로우)과 연관성을 제공한다.
단일 컬럼에 특성 값을 두 개 이상 저장하는 것은 좋은 생각이 아니다. 이렇게 하면 검색을 하거나 값을 집계할 때 특성 값을 분리하기가 어렵기 때문이다. 중요한 개별 특성은 자체 컬럼에 넣는 것을 고려해야 한다. 특성들을 개별 컬럼으로 분할하면 개별 데이터 검색이나 그루핑을 수행하기가 쉽다.
BETTER WAY 5 왜 계산 데이터를 저장하면 좋지 않은지 이해하자
계산 필드는 데이터 웨어하우스 시스템에서는 좋을지 몰라도 운영 중인 데이터베이스 성능에는 심각한 영향을 미칠 수 있다. 또한 테이블의 로우가 변경·삽입·삭제될 때마다 값을 재계산해야 하므로 데이터 무결성을 유지하기가 어렵다. 그러한 계산 필드를 현행화하는 가장 원시적인 방법은 계산에 사용되는 컬럼이 있는 테이블에 트리거를 추가하는 것이다.
하지만 트리거는 정확하게 작성하기가 어렵고 비용도 비싸기 때문에, 몇몇 DBMS에서는 테이블을 생성할 때 계산 컬럼을 정의하는 방법을 제공한다. 테이블 정의의 일부로 계산 컬럼을 정의하면 트리거를 작성할 때 종종 필요한 복잡한 코드를 작성하지 않아도 된다. 그러나 계산 컬럼을 추가하는 것 역시 서버에 심각한 부하가 발생하고 응답 시간이 현저히 느려진다.
따라서 시스템에 발생하는 부하와 계산 컬럼으로 얻는 이점을 비교하여 사용하거나, 뷰를 이용해 수행할 계산을 정의하는 방법을 사용하자.
BETTER WAY 6 참조 무결성을 보호하려면 외래키를 정의하자
스키마를 제대로 설계하려면 관련된 부모 테이블의 기본키 값을 포함하도록 테이블에 외래키를 정의하는 것이 좋다. 명시적으로 외래키를 만들면 부모 테이블에 없는 로우를 가리키는 자식 테이블 로우가 없음을 보장할 수 있으므로 관련된 테이블 간에 데이터 무결성을 확인하기 좋다.
일부 데이터베이스 시스템에서는 외래키 제약 조건을 정의하면 자동으로 외래키 컬럼에 인덱스를 만들기 때문에 조인 성능이 향상될 수 있다. 또한 인덱스 없이도 옵티마이저가 해당 컬럼을 특별 취급해서, 더 나은 실행 계획을 세우기도 한다.
BETTER WAY 7 테이블 간 관계를 명확히 하자
관계형 모델에서는 실제 데이터를 입력하기 전에 가능한 모든 컬럼과 데이터 타입을 나열해 데이터를 제대로 정의해야 한다. 컬럼이 비슷한 테이블을 서로 병합해 관계를 간소화하는 것이 정말 타당한지 면밀히 검토한다.
데이터 타입이 일치한다면(또는 묵시적 타입 변환이 가능하다면) 두 테이블에 있는 컬럼 간 조인을 생성할 수 있는데, 이 관계는 해당 컬럼이 같은 도메인에 있을 때만 성립한다. 조인에 사용되는 두 컬럼의 데이터 타입은 동일한 것이 가장 좋다.
BETTER WAY 8 제3정규화로도 부족하다면 더 정규화하자
대부분의 데이터 모델에서 이미 제3정규화를 거친 엔터티는 더 높은 수준의 정규화를 만족할 가능성이 크다. 실제로 오늘날 많은 데이터베이스에서 무수한 참조 테이블이 이미 제5정규화, 심지어 제6정규화까지 도달해 있는데도 사람들은 이를 제3정규화로 칭한다. 따라서 제3정규화를 거친 테이블이지만 더 높은 정규화를 위반하는 경우를 찾아야 한다.
1, 2, 3정규화와 BCNF는 관계의 속성 중 기능적 의존성이 있는 속성과 관련되었다. 기능적 의존성이란 해당 속성이 관계를 이루는 키에 의존함을 의미한다.
제4정규화는 다치 종속(Multivalued Dependency)과 관련이 있다. 두 속성이 서로 독립적이지만 관계에서 동일한 키에 의존하는 경우를 다룬다. 이때 두 속성 간에 가능한 조합을 만든다.
제5정규화에서는 후보키가 모든 조인 의존성을 함축해야 한다. 즉, 개별 요소에 근거해 후보키에 유효한 값이 무엇인지 제약할 수 있어야 한다는 말이다. 이것은 키가 복합키일 때만 발생한다.
제6정규화는 일반적으로 관계를 키가 아닌 속성 하나로만 줄이는 것이다. 따라서 테이블 개수가 급격히 늘어나지만 널 허용 컬럼을 정의할 필요가 없다.
더 높은 정규화 형식을 위반하는지 분석할 때 사용할 수 있는 방법 중 하나는 바로 무손실 분할이다. 테이블에 컬럼이 많으면 일련의 부분 집합으로 분할하고, 부분 집합에 대해 SELECT DISTINCT 문을 수행한 후 LEFT OUTER JOIN을 사용해 다시 조인한 결과가 원래 테이블 결과와 동일한지 확인한다. 데이터 손실이 없다면 원본 테이블은 일부 정규화 형식을 위반한 것이므로 더 면밀히 조사해서 데이터 이상 문제를 일으키는지 파악해야 한다. 특히 복합키를 사용하거나 여러 다대다 관계에 참여하는 테이블일 때는 더욱 그렇다.
BETTER WAY 9 데이터 웨어하우스에는 역정규화를 사용하자
정규화된 데이터는 테이블 간 조인을 의미한다는 문제가 있다. 조인이 많을수록 옵티마이저가 최적의 실행 계획을 찾기가 어려워 읽기 성능은 떨어질 수 있다.
역정규화된 데이터베이스는 읽기 작업이 많을 때도 잘 작동한다. 데이터가 소수의 테이블에 있으므로 필요한 조인 횟수가 적거나 아예 없어서 SELECT 속도가 빠르기 때문이다. 또 데이터 쓰기 작업이 빈번하지 않으므로 너무 많은 인덱스가 쓰기 성능에 심각한 영향을 줄까 걱정하지 않아도 된다. 필요하다면 테이블에 있는 모든 컬럼에 인덱스를 만들어서 조회와 정렬 성능을 크게 높일 수도 있다.
가장 쉬운 역정규화 방법은 테이블에 있는 IDENTITY 필드를 복제해 조인을 없애는 것이다. 또 다른 일반적 접근 방법은 다른 테이블을 가리키는 필드를 추가하는 것이다. 이렇게 하면 성능이 향상될 뿐만 아니라 이력 정보를 관리하는 데도 도움이 된다.
'도서 > SQL 코딩의 기술 (Effective SQL)' 카테고리의 다른 글
| 4장 데이터 필터링과 검색 (0) | 2024.08.12 |
|---|---|
| 3장 데이터 모델 설계를 변경할 수 없는 경우 (0) | 2024.08.08 |
| 2장 인덱스 설계와 프로그램적 처리 (0) | 2024.08.08 |