การใช้ UUID เป็นคีย์หลักในโมเดล Django (ผลกระทบของความสัมพันธ์ทั่วไป)


92

ด้วยเหตุผลหลายประการ ^ ฉันต้องการใช้ UUID เป็นคีย์หลักใน Django บางรุ่นของฉัน หากฉันทำเช่นนั้นฉันจะยังสามารถใช้แอปภายนอกเช่น "Contrib.comments", "django-vote" หรือ "django-tagging" ซึ่งใช้ความสัมพันธ์ทั่วไปผ่าน ContentType ได้หรือไม่

การใช้ "django-vote" เป็นตัวอย่างรูปแบบการโหวตจะมีลักษณะดังนี้:

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 เป็นเรื่องยุ่งยากหรือไม่ หรืออาจเป็นไปได้ว่าฉันกำลังอ่านสถานการณ์นี้ผิด?

มีวิธีใช้ UUID เป็นคีย์หลักใน Django โดยไม่ทำให้เกิดปัญหามากเกินไปหรือไม่?


^ เหตุผลบางประการ: การซ่อนจำนวนวัตถุป้องกัน url "id crawling" ใช้เซิร์ฟเวอร์หลายตัวเพื่อสร้างวัตถุที่ไม่ขัดแย้งกัน ...

คำตอบ:


57

คีย์หลักของ UUID จะทำให้เกิดปัญหาไม่เพียง แต่กับความสัมพันธ์ทั่วไปเท่านั้น แต่ยังมีประสิทธิภาพโดยทั่วไป: คีย์ต่างประเทศทุกตัวจะมีราคาแพงกว่าอย่างมาก - ทั้งในการจัดเก็บและการเชื่อมต่อ - มากกว่าคำในเครื่อง

แต่ไม่มีอะไรต้อง UUID ที่จะเป็นคีย์หลัก: เพียงแค่ทำให้มันเป็นรองที่สำคัญโดยการเสริมรูปแบบของคุณกับข้อมูล uuid unique=Trueกับ ใช้คีย์หลักโดยนัยตามปกติ (ภายในระบบของคุณ) และใช้ UUID เป็นตัวระบุภายนอกของคุณ


16
โจฮอลโลเวย์ไม่ต้องการสิ่งนั้น: คุณสามารถจัดหาฟังก์ชันการสร้าง UUID ให้เป็นฟิลด์defaultได้
Pi Delport

4
โจ: ฉันใช้ django_extensions.db.fields.UUIDField เพื่อสร้าง UUID ในโมเดลของฉัน มันง่ายมากฉันแค่กำหนดฟิลด์ของฉันแบบนี้ user_uuid = UUIDField ()
mitchf

3
@MatthewSchinckel: เมื่อคุณใช้django_extensions.db.fields.UUIDFieldตามที่ mitchf กล่าวไว้คุณจะไม่มีปัญหากับการย้ายถิ่นของ Django-South - ฟิลด์ที่เขากล่าวถึงมีการรองรับการย้ายถิ่นทางใต้ในตัว
Tadeck

127
คำตอบแย่มาก Postgres มี UUID ดั้งเดิม (128 บิต) ซึ่งเป็นเพียง 2 คำในเครื่อง 64 บิตดังนั้นจึงไม่ "แพงกว่าอย่างมีนัยสำคัญ" ไปกว่า INT 64 บิตดั้งเดิม
postfuturist

8
Piet เนื่องจากมีดัชนี btree อยู่จะมีการเปรียบเทียบกี่ข้อในแบบสอบถามที่ระบุ? ไม่มาก. นอกจากนี้ฉันแน่ใจว่าการโทร memcmp จะถูกจัดแนวและปรับให้เหมาะสมกับ OS ส่วนใหญ่ จากลักษณะของคำถามฉันจะบอกว่าการไม่ใช้ UUID เนื่องจากความแตกต่างของประสิทธิภาพที่เป็นไปได้ (อาจเล็กน้อย) เป็นการเพิ่มประสิทธิภาพที่ไม่ถูกต้อง
postfuturist

224

ดังที่เห็นในเอกสารจาก 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 เราจะตั้งค่าให้ django ใช้สิ่งนี้ทุกครั้งเมื่อสร้าง ID สำหรับตารางโดยอัตโนมัติได้อย่างไร?
anon58192932

3
@ anon58192932 ไม่ชัดเจนจริงๆว่า "ทุกครั้ง" หมายถึงอะไร หากคุณต้องการใช้ UUID สำหรับทุกโมเดลให้สร้างโมเดลพื้นฐานนามธรรมของคุณเองและใช้แทน django.models.Model
НазарТопольський

