Sử dụng UUID làm khóa chính trong các mô hình Django (tác động đến quan hệ chung)


90

Vì một số lý do ^, tôi muốn sử dụng UUID làm khóa chính trong một số mô hình Django của mình. Nếu tôi làm như vậy, liệu tôi có còn có thể sử dụng các ứng dụng bên ngoài như "Contrib.comments", "django-vote" hoặc "django-tagging" sử dụng quan hệ chung thông qua ContentType không?

Sử dụng "django-bỏ phiếu" làm ví dụ, mô hình Bỏ phiếu trông giống như sau:

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)

Ứng dụng này dường như đang giả định rằng khóa chính cho mô hình đang được bình chọn là một số nguyên.

Tuy nhiên, ứng dụng nhận xét tích hợp dường như có khả năng xử lý các PK không phải số nguyên:

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")

Vấn đề "số nguyên-PK-giả định" này có phải là tình huống phổ biến đối với các ứng dụng của bên thứ ba khiến việc sử dụng UUID trở nên khó khăn không? Hoặc, có thể, tôi đang hiểu sai tình huống này?

Có cách nào để sử dụng UUID làm khóa chính trong Django mà không gây ra quá nhiều rắc rối không?


^ Một số lý do: ẩn số lượng đối tượng, ngăn url "id thu thập thông tin", sử dụng nhiều máy chủ để tạo các đối tượng không xung đột, ...

Câu trả lời:


55

Khóa chính UUID sẽ gây ra các vấn đề không chỉ với quan hệ chung chung mà còn với hiệu quả nói chung: mọi khóa ngoại sẽ đắt hơn đáng kể — cả để lưu trữ và tham gia — so với một từ máy.

Tuy nhiên, không có gì yêu cầu UUID làm khóa chính: chỉ cần đặt nó làm khóa phụ , bằng cách bổ sung mô hình của bạn với trường uuid với unique=True. Sử dụng khóa chính ngầm như bình thường (nội bộ trong hệ thống của bạn) và sử dụng UUID làm mã định danh bên ngoài của bạn.


16
Joe Holloway, không cần điều đó: bạn có thể chỉ cần cung cấp chức năng tạo UUID làm trường default.
Pi Delport

4
Joe: Tôi sử dụng django_extensions.db.fields.UUIDField để tạo UUID trong mô hình của mình. Nó đơn giản, tôi chỉ xác định lĩnh vực của tôi như thế này: user_uuid = UUIDField ()
mitchf

3
@MatthewSchinckel: Khi bạn sử dụng django_extensions.db.fields.UUIDFieldnhư được đề cập bởi mitchf, bạn sẽ không gặp vấn đề gì với việc di cư Django-South - trường mà anh ấy đề cập đã tích hợp sẵn hỗ trợ cho việc di cư Nam.
Tadeck

125
Câu trả lời kinh khủng. Postgres có UUID gốc (128 bit) chỉ có 2 từ trên máy 64 bit, vì vậy sẽ không "đắt hơn đáng kể" so với INT 64 bit gốc.
postfuturist

8
Piet, cho rằng nó có một chỉ mục btree trên đó, có bao nhiêu phép so sánh sẽ có trên một truy vấn nhất định? Không nhiều. Ngoài ra, tôi chắc chắn rằng lệnh gọi memcmp sẽ được căn chỉnh và tối ưu hóa trên hầu hết các hệ điều hành. Dựa trên bản chất của các câu hỏi, tôi sẽ nói rằng không sử dụng UUID vì sự khác biệt về hiệu suất có thể (có thể không đáng kể) là tối ưu hóa sai.
postfuturist

219

Như đã thấy trong tài liệu , từ Django 1.8 có một trường UUID được tích hợp sẵn. Sự khác biệt về hiệu suất khi sử dụng UUID so với số nguyên là không đáng kể.

import uuid
from django.db import models

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

Bạn cũng có thể kiểm tra câu trả lời này để biết thêm thông tin.


@Keithhackbarth làm cách nào để chúng ta đặt django sử dụng điều này mỗi khi tự động tạo ID cho bảng?
anon58192932

3
@ anon58192932 Không thực sự rõ ý bạn chính xác là "mọi lúc". Nếu bạn muốn UUID được sử dụng cho mọi mô hình, hãy tạo mô hình cơ sở trừu tượng của riêng bạn và sử dụng nó thay vì django.models.Model.
Назар Топольський Ngày

4
Sự khác biệt về hiệu suất chỉ không đáng kể khi cơ sở dữ liệu bên dưới hỗ trợ loại UUID. Django vẫn sử dụng một charfield cho hầu hết các DB (postgresql là db duy nhất được tài liệu hóa để hỗ trợ trường UUID).
NirIzr

