Использование UUID в качестве первичного ключа в моделях Django (влияние общих отношений)

91

По ряду причин ^ я хотел бы использовать 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-адресов, использование нескольких серверов для создания неконфликтующих объектов, ...

Mitchf
источник

Ответы:

56

Первичный ключ 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, у вас не будет проблем с миграциями Django-South - поле, указанное им, имеет встроенную поддержку миграции South.
Tadeck
126
Ужасный ответ. Postgres имеет собственные (128-битные) UUID, которые представляют собой всего 2 слова на 64-битной машине, поэтому не будет «значительно дороже», чем собственный 64-битный INT.
postfuturist
8
Piet, учитывая, что на нем есть индекс btree, сколько сравнений будет по данному запросу? Не много. Кроме того, я уверен, что вызов memcmp будет согласован и оптимизирован для большинства ОС. Исходя из характера вопросов, я бы сказал , не используя UUID из - за возможные различия (вероятно , ничтожна) производительности является неправильной оптимизацией.
постфутурист 02
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
источник
@Keithhackbarth, как нам настроить django на использование этого каждый раз при автоматическом создании идентификаторов для таблиц?
anon58192932
3
@ anon58192932 Не совсем понятно, что именно вы подразумеваете под «каждый раз». Если вы хотите, чтобы UUID использовались для каждой модели, создайте свою собственную абстрактную базовую модель и используйте ее вместо django.models.Model.
Назар Топольський 01
4
Различия в производительности незначительны только в том случае, если базовая база данных поддерживает тип UUID. Django по-прежнему использует charfield для большинства баз данных (postgresql - единственная документированная база данных, поддерживающая поле UUID).
NirIzr 06
Я не понимаю, почему это популярный ответ ... Вопрос касался проблем со сторонними пакетами. Несмотря на то, что 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 - это фрагментация диска и ухудшение качества вставки, связанное с нечисловыми идентификаторами. Поскольку PK является кластеризованным индексом, когда он не увеличивается автоматически, вашему движку БД придется прибегать к вашему физическому диску при вставке строки с идентификатором более низкого порядка, что будет происходить все время с UUID. Когда вы получаете много данных в своей БД, вставка одной новой записи может занять много секунд или даже минут. И ваш диск со временем станет фрагментированным, что потребует периодической дефрагментации диска. Это все очень плохо.

Чтобы решить эту проблему, я недавно придумал следующую архитектуру, которой, как мне казалось, стоит поделиться.

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

Этот метод позволяет вам использовать преимущества UUID в качестве первичного ключа (с использованием уникального индекса UUID), сохраняя при этом автоматически увеличивающийся PK для устранения фрагментации и вставки проблем снижения производительности из-за наличия нечислового PK.

Как это работает:

  1. Создайте автоматически увеличивающийся первичный ключ, вызываемый pkidв ваших моделях БД.
  2. Добавьте idполе UUID с уникальным индексом, чтобы можно было выполнять поиск по идентификатору 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), не забудьте также создать класс 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.

EMS
источник