Django 모델에서 UUID를 기본 키로 사용 (일반 관계 영향)


91

여러 가지 이유로 ^, 일부 Django 모델에서 UUID를 기본 키로 사용하고 싶습니다. 이렇게하면 ContentType을 통해 일반 관계를 사용하는 "contrib.comments", "django-voting"또는 "django-tagging"과 같은 외부 앱을 계속 사용할 수 있습니까?

"django-voting"을 예로 사용하면 투표 모델은 다음과 같습니다.

class Vote(models.Model):
    user         = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id    = models.PositiveIntegerField()
    object       = generic.GenericForeignKey('content_type', 'object_id')
    vote         = models.SmallIntegerField(choices=SCORES)

이 앱은 투표중인 모델의 기본 키가 정수라고 가정하는 것 같습니다.

기본 제공 댓글 앱은 정수가 아닌 PK를 처리 할 수있는 것 같습니다.

class BaseCommentAbstractModel(models.Model):
    content_type   = models.ForeignKey(ContentType,
            verbose_name=_('content type'),
            related_name="content_type_set_for_%(class)s")
    object_pk      = models.TextField(_('object ID'))
    content_object = generic.GenericForeignKey(ct_field="content_type", fk_field="object_pk")

이 "정수 PK 가정"문제가 UUID 사용을 어렵게 만드는 타사 앱의 일반적인 상황입니까? 아니면 내가이 상황을 잘못 읽었 을까요?

Django에서 너무 많은 문제를 일으키지 않고 UUID를 기본 키로 사용하는 방법이 있습니까?


^ 몇 가지 이유 : 개체 수 숨기기, URL "ID 크롤링"방지, 여러 서버를 사용하여 충돌하지 않는 개체 만들기, ...

답변:


57

UUID 기본 키는 일반 관계뿐만 아니라 일반적으로 효율성에도 문제를 일으킬 수 있습니다. 모든 외래 키는 기계어보다 저장하고 결합하는 데 훨씬 더 비쌉니다.

그러나 UUID가 기본 키가 될 필요는 없습니다. 모델에 uuid 필드를 추가하여 보조 키로 만드십시오 unique=True. 암시 적 기본 키를 정상적으로 (시스템 내부) 사용하고 UUID를 외부 식별자로 사용합니다.


16
Joe Holloway, 그럴 필요 없음 : UUID 생성 기능을 필드의 default.
Pi Delport

4
Joe : django_extensions.db.fields.UUIDField를 사용하여 모델에 UUID를 만듭니다. 간단합니다. 다음과 같이 필드를 정의합니다. user_uuid = UUIDField ()
mitchf

3
@MatthewSchinckel : mitchf django_extensions.db.fields.UUIDField가 언급 한대로 사용하면 Django-South 마이그레이션에 문제가 없습니다. 그가 언급 한 필드에는 South 마이그레이션에 대한 지원이 내장되어 있습니다.
Tadeck

127
끔찍한 대답. Postgres에는 64 비트 시스템에서 2 단어 인 네이티브 (128 비트) UUID가 있으므로 네이티브 64 비트 INT보다 "상당히 더 비싸지"않습니다.
postfuturist

8
Piet, btree 인덱스가있는 경우 주어진 쿼리에 대해 얼마나 많은 비교가 수행됩니까? 많지 않습니다. 또한 memcmp 호출이 대부분의 OS에서 정렬되고 최적화 될 것이라고 확신합니다. 질문의 성격에 따라 가능한 (무시할 수있는) 성능 차이 때문에 UUID를 사용 하지 않는 것이 잘못된 최적화 라고 말할 수 있습니다.
포스트 퓨처리스트

222

문서에서 볼 수 있듯이 Django 1.8에는 내장 UUID 필드가 있습니다. UUID와 정수를 사용할 때의 성능 차이는 무시할 수 있습니다.

import uuid
from django.db import models