Tôi bối rối tại sao đây là một câu trả lời phổ biến ... Câu hỏi đặt ra về khó khăn với các gói của bên thứ ba. Mặc dù Django tự nhiên hỗ trợ UUID, dường như vẫn có một số gói không tính đến UUID. Theo kinh nghiệm của tôi, đó là một nỗi đau.
ambe5960

12

Tôi đã gặp phải một tình huống tương tự và phát hiện ra trong tài liệu Django chính thức , rằng khóa object_idkhông nhất thiết phải cùng loại với khóa_ chính của mô hình liên quan. Ví dụ: nếu bạn muốn mối quan hệ chung của mình hợp lệ cho cả Id IntegerFieldCharField , chỉ cần đặt của bạn object_idCharField . Vì các số nguyên có thể buộc thành chuỗi nên sẽ ổn thôi. Tương tự với UUIDField .

Thí dụ:

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

Vấn đề thực sự với UUID dưới dạng PK là sự phân mảnh ổ đĩa và sự suy giảm chèn liên quan đến các định danh không phải số. Bởi vì PK là một chỉ mục được phân nhóm, khi nó không tự động tăng lên, công cụ DB của bạn sẽ phải sử dụng ổ đĩa vật lý của bạn khi chèn một hàng có id có thứ tự thấp hơn, điều này sẽ xảy ra mọi lúc với UUID. Khi bạn nhận được nhiều dữ liệu trong DB của mình, có thể mất nhiều giây hoặc thậm chí vài phút chỉ để chèn một bản ghi mới. Và đĩa của bạn cuối cùng sẽ bị phân mảnh, yêu cầu chống phân mảnh đĩa định kỳ. Tất cả đều thực sự tồi tệ.

Để giải quyết vấn đề này, gần đây tôi đã đưa ra kiến ​​trúc sau đây mà tôi nghĩ sẽ đáng để chia sẻ.

Khóa chính của UUID Pseudo

Phương pháp này cho phép bạn tận dụng các lợi ích của UUID làm Khóa chính (sử dụng UUID chỉ mục duy nhất), đồng thời duy trì PK tự động tăng dần để giải quyết tình trạng phân mảnh và chèn các lo ngại về phân cấp hiệu suất khi có PK không phải số.

Làm thế nào nó hoạt động:

  1. Tạo khóa chính tự động tăng dần được gọi pkidtrên Mô hình DB của bạn.
  2. Thêm idtrường UUID được lập chỉ mục duy nhất để cho phép bạn tìm kiếm theo id UUID, thay vì khóa chính số.
  3. Trỏ ForeignKey vào UUID (sử dụng to_field='id') để cho phép các khóa ngoại của bạn đại diện đúng cho Pseudo-PK thay vì ID số.

Về cơ bản, bạn sẽ làm như sau:

Đầu tiên, tạo một Mô hình cơ sở Django trừu tượng

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

Đảm bảo mở rộng mô hình cơ sở thay vì các mô hình.

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

Ngoài ra, hãy đảm bảo rằng ForeignKeys của bạn trỏ đến trường UUID idthay vì pkidtrường tăng tự động :

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

Nếu bạn đang sử dụng Django Rest Framework (DRF), hãy đảm bảo bạn cũng tạo một lớp Base ViewSet để đặt trường tìm kiếm mặc định:

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

Và mở rộng điều đó thay vì ModelViewSet cơ sở cho các chế độ xem API của bạn:

class SiteViewSet(UUIDModelViewSet):
    model = Site

class PageViewSet(UUIDModelViewSet):
    model = Page

Thêm ghi chú về lý do và cách thức trong bài viết này: https://www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps


0

điều này có thể được thực hiện bằng cách sử dụng mô hình trừu tượng cơ sở tùy chỉnh, sử dụng các bước sau.

Đầu tiên, hãy tạo một thư mục trong dự án của bạn, gọi nó là basemodel, sau đó thêm một abstractmodelbase.py như sau:

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']

thứ hai: trong tất cả tệp mô hình của bạn cho mỗi ứng dụng, hãy làm điều này

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) 

Vì vậy, sự cố mô hình trên vốn có tất cả các trường trong mô hình baseabstract.


-1

Câu hỏi có thể được diễn đạt lại là "có cách nào để khiến Django sử dụng UUID cho tất cả id cơ sở dữ liệu trong tất cả các bảng thay vì một số nguyên tăng tự động không?".

Chắc chắn, tôi có thể làm:

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

trong tất cả các bảng của tôi, nhưng tôi không thể tìm ra cách thực hiện việc này cho:

  1. Mô-đun của bên thứ 3
  2. Django đã tạo nhiều bảng ManyToMany

Vì vậy, đây dường như là một tính năng Django bị thiếu.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.