在Django模型中使用UUID作为主键(通用关系影响)


90

由于多种原因^,我想在某些Django模型中使用UUID作为主键。如果这样做,我是否仍可以使用通过ContentType使用通用关系的外部应用程序,例如“ contrib.comments”,“ django-voting”或“ django-tagging”?

以“ django-voting”为例,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爬行”,使用多台服务器创建不冲突的对象,...

Answers:


55

一个UUID主键不仅会导致通用关系,而且会给整个效率带来问题:每个外键(无论是存储还是加入)都比机器字昂贵得多。

但是,没有什么要求UUID是主键:通过使用uuid字段对模型进行补充,只需将其作为辅助键即可unique=True。照常使用隐式主键(系统内部),并使用UUID作为外部标识符。


16
Joe Holloway,无需这样做:您只需提供UUID生成函数作为字段的即可default
Pi Delport

4
乔:我使用django_extensions.db.fields.UUIDField在模型中创建我的UUID。很简单,我只是这样定义字段:user_uuid = UUIDField()
mitchf 2010年

3
@MatthewSchinckel:当您django_extensions.db.fields.UUIDField按照mitchf的说明使用时,Django-South迁移不会有任何问题-他提到的字段内置了对South迁移的支持。
塔德克2012年

125
糟糕的答案。Postgres具有本机(128位)UUID,在64位计算机上只有2个字,因此不会比本机64位INT“贵得多”。
postfuturist 2013年

8
Piet,鉴于它上面有一个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在为表自动创建ID时每次使用?
anon58192932 '01 -02-26

3
@ anon58192932不太清楚“每次”到底是什么意思。如果希望每个模型都使用UUID,请创建自己的抽象基础模型并使用它代替django.models.Model。
НазарТопольський

4
仅当基础数据库支持UUID类型时,性能差异才可以忽略不计。Django仍然对大多数数据库使用charfield(PostgreSQL是唯一记录的支持UUID字段的数据库)。
NirIzr

我很困惑为什么这是一个受欢迎的答案...问题是询问第三方软件包的难度。尽管Django原生支持UUID,但似乎仍有许多软件包无法说明UUID。以我的经验,这很痛苦。
ambe5960

12

我遇到了类似的情况,并在Django官方文档中发现,object_id不必与相关模型的primary_key具有相同的类型。例如,如果您希望您的通用关系对IntegerFieldCharField id均有效,则只需将您的设置object_idCharField即可。由于整数可以强制转换为字符串,所以就可以了。这同样适用于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)

3

UUID作为PK的真正问题是磁盘碎片和与非数字标识符关联的插入性能下降。因为PK是聚集索引,所以当它不自动递增时,当插入具有较低序号的ID的行时,数据库引擎将不得不诉诸物理驱动器,而对于UUID来说,这将一直发生。当您在数据库中获得大量数据时,仅插入一条新记录可能要花费几秒钟甚至几分钟。您的磁盘最终将变得碎片化,需要定期对磁盘进行碎片整理。这真的很糟糕。

为了解决这些问题,我最近想出了以下我认为值得分享的体系结构。

UUID伪主键

使用此方法,您可以利用UUID作为主键的好处(使用唯一索引UUID),同时保持自动递增的PK来解决碎片问题并插入具有非数字PK的性能下降问题。

这个怎么运作:

  1. 创建一个pkid在数据库模型上调用的自动递增的主键。
  2. 添加唯一索引的UUIDid字段,以允许您通过UUID ID(而不是数字主键)进行搜索。
  3. 将ForeignKey指向UUID(使用to_field='id'),以允许您的外键正确表示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指向UUIDid字段而不是自动递增的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' 

并为您的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

这个问题可以改写为“是否有办法让Django对所有表中的所有数据库ID都使用UUID而不是自动递增的整数?”。

当然可以,我可以:

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

在我所有的表格中,但我找不到以下方法:

  1. 第三方模块
  2. Django生成了ManyToMany表

因此,这似乎是Django缺少的功能。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.