ORM 컨셉과 RDB에 대해 조금 더 알아보자. 그리고 django model - orm에서 PK, FK, UNIQUE 등의 index처리를 너무 depth있게 살펴보기 보다 전체 그림에서 포인트만 잡아보자!
다대일 관계를 정의하기 위해 django.db.model.ForeignKey를 사용합니다. 여타 다른 Field 타입을 사용하듯이 모델에 클래스 속성을 포함시킴으로서 사용합니다.
class:~django.db.models.ForeignKey 는 위치 기반 인자 (인자의 순서가 있는 방식) 로 해당 모델과 관련있는 클래스를 필요로 합니다 (주: 예를 들어 첫번째 인자로 상대 클래스를 명기 해야하는식으로).
예를 들어 Car 모델은 한개의 Manufacturer (제조사) 을 가지고 있습니다,즉 Manufacturer 는 여러개의 Car 를 만들 수 있지만 하나의 Car 그 자체로는 하나의 Manufacturer 만을 갖습니다!
from django.db import models
class Manufacturer(models.Model):
# ...
pass
class Car(models.Model):
manufacturer = models.ForeignKey(Manufacturer, on_delete=models.CASCADE)
# ...
on_delete=models.CASCADE 를 살펴보면 일반적인 관계형 데이터베이스에서 관계를 맺은 데이터가 delete될 때 어떤 규칙을 따르는지 명시한 것 이다.
from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import models
def get_sentinel_user():
return get_user_model().objects.get_or_create(username='deleted')[0]
class MyModel(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.SET(get_sentinel_user),
)
FK는 related_name, related_query_name 등을 살펴볼 필요가 있다. 해당 내용은 시리즈의 정참조, 역참조, related_names, select_related, prefetch_related 를 참조하길 바란다.
from django.db import models
class Topping(models.Model):
name = models.CharField(max_length=50)
class Pizza(models.Model):
name = models.CharField(max_length=50)
toppings = models.ManyToManyField(Topping)
# 그리고 자기 자신과 여러 관계를 맺을 수 도 있다.
# if I am your friend, then you are my friend.
class Person(models.Model):
friends = models.ManyToManyField("self")
공식 문서에서는 "unique=True를 준 FK와 비슷하지만, 정접근 역접근 모두 같은 하나의 모델을 주는 것" 이라고 설명한다. user <-> profile 관계를 보통 1:1 관계 설정하는 경우가 있다. (물론 FK와 같이 1:N도 충분히 가능하다.)
아래 예시 코드와 같이 User모델을 스스로 1:1로 주어서 슈퍼바이저 관계를 가질 수 있다.
from django.conf import settings
from django.db import models
class MySpecialUser(models.Model):
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
supervisor = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='supervisor_of',
)
각 필드의 값이, 테이블 내에서 "유일한 값"을 가지는 필드로 만든다. 즉 모든 레코드가 서로 다른 값을 가져야만 할 때 사용한다.
모델을 정의할 때 Field(unique=True)
로 유니크 값을 설정할 수 있다. 보통 타임스탬프 값이 유니크값이 될 수 있다. 하지만 충분히 중복 가능성은 있기 때문에, 타임스탬프에 시퀀스 값을 붙이는 경우도 있다.
사실 더 핵심 사용은 "여러개 필드를 묶어서 Unique를 만들기" 다. 일반적으로 SQL을 사용할 때, 복수의 컬럼(키) 값을 묶어 Unique 집합으로 사용하는 것 과 같다. 예를 들면, title과 date를 묶어 유니크 하게 하면 같은 날짜에 같은 title을 가지는 레코드(row)는 존재할 수 없게 할 수 있다.
좋은 예시가 있어서 링크와 사진을 가져왔다! 출처
사실 인덱스는 개념부터 좀 제대로 잡고 살펴보는 것이 좋다. 느린 RDBMS에서 어떻게 서치(SELECT)를 최적화 할까? 에 초점이 맞춰진 개념이다.
indexing 기술은 RDBMS에서 검색 속도를 높이기 위해서 사용되는 기술 이다. Table의 컬럼을 따로 파일로 저장하여서, 검색시 해당 Table의 레코드를 full scan하는 것이 아니라, 저장한 Index 파일을 검색하여서 검색 속도를 빠르게한다.
Array의 index를 알 때, O(1) 의 시간복잡도로 접근할 수 있는 개념과 비슷하다. 인덱스는 DBMS 내부적으로 처리되는 알고리즘을 사용한다. 대표적으로 B-Tree, Hash, Fractal-Tree 등이 있다. [Real MySQL] B-Tree 인덱스 글을 읽어보는 것을 추천한다.
데이터의 종류가 적을 수록 Index의 효율이 떨어진다. 반대로 서로 다른 값으로 여러 종류의 데이터가 존재하면 효율은 좋아진다.
이렇게 index가 db에 제대로 반영 되는지 확인하기 위해서는 EXPLAIN 문을 이용해서 알 수 있다. SELECT 문 앞에 'EXPLAIN'을 붙여주고, possible_keys와 key 컬럼을 통해 알 수 있다.
class Post(models.Model):
title = models.CharField(db_index=True, max_length=100) # title
contents = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# ...생략...
# author로 User를 FK로 가진다고 생각하자
CharField인 title에 옵션 db_index=True를 주어서 인덱싱을 했다.
django 문서 상에서는 filter, exclude, order_by가 자주 사용되는 컬럼에 db_index를 사용하는 것을 추천한다. -> 장고 쿼리 튜닝에 좋은 초점이 된다.
Django 에서는 Queryset에 explain method가 존재하는데, 이것을 통해 index 사용 여부를 알 수있다.
test = Post.objects.filter(user_id=1)
print(test.explain)
#결과 값
'0 0 0 SEARCH TABLE posts_post USING INDEX posts_post_user_id_id_8907527a (user_id_id=?)'
카디널리티 (Cardinality) 높을수록 / 한 컬럼이 갖고 있는 값의 중복 정도가 낮을 수록 좋다.
a. 상대적인 개념이다. 다른 컬럼에 비해 카디널리티가 높고 낮음을 결정
선택도 (Selectivity) 가 낮을 수록 인덱스 설정에 좋은 컬럼이다.
a. 5~10% 정도가 적당
b. 컬럼의 특정 값의 row 수 / 테이블의 총 row 수 100
c. 컬럼의 값들의 평균 row 수 / 테이블의 총 row 수 100
활용도 높을 수록 인덱스 설정에 좋은 컬럼
a. 실제 작업(query에서 얼마나 활용되는지에 대한 값, SELECT 되는 컬럼, WHERE 절에 자주 활용되는 컬럼
중복도
a. 중복도가 없을 수록 인덱스 설정에 좋은 컬럼이다. 카디널리티와 중복도는 상대적이고 절대적인 개념에서 엄연히 다르다.