class MyUUIDModel(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

자세한 내용은 이 답변확인할 수도 있습니다 .


@Keithhackbarth 테이블에 대한 ID를 자동으로 생성 할 때마다 이것을 사용하도록 django를 어떻게 설정합니까?
anon58192932

3
@ anon58192932 "매번"이 정확히 무엇을 의미하는지 명확하지 않습니다. 모든 모델에 UUID를 사용하려면 고유 한 추상 기본 모델을 만들고 django.models.Model 대신 사용하세요.
Назар Топольський

4
성능 차이는 기본 데이터베이스가 UUID 유형을 지원하는 경우에만 무시할 수 있습니다. Django는 여전히 대부분의 DB에 charfield를 사용합니다 (postgresql은 UUID 필드를 지원하는 유일한 문서화 된 db입니다).
NirIzr

왜 이것이 인기있는 대답인지 혼란 스럽습니다 ... 질문은 타사 패키지의 어려움에 대한 질문이었습니다. Django는 기본적으로 UUID를 지원하지만 UUID를 고려하지 않는 패키지가 여전히 많이있는 것 같습니다. 제 경험상 고통입니다.
ambe5960

12

비슷한 상황에 처해 공식 Django 문서 에서 관련 모델 object_idprimary_key 와 동일한 유형일 필요가 없다는 것을 알게되었습니다 . 예를 들어 IntegerFieldCharField ID 모두에 대해 일반 관계를 유효하게하려면 CharField로 설정 object_id하면 됩니다 . 정수는 문자열로 강제 변환 될 수 있으므로 괜찮습니다. 동일은 간다 UUIDField .

예:

class Vote(models.Model):
    user         = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id    = models.CharField(max_length=50) # <<-- This line was modified 
    object       = generic.GenericForeignKey('content_type', 'object_id')
    vote         = models.SmallIntegerField(choices=SCORES)

4

PK로서 UUID의 실제 문제는 숫자가 아닌 식별자와 관련된 디스크 조각화 및 삽입 성능 저하입니다. PK가 클러스터형 인덱스이기 때문에 자동 증가되지 않는 경우 DB 엔진은 ID가 더 낮은 순서의 행을 삽입 할 때 물리적 드라이브를 재지 정해야하며, 이는 UUID에서 항상 발생합니다. DB에 많은 데이터가있는 경우 새 레코드 하나를 삽입하는 데 몇 초 또는 몇 분이 걸릴 수 있습니다. 그리고 디스크는 결국 조각화되어주기적인 디스크 조각 모음이 필요합니다. 이건 정말 나쁘다.

이를 해결하기 위해 최근에 공유 할 가치가 있다고 생각하는 다음 아키텍처를 생각해 냈습니다.

UUID 의사 기본 키

이 방법을 사용하면 UUID의 이점을 기본 키 (고유 인덱스 UUID 사용)로 활용하는 동시에 자동 증가 된 PK를 유지하여 조각화를 해결하고 숫자가 아닌 PK를 갖는 성능 저하 문제를 삽입 할 수 있습니다.

작동 원리 :

  1. pkidDB 모델에서 호출되는 자동 증가 기본 키를 생성합니다 .
  2. id숫자 기본 키 대신 UUID ID로 검색 할 수 있도록 고유 색인 UUID 필드를 추가하십시오 .
  3. to_field='id'외래 키가 숫자 ID 대신 의사 PK를 적절하게 나타낼 수 있도록 ForeignKey를 UUID (사용 )로 지정합니다.

기본적으로 다음을 수행합니다.

먼저 추상 Django 기본 모델을 만듭니다.

class UUIDModel(models.Model):
    pkid = models.BigAutoField(primary_key=True, editable=False)
    id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)

    class Meta:
        abstract = True

모델 대신 기본 모델을 확장해야합니다.

class Site(UUIDModel):
    name = models.CharField(max_length=255)

또한 ForeignKeys id가 자동 증가 pkid필드 대신 UUID 필드를 가리키는 지 확인하십시오 .

class Page(UUIDModel):
    site = models.ForeignKey(Site, to_field='id', on_delete=models.CASCADE)

Django Rest Framework (DRF)를 사용하는 경우 기본 검색 필드를 설정하는 기본 ViewSet 클래스도 만들어야합니다.

class UUIDModelViewSet(viewsets.ModelViewSet):
    lookup_field = 'id' 

그리고 API 뷰에 대한 기본 ModelViewSet 대신 확장하십시오.

class SiteViewSet(UUIDModelViewSet):
    model = Site

class PageViewSet(UUIDModelViewSet):
    model = Page

이 기사의 이유와 방법에 대한 추가 정보 : https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps


0

이것은 다음 단계를 사용하여 사용자 정의 기본 추상 모델을 사용하여 수행 할 수 있습니다.

먼저 프로젝트에 basemodel이라는 폴더를 만들고 다음과 같이 abstractmodelbase.py를 추가하십시오.

from django.db import models
import uuid


class BaseAbstractModel(models.Model):

    """
     This model defines base models that implements common fields like:
     created_at
     updated_at
     is_deleted
    """
    id=models.UUIDField(primary_key=True, ,unique=True,default=uuid.uuid4, editable=False)
    created_at=models.DateTimeField(auto_now_add=True,editable=False)
    updated_at=models.DateTimeField(auto_now=True,editable=False)
    is_deleted=models.BooleanField(default=False)

    def soft_delete(self):
        """soft  delete a model instance"""
        self.is_deleted=True
        self.save()

    class Meta:
        abstract=True
        ordering=['-created_at']

두 번째 : 각 앱의 모든 모델 파일에서 다음을 수행하십시오.

from django.db import models
from basemodel import BaseAbstractModel
import uuid

# Create your models here.

class Incident(BaseAbstractModel):

    """ Incident model  """

    place = models.CharField(max_length=50,blank=False, null=False)
    personal_number = models.CharField(max_length=12,blank=False, null=False)
    description = models.TextField(max_length=500,blank=False, null=False)
    action = models.TextField(max_length=500,blank=True, null=True)
    image = models.ImageField(upload_to='images/',blank=True, null=True)
    incident_date=models.DateTimeField(blank=False, null=False) 

따라서 위의 모델 사건은 기본 추상 모델의 모든 분야에 내재되어 있습니다.


-1

이 질문은 "장고가 자동 증가 정수 대신 모든 테이블의 모든 데이터베이스 ID에 대해 UUID를 사용하도록하는 방법이 있습니까?"로 다시 표현할 수 있습니다.

물론입니다.

id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

내 모든 테이블에서이 작업을 수행하는 방법을 찾을 수 없습니다.

  1. 타사 모듈
  2. Django는 ManyToMany 테이블을 생성했습니다.

따라서 이것은 Django 기능이 누락 된 것으로 보입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.