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

mit*_*chf 81 django uuid content-type primary-key django-models

出于多种原因^,我想在我的一些Django模型中使用UUID作为主键.如果我这样做,我是否仍然可以使用"contrib.com","django-voting"或"django-tagging"之类的外部应用程序,它们通过ContentType使用通用关系?

以"django-voting"为例,投票模型如下所示:

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)
Run Code Online (Sandbox Code Playgroud)

这个应用程序似乎假设被投票的模型的主键是一个整数.

内置的评论应用程序似乎能够处理非整数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")
Run Code Online (Sandbox Code Playgroud)

对于第三方应用程序而言,这种"整数假定PK"问题是否会成为一种常见情况,这会使使用UUID变得痛苦?或者,我可能会误读这种情况吗?

有没有办法在Django中使用UUID作为主键而不会造成太多麻烦?


^一些原因:隐藏对象计数,防止URL"id爬行",使用多个服务器创建非冲突对象,...

kei*_*rth 191

从文档中可以看出,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)
Run Code Online (Sandbox Code Playgroud)

您还可以查看此答案以获取更多信息.

  • 这是否解决了一般关系问题? (6认同)
  • 我很困惑为什么这是一个受欢迎的答案......问题是询问第三方软件包的困难。尽管 Django 本身支持 UUID,但似乎仍然有许多包不考虑 UUID。根据我的经验,这是一种痛苦。 (5认同)
  • @ anon58192932不是很清楚"每次"到底是什么意思.如果您希望UUID用于每个模型,请创建您自己的抽象基础模型并使用它而不是django.models.Model. (3认同)
  • 当底层数据库支持UUID类型时,性能差异只能忽略不计.Django仍然为大多数数据库使用charfield(postgresql是唯一记录的支持UUID字段的数据库). (3认同)

Pi *_*ort 46

UUID主键不仅会导致通用关系出现问题,而且通常会带来效率问题:每个外键的存储和加入都比机器字要贵得多.

但是,没有任何东西需要UUID作为主键:只需将其作为辅助键,通过使用uuid字段补充模型unique=True.正常使用隐式主键(系统内部),并使用UUID作为外部标识符.

  • 可怕的答案.Postgres具有原生(128位)UUID,在64位机器上只有2个字,因此不会比原生64位INT"显着更昂贵". (105认同)
  • Joe Holloway,不需要:你可以简单地提供UUID生成函数作为字段的'default`. (15认同)
  • Piet,鉴于它有一个btree索引,在给定的查询中会有多少比较?不多.此外,我确信memcmp调用将在大多数操作系统上进行对齐和优化.根据问题的性质,我会说*不*使用UUID,因为可能(可能忽略不计)性能差异是错误的优化. (8认同)
  • Joe:我使用django_extensions.db.fields.UUIDField在我的模型中创建我的UUID.这很简单,我只是像这样定义我的字段:user_uuid = UUIDField() (4认同)
  • @MatthewSchinckel:当您使用mitchf提到的`django_extensions.db.fields.UUIDField`时,您将对Django-South迁移没有任何问题 - 他提到的字段内置了对South迁移的支持. (3认同)
  • postfuturist:PostgreSQL 的 UUID 类型(目前,至少)作为 C 字符数组实现,使用 memcmp() 进行比较:这确实比机器字比较昂贵,一般来说。除此之外,您必须考虑局部性:如果您的 UUID 是均匀分布的,而不是顺序分布的,那么您的索引性能可能会下降,具体取决于您的工作负载。(这对于索引集群后端尤其重要,例如 MySQL InnoDB。) (2认同)
  • 当然,这取决于:这就是优化的本质。关于索引,重要的不是树的深度或比较次数,而是键分布:随机分布本质上要求所有页面都适合工作内存(并使索引更新成本更高),而顺序键将倾向于遵循数据的自然聚类。这可能(也可能不会)对性能产生巨大影响,但无论如何,这是需要注意的。 (2认同)
  • 你是包罗万象的,概括的。这取决于引擎盖下的数据库实现。而且我知道一种比机器字更有效地比较字符串的实现。 (2认同)

Jor*_*rdi 11

我遇到了类似的情况,并在官方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)
Run Code Online (Sandbox Code Playgroud)


Ste*_*ley 11

UUID 作为 PK 的真正问题是与非数字标识符相关的磁盘碎片和插入降级。因为 PK 是一个聚集索引(几乎在除 PostgreSQL 之外的每个 RDBMS 中),当它不是自动递增时,您的数据库引擎在插入具有较低序数的行时将不得不求助于您的物理驱动器,这将一直发生带有 UUID。当您在数据库中获得大量数据时,插入一条新记录可能需要数秒甚至数分钟。并且您的磁盘最终会变得碎片化,需要定期进行磁盘碎片整理。这一切都非常糟糕。

为了解决这些问题,我最近提出了以下我认为值得分享的架构。

UUID 伪主键

此方法允许您利用 UUID 作为主键的优势(使用唯一索引 UUID),同时维护自动递增的 PK 以解决非数字 PK 的碎片和插入性能下降问题。

这个怎么运作:

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

基本上,您将执行以下操作:

首先,创建一个抽象的Django Base Model

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
Run Code Online (Sandbox Code Playgroud)

确保扩展基本模型而不是 models.Model

class Site(UUIDModel):
    name = models.CharField(max_length=255)
Run Code Online (Sandbox Code Playgroud)

还要确保您的 ForeignKeys 指向 UUIDid字段而不是自动增加的pkid字段:

class Page(UUIDModel):
    site = models.ForeignKey(Site, to_field='id', on_delete=models.CASCADE)
Run Code Online (Sandbox Code Playgroud)

如果您使用的是 Django Rest Framework (DRF),请确保还创建一个 Base ViewSet 类来设置默认搜索字段:

class UUIDModelViewSet(viewsets.ModelViewSet):
    lookup_field = 'id' 
Run Code Online (Sandbox Code Playgroud)

并为您的 API 视图扩展它而不是基础 ModelViewSet:

class SiteViewSet(UUIDModelViewSet):
    model = Site

class PageViewSet(UUIDModelViewSet):
    model = Page
Run Code Online (Sandbox Code Playgroud)

关于本文中的原因和方法的更多说明:https : //www.stevenmoseley.com/blog/uuid-primary-keys-django-rest-framework-2-steps

  • 这是不正确的。Postgres 不会按主键对磁盘上的行进行排序。表以块的形式写入,当添加或更新一行时,它被放置在最后一个块的末尾。 (7认同)

小智 7

这可以通过使用自定义基本抽象模型来完成,步骤如下。

首先在项目中创建一个文件夹,将其命名为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']
Run Code Online (Sandbox Code Playgroud)

第二:在每个应用程序的所有模型文件中执行此操作

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) 
Run Code Online (Sandbox Code Playgroud)

因此,上述模型事件固有了基础抽象模型中的所有字段。