[AntiPattern 1] 쉼표로 구분된 목록에 저장
ex) column을 varchar로 바꾸고 a_id 를 comma-split 해서 넣는 것.
•
큰 단점
◦
a_id 를 기준으로 작업을 해야 한다면 끔찍한 SQL을 작성하거나 항상 application을 거쳐야 한다.
◦
구분자도 애마하고, 길이 제한도 애매하다.
해결 : 테이블 분리
안티패턴에 대한 반박 : 절대 a_id 를 기준으로 쓸 일이 없을 때 or 애플리케이션에서 쓰기만 하는 로그성 데이터와 유사할 때
(그냥 생각)
•
1 : N 구조에서 N의 개수 제한이 있을 때 결국 해결책은 분산락.
[AntiPattern 2] 단순히 parent_id만을 가진 Tree 구조의 Table
이러한 구조를 “인접 목록 모델 (adjacency list)”이라고 부른다.
안티패턴에 대한 반박 : 데이터 양이 그리 많지 않은 경우
장점 : 주어진 노드의 부모나 자식을 바로 얻을 수 있고 새로운 노드를 추가하기 쉽다.
몇 가지 대안
1) 경로 열거 - parent_id 대신 path를 갖게 한다. path는 구분자를 갖는 parent_id 들의 문자열이다.
2) 중첩 집합 - nsleft, nsright 값을 추가한다. nsleft는 모든 자식 노드의 nsleft 수보다 작아야 하고, nsright는 모든 자식의 nsright 값보다 커야 한다.
장점 - 특정 node의 모든 자손, 모든 부모를 가져오는 것이 용이하다. / 자식을 가진 노드를 삭제해도, 삭제된 노드의 자식들은 자연스럽게 삭제된 노드 부모의 자손이 된다.
단점 - 바로 상위 부모를 찾는 것이 어렵고, 노드를 추가, 이동하는 트리 조작이 불편하다.
각 노드에 대해 조작하기 보다 서브 트리를 쉽고 빠르게 조회하는 것이 중요할 때 가장 잘 맞는다.
3) 클로저 - 추가적인 테이블을 만들어 (부모id, 자식id)를 모든 쌍에 대해 기록한다.
예를 들어 1번 노드 아래 2번, 3번, 3번 노드 아래 4번이 있다면 (1, 2) / (1, 3) / (1, 4) / (3, 4)가 저장된다.
cross join을 사용하면 새 위치의 조상과 서브트리의 모든 노드를 대응시키기 위해 필요한 행을 만들어 낼 수도 있고, path_length 속성을 새로운 테이블에 만든다면 부모나 자식 노드를 더 쉽게 조회할 수도 있다.
[AntiPattern 3] 무지성 id (PK)
가상키와 자연키의 개념
단점
•
다른 column이 사실상 id일 수 있고, (a_id, b_id)와 같은 unique를 추가로 걸어야 할 수 있다.
•
너무 일반적이라 아무런 의미를 가질 수 없다.
안티패턴에 대한 반박 : ORM을 사용해 id 를 쓰는 것이 편리한 경우 or 지나치게 긴 자연키를 대체하고 싶은 경우
몇 가지 해결
PK의 이름을 table_id 로 해야 하는 것이 아닌가?
[AntiPattern 4] FK를 사용하지 않음.
•
여러 테이블을 변경하기 위해 테이블 잠금을 사용할 필요가 없다.
•
불가피하게 생기는 고아 데이터를 정정하기 위해 품질 제어 스크립트를 주기적으로 돌릴 필요가 없다.
[AntiPattern 5] key-value를 가진 테이블 (EAV : Entity-Attribute-Value)
A class를 B와 C가 상속하고 속성이 다른 경우
create table example (
PK,
key varchar(100) not null,
value varchar(100) not null
);
SQL
복사
이런 구조는 몇 가지 단점이 존재한다.
•
복잡한 속성 조회
•
필수 속성 사용 불가
•
SQL 데이터 타입 사용 불가
•
참조 정합성 강제 불가
•
속성 이름 강제 불가
몇 가지 대안
1.
단일 테이블 상속 (single table inheritance)
2.
구체 테이블 상속 (concrete table inhertiance)
3.
클래스 테이블 상속 (class table inheritance)
•
모든 서브타입에 대한 조회가 많고 공통 column을 참조하는 경우가 많다면 이 설계가 가장 적합
4.
반구조적 데이터 (or 직렬화된 데이터, serialized LOB)
•
장점 : 확장이 쉽다
•
단점 : SQL이 특정 속성에 접근하는 것을 거의 지원하지 못한다.
[AntiPattern 6] 다형성 연관
한 테이블의 FK가 여러 테이블을 가리킬 수 있는 경우 (FK, type)을 기준으로 1) 어떤 테이블인지 2) 어떤 row인지를 가리키게 된다.
•
사용이 합당한 경우 : 성숙도가 높은 프레임워크 (like 하이버네이트) 를 사용할 경우 써야할 때도 있음
•
해결 방법 : 매핑 테이블을 배치한다
# As-Is 부모가 둘 이던 테이블
create table child (
fk bigint # 부모 테이블의 row
type varchar(255) # 부모의 타입
)
# To-Be 각 부모와 매핑하는 테이블을 여러개로 구성한다
create table child (
PK
)
create table child_type1 (
child_id bigint
type1_id bigint
)
create table child_type2 (
child_id bigint
type2_id
)
SQL
복사
•
이렇게 되면 type 마다 매핑 테이블이 생긴다는 단점도 있어 보이기는 한다.. 
[AntiPattern 7] 다중 칼럼 속성
•
tag1 / tag2 / tag3 처럼 column 여러개 하지 말고 그냥 1 : N 해라
◦
값 조회도 어렵고, 유일성 보장도 어려움
◦
column이 부족해질 수도 있음. 그렇다고 column을 늘리기에는 이미 데이터를 포함하고 있는 DB 구조를 변경하기도 쉽지 않음
•
사용이 합당한 경우 : 속성의 개수가 고정되고 위치나 순서가 중요한 경우
[AntiPattern 8] 메타데이터 트리블
•
column, 테이블 등을 지속적으로 추가해야 하는 구조 (ex. 월이 바뀔 때마다 늘어남)
•
사용이 합당한 경우 : 현재 데이터와 오래된 데이터를 함께 조회할 필요가 없는 경우
◦
ex) 3년치만 보관하고 3년이 넘은 데이터는 분리 보관하는 경우
•
시간 단위로 구분할거면 차라리 파티션을 써라
•
수직 분할 : column을 기준으로 테이블을 나눈다. 거의 사용되지 않는 column이 있을 때 유용할 수 있다.
물리적인 안티패턴 or SQL 안티패턴 종류
•
FLOAT 소수점 계산 조심해라
◦
NUMERIC 혹은 DECIMAL을 사용해라
•
check 라는 구문을 통해 문자열 column에 들어오는 값을 검증하려 하지 말고 enum 타입도 쓰지 마라
◦
차라리 enum 성격의 테이블을 별도로 만들던가 애플리케이션 레벨에서 처리해라
•
이미지를 local server에 저장하지 말고 차라리 DB에 blob으로 저장해라
◦
요즘에는 다 cloud 쓰지 않나.. 
•
인덱스 잘 써라
•
NULL을 사용하는 것 자체는 안티패턴이 아니다. NULL을 일반적인 값처럼 사용하거나 일반적인 값을 NULL처럼 사용하는 것이 안티패턴이다.
•
랜덤한 값 구현
◦
ORDER BY rand() limit 1 → 정말 개느리다
◦
애플리케이션에 데이터를 모두 가져와 랜덤하게 고르기 → 몇만건까지는 할만하다
◦
PK 혹은 rand 라는 필드를 만들어 이 필드를 기준으로 고르기 → 완벽하게 균일한 램덤은 아니지만 할만하다
스파게티 SQL, 이런 쿼리를 잘못된 결과를 얻을 수 있을 뿐만 아니라, 작성하기도 어렵고, 수정하기도 어렵고, 디버깅하기도 어렵다. 단순한 쿼리를 사용하는 편이 훨씬 경제적이다.
•
SELECT * FROM : 쿼리에서 와일드카드를 사용하는 편리함은 성능과 확장가능성에 해를 끼칠 수 있다. 쿼리에서 더 많은 column을 선택하면 DB - 애플리케이션 사이의 네트워크를 통해 더 많은 데이터가 전달되어야 한다.
Active Record 패턴 / Data Mapper 패턴
•
Active Record : DB를 조회하는 메소드를 모델 내에 정의하는 패턴
•
Data Mapper : 별도의 클래스에 DB 조회 메소드가 존재하는 패턴