5
ความแตกต่างของประสิทธิภาพเป็นเพียงเล็กน้อยเมื่อฐานข้อมูลพื้นฐานรองรับประเภท UUID Django ยังคงใช้ชาร์ฟิลด์สำหรับฐานข้อมูลส่วนใหญ่ (postgresql เป็นฐานข้อมูลเดียวที่ได้รับการบันทึกเพื่อรองรับฟิลด์ UUID)
NirIzr

ฉันงงว่าทำไมถึงเป็นคำตอบยอดนิยม ... คำถามคือถามเกี่ยวกับปัญหาเกี่ยวกับแพ็คเกจของบุคคลที่สาม แม้ Django จะรองรับ UUID แต่ก็ยังมีแพ็คเกจอีกจำนวนหนึ่งที่ไม่ได้ใช้ UUID จากประสบการณ์ของฉันมันเป็นความเจ็บปวด
ambe5960

โปรดทราบว่าวิธีนี้ใช้ไม่ได้กับการเปลี่ยนคีย์หลักสำหรับรุ่นที่มีอยู่เป็น UUID
infiniteloop

12

ฉันพบสถานการณ์ที่คล้ายกันและพบในเอกสาร Django อย่างเป็นทางการว่าobject_idไม่จำเป็นต้องเป็นประเภทเดียวกับคีย์หลักของโมเดลที่เกี่ยวข้อง ตัวอย่างเช่นถ้าคุณต้องการความสัมพันธ์ทั่วไปของคุณถูกต้องสำหรับทั้งIntegerFieldและCharField ID ของเพียงแค่ตั้งค่าของคุณobject_idจะเป็นCharField เนื่องจากจำนวนเต็มสามารถบังคับให้เป็นสตริงได้จึงไม่เป็นไร กันไปสำหรับ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

ปัญหาที่แท้จริงของ UUID ในฐานะ PK คือการกระจายตัวของดิสก์และการย่อยสลายที่เกี่ยวข้องกับตัวระบุที่ไม่ใช่ตัวเลข เนื่องจาก PK เป็นดัชนีแบบคลัสเตอร์เมื่อไม่ได้เพิ่มขึ้นโดยอัตโนมัติเอ็นจิน DB ของคุณจะต้องใช้ไดรฟ์ทางกายภาพของคุณเมื่อแทรกแถวที่มี id ของลำดับต่ำกว่าซึ่งจะเกิดขึ้นตลอดเวลากับ UUIDs เมื่อคุณได้รับข้อมูลจำนวนมากในฐานข้อมูลของคุณอาจใช้เวลาหลายวินาทีหรือหลายนาทีในการแทรกระเบียนใหม่หนึ่งรายการ และในที่สุดดิสก์ของคุณก็จะแตกกระจายโดยต้องมีการจัดเรียงข้อมูลบนดิสก์เป็นระยะ ทั้งหมดนี้แย่จริงๆ

เพื่อแก้ปัญหานี้ฉันเพิ่งได้พบกับสถาปัตยกรรมต่อไปนี้ที่ฉันคิดว่าควรค่าแก่การแบ่งปัน

UUID Pseudo-Primary-Key

วิธีนี้ช่วยให้คุณสามารถใช้ประโยชน์จาก UUID เป็นคีย์หลัก (โดยใช้ UUID ดัชนีเฉพาะ) ในขณะที่ยังคงรักษา PK ที่เพิ่มขึ้นโดยอัตโนมัติเพื่อจัดการกับการแยกส่วนและแทรกข้อกังวลเกี่ยวกับการลดประสิทธิภาพของการมี PK ที่ไม่ใช่ตัวเลข

มันทำงานอย่างไร:

  1. สร้างคีย์หลักที่เพิ่มขึ้นโดยอัตโนมัติที่เรียกว่าpkidโมเดล DB ของคุณ
  2. เพิ่มช่อง UUID ที่จัดทำดัชนีเฉพาะidเพื่อให้คุณสามารถค้นหาด้วยรหัส UUID แทนคีย์หลักที่เป็นตัวเลข
  3. ชี้ ForeignKey ไปที่ UUID (โดยใช้to_field='id') เพื่ออนุญาตให้ Foreign-keys ของคุณแสดง Pseudo-PK แทน ID ตัวเลขได้อย่างถูกต้อง

โดยพื้นฐานแล้วคุณจะทำสิ่งต่อไปนี้:

ขั้นแรกสร้างแบบจำลองฐาน 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ฟิลด์UUID แทนที่จะเป็นpkidฟิลด์ที่เพิ่มขึ้นอัตโนมัติ:

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

หากคุณใช้ Django Rest Framework (DRF) อย่าลืมสร้างคลาส Base ViewSet เพื่อตั้งค่าช่องค้นหาเริ่มต้นด้วย:

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

และขยายแทน ModelViewSet พื้นฐานสำหรับมุมมอง API ของคุณ:

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

คำถามสามารถเปลี่ยนวลีเป็น "มีวิธีทำให้ Django ใช้ 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.