ChatGPT解决这个技术问题 Extra ChatGPT

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

出于多种原因^,我想在我的一些 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 变得很痛苦?或者,可能,我误读了这种情况?

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


p
pyjavo

As seen in the documentation,从 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)

您还可以check this answer了解更多信息。


@Keithhackbarth 我们如何设置 django 在每次自动为表创建 ID 时使用它?
@anon58192932 不太清楚“每次”到底是什么意思。如果您希望每个模型都使用 UUID,请创建您自己的抽象基础模型并使用它而不是 django.models.Model。
只有当底层数据库支持 UUID 类型时,性能差异才可以忽略不计。对于大多数数据库,Django 仍然使用字符字段(postgresql 是唯一记录在案的数据库以支持 UUID 字段)。
我很困惑为什么这是一个流行的答案...问题是询问第三方软件包的难度。尽管 Django 本身支持 UUID,但似乎仍有许多包不考虑 UUID。根据我的经验,这是一种痛苦。
请注意,这不适用于将现有模型的主键切换为 UUID。
P
Pi Delport

UUID 主键不仅会导致泛型关系问题,还会导致效率问题:每个外键都将比机器字更昂贵——无论是存储还是连接。

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


Joe Holloway,不需要这样做:您可以简单地提供 UUID 生成函数作为字段的 default
Joe:我使用 django_extensions.db.fields.UUIDField 在我的模型中创建我的 UUID。很简单,我只是这样定义我的字段:user_uuid = UUIDField()
@MatthewSchinckel:当您使用 mitchf 提到的 django_extensions.db.fields.UUIDField 时,Django-South 迁移不会有任何问题 - 他提到的字段内置了对 South 迁移的支持。
可怕的答案。 Postgres 具有本机(128 位)UUID,在 64 位机器上只有 2 个字,因此不会比本机 64 位 INT“贵得多”。
Piet,考虑到它上面有一个 btree 索引,给定的查询会有多少比较?不太多。此外,我确信 memcmp 调用将在大多数操作系统上对齐和优化。根据问题的性质,我会说因为可能(可能可以忽略不计)的性能差异而不使用 UUID 是错误的优化。
S
Steven Moseley

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

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

UUID 伪主键

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

这个怎么运作:

在您的数据库模型上创建一个名为 pkid 的自动递增主键。添加唯一索引的 UUID id 字段以允许您按 UUID id 搜索,而不是数字主键。将 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

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

class Site(UUIDModel):
    name = models.CharField(max_length=255)

还要确保您的 ForeignKeys 指向 UUID id 字段而不是自动递增的 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


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

我遇到了类似的情况,在 official Django documentation 中发现,object_id 不必与相关模型的 primary_key 属于同一类型。例如,如果您希望通用关系对 IntegerFieldCharField 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)

A
Anatol

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

首先在您的项目中创建一个文件夹,将其命名为 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) 

所以上述模型事件包含了baseabstract模型中的所有领域。


E
EMS

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

当然,我可以这样做:

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

在我所有的桌子上,但我找不到这样做的方法:

3rd 方模块 Django 生成 ManyToMany 表

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


关注公众号,不定期副业成功案例分享
关注公众号

不定期副业成功案例分享

领先一步获取最新的外包任务吗?

立即订阅