Використання UUID як первинного ключа в моделях Django (вплив загальних відносин)


90

З ряду причин ^, я хотів би використовувати 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)

Схоже, цей додаток припускає, що основним ключем для моделі, за якою голосують, є ціле число.

Здається, вбудований додаток для коментарів здатний обробляти нецілі ПК, однак:

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, не завдаючи зайвих проблем?


^ Деякі причини: приховування підрахунку об’єктів, запобігання "скануванню ідентифікатора", використання декількох серверів для створення неконфліктних об’єктів, ...

Відповіді:


55

Первинний ключ UUID спричинить проблеми не тільки із загальними відносинами, але і з ефективністю в цілому: кожен зовнішній ключ буде значно дорожчим - і для зберігання, і для приєднання - ніж машинне слово.

Однак ніщо не вимагає, щоб UUID був первинним ключем: просто зробіть його вторинним ключем, доповнивши свою модель полем uuid unique=True. Використовуйте неявний первинний ключ як звичайний (внутрішній для вашої системи) та використовуйте UUID як зовнішній ідентифікатор.


16
Джо Холлоуей, у цьому немає потреби: ви можете просто надати функцію генерації UUID як поле default.
Пі Дельпорт,

4
Джо: Я використовую django_extensions.db.fields.UUIDField для створення своїх UUID в моїй моделі. Це просто, я просто визначаю своє поле так: user_uuid = UUIDField ()
mitchf

3
@MatthewSchinckel: Коли ви використовуєте, django_extensions.db.fields.UUIDFieldяк згадав mitchf, у вас не буде проблем з міграціями Джанго-Південь - згадане ним поле має вбудовану підтримку для південних міграцій.
Tadeck

125
Жахлива відповідь. Postgres має власні (128 бітні) UUID, які складають лише 2 слова на 64-бітній машині, тому не будуть "значно дорожчими", ніж рідні 64-бітні INT.
постфутурист

8
Піт, враховуючи, що на ньому є індекс btree, скільки порівнянь буде за даним запитом? Не багато. Крім того, я впевнений, що виклик memcmp буде вирівняний та оптимізований для більшості ОС. Залежно від характеру запитань, я б сказав, що не використання UUID через можливі (ймовірно незначні) відмінності в продуктивності є неправильною оптимізацією.
постфутурист

219

Як видно з документації , у 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 використовувати це кожен раз при автоматичному створенні ідентифікаторів для таблиць?
anon58192932

3
@ anon58192932 Не зовсім зрозуміло, що саме ви маєте на увазі під словом "кожен раз". Якщо ви хочете використовувати UUID для кожної моделі, створіть власну абстрактну базову модель і використовуйте її замість django.models.Model.
Назар Топольський

4
Різниця у продуктивності незначна лише тоді, коли база даних підтримує тип UUID. Django все ще використовує поле знаків для більшості БД (postgresql - це єдиний задокументований db для підтримки поля UUID).
NirIzr

Мене бентежить, чому це популярна відповідь ... Задавали питання про труднощі зі сторонніми пакетами. Незважаючи на те, що Django власним чином підтримує UUID, все ще існує ціла низка пакетів, які не враховують UUID. З мого досвіду, це біль.
ambe5960

12

Я зіткнувся з подібною ситуацією і дізнався в офіційній документації Django , що object_idце не повинно бути того самого типу, що і primary_key відповідної моделі. Наприклад, якщо ви хочете, щоб ваші загальні відносини були дійсними як для IntegerField, так і для ідентифікаторів CharField , просто встановіть, 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 є кластерним індексом, коли він не збільшується автоматично, двигуну БД доведеться вдатися до вашого фізичного накопичувача при вставці рядка з ідентифікатором нижчої порядковості, що буде відбуватися весь час із UUID. Коли ви отримуєте багато даних у своїй БД, може знадобитися багато секунд або навіть хвилин, щоб просто вставити один новий запис. І ваш диск з часом стане фрагментованим, що вимагатиме періодичної дефрагментації диска. Це все насправді погано.

Щоб вирішити їх, нещодавно я придумав таку архітектуру, якою, на мою думку, варто було б поділитися.

Псевдопервинний ключ UUID

Цей метод дозволяє використовувати переваги UUID як первинного ключа (використовуючи унікальний індекс UUID), зберігаючи при цьому автоматично збільшений PK для вирішення фрагментації та вставки проблем з дегрегацією продуктивності від нечислового PK.

Як це працює:

  1. Створіть первинний ключ із автоматичним збільшенням, що викликається pkidна ваших моделях БД.
  2. Додайте унікально проіндексований UUID id поле щоб можна було шукати за ідентифікатором UUID, замість числового первинного ключа.
  3. Наведіть ForeignKey на UUID (використовуючи to_field='id' ), щоб ваші зовнішні ключі могли правильно представляти псевдо-PK замість числового ідентифікатора.

По суті, ви зробите наступне:

Спочатку створіть абстрактну базову модель 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), не забудьте також створити базовий клас 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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.