Search

SQL AntiPatterns

[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 조회 메소드가 존재하는 패턴