美文网首页
Two Scoops Django 推荐的数据模型最佳实践

Two Scoops Django 推荐的数据模型最佳实践

作者: haiiiiiyun | 来源:发表于2020-03-24 16:50 被阅读0次

    添加或修改数据模型都不能马虎,有关数据的操作都需慎重考虑。

    推荐使用的 Django 数据模型相关的包:

    1. django-model-utils: 使用其 TimeStampedModel
    2. django-extensions: 使用其管理命令 shell_plus,它会自动加载所有已安装应用的数据模型

    基础

    将具有很多数据模型的应用进行拆分

    推荐每个应用的数据模型数不超过 5 个。如果一个应用的数据模型数太多,意味着该应用做的事太多了,需要进行拆分。

    慎重选择数据模型继承方式

    Django 支持三种继承方式:

    1. 抽象基类
    2. 多表继承
    3. 代理模型

    Django 抽象基类和 Python 的抽象基类是不同的!,它们有不同的目的和行为。

    各种继承方式的优缺点:

    继承方式 | 优点 | 缺点
    ------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------|
    抽象基类:只有继承的子数据模型才会创建数据表 | 能在抽象父类中定义共同项来减少重复输入,同时没有多表继承的额外数据表和 join 操作的开销 | 父类不能单独使用
    多表继承:父类和子类都会创建对应的数据表。两者之间隐含有一个 OneToOneField 关联 | 因每个数据模型都有表,故可对父子各自进行查询操作。同时可以通过 parent.child 从父对象直接访问子对象 | 对子表的查询都会有一个与其所有父表的 join 操作。非常不推荐使用多表继承!
    代理模型:只为原始数据模型创建数据表 | 可以为原始数据模型创建一个别名,并添加不同的 Python 行为 | 无法修改数据模型项

    如何确定应该使用哪种继承方式:

    • 如果重叠量很少(只有一两个项),则不需要用继承,只需在两个数据模型中都进行定义
    • 如果两者间有较多的重复项,则应重构代码,将相同项放置到一个抽象基类中
    • 代理模型有时会很有用,但它与其它两种模型继承方式非常不同
    • 应避免使用多表继承,因其即增加了复杂度又提高了性能开销。可以用 OneToOneFieldForeignKeys 来代替。

    数据模型继承实践: TimeStampedModel

    在数据模型中增加 createdmodified 两个时间戳项是个普遍的需求。可以写一个 TimeStampedModel 基类如下:

    # core/models.py
    from django.db import models
    
    class TimeStampedModel(models.Model):
        """
        An abstract base class model that provides self-
        updating ``created`` and ``modified`` fields.
        """
        created = models.DateTimeField(auto_now_add=True)
        modified = models.DateTimeField(auto_now=True)
    
        class Meta:
            abstract = True
    

    然后所有继承自该抽象基类的数据模型都会有这两项:

    # flavors/models.py
    from django.db import models
    from core.models import TimeStampedModel
    
    class Flavor(TimeStampedModel):
        title = models.CharField(max_length=200)
    

    数据库 Migration

    Django 内置一个强大的数据库修改传导库叫 migrations,即 django.db.migrations

    创建 migrations 的建议:

    • 一旦新建了一个应用或数据模型后,应立即为该新的数据模型创建初始的 django.db.migrations。其实我们只需用 python manage.py makemigrations 命令就能完成
    • 在运行之前要对生成的 migration 代码进行检查,特别当涉及到复杂修改的时候。同时使用 sqlmigrate 命令来核查实际使用的 SQL 语句
    • 使用 MIGRATION_MODULES 配置项来管理第三方应用的 migration
    • 不要在意生成的 migrations 很多,我们可以用 squashmigrations 命令对其进行合并

    部署及管理 migrations:

    • 在部署前,应先检查能否对该 migrations 进行回滚。
    • 如果表中有数百万条数据,应在 staging 服务器上对该数量级的数据进行测试。在真实数据库上进行 migrations 花费的时间可能比预期的要多得多!
    • 如果使用 MySQL:
      • 在涉及模式修改前必须进行备份。MySQL 对模式修改不提供事务支持,因此不可能进行回滚
      • 如果可能,在执行修改前应将项目置于 只读 模式
      • 对涉及大量数量的表格进行模式修改会花费很多时间。不是几秒,也不是几分钟,而是用小时计算的!

    Django 数据模型设计

    对数据库进行规范化

    一个数据模型不应该包含有已在其它数据模型中保存过了的数据。

    相关资源:

    缓存应在逆规范化前进行

    只有在绝对必要时才进行逆规范化

    何时使用 Null 和 Blank

    数据项类型 | 设置 null=True | 设置 blank=True
    -----------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------|
    CharField, TextField, SlugField, EmailField, CommaSeparatedIntegerField, UUIDField | 不要这样设置。Django 的传统是将空值保存为空字符串,而获取的 NULL 或空值解析为空字符串 | 可以设置。如果你允许其对应的表单项接受空值。
    FileField, ImageField | 不要这样设置。Django 只是将从 MEDIA_ROOT 到该文件的路径保存在 CharField, 因此规则同上 | 可以这样设置,规则如 CharField
    BooleanField | 不要这样设置。不要用这个 Field, 用 NullBoolField | 不要这样设置
    IntegerField, FloatField, DecimalField, DurationField 等 | 如果你允许在数据库中存储 NULL 的话,可以这样设置 | 如果你允许其相应的表单组件接受空值,可以这样设置,同时要设置 null=True
    DateFieldField, DateField, TimeField 等 | 如果你允许在数据库中存储 NULL 的话,可以这样设置 | 如果你允许其对应的表单组件接受空值,或者使用了 auto_nowauto_now_add 的话,可以这样设置。同时要设置 null=True
    ForeignKey, ManyToManyField, OneToOneField | 可以这样设置 | 可以这样设置
    GenericIPAddressField | 可以这样设置 | 可以这样设置
    IPAddressField | 不推荐使用该项类型,已在 Django 1.7 中过时 | 不推荐使用该项类型

    何时使用 BinaryField

    该类型在 Django 1.8 中添加,用于存放原始二进制数据,或 bytes。在该类型项上无法进行 filter、exclude 或其它的 SQL 操作。但它在以下情况下有用:

    • MessagePack 格式的内容
    • 传感器原始数据
    • 压缩的数据。如 Sentry 保存为 BLOB 的数据, 但是由于历史原因需要进行 base64 编码

    二进制数据串可以会很大,这将会拖慢数据库。此时应将内容保存在一个文件中,然后用 FileField 来引用。

    绝对不要通过 BinaryField 提供文件服务

    • 对数据库的读写比对文件系统更慢
    • 你的数据库会变得越来越大,从而性能越来越低
    • 此时访问文件需要经过 Django 应用层和数据库层共两层。

    尽量避免使用通用关联 models.field.GenericForeignKey

    使用 GenericForeignKey 会使该外键不受完整性约束,有以下的问题:

    • 因在模型间缺少索引,从而会降低查询速度
    • 数据表可能会引用一条不存在的记录,数据有损坏的风险

    其优势是:由于没有完整性约束,一个数据项可以关联到不同类型的记录。大多应用于 voting、 tagging、ratings 中。

    可以使用 ForeignKeyManyToMany 来实现 GenericForeignKey 的功能,从而即保证了数据的完整性,又提升了性能。

    因此,

    • 尽量避免使用通用关联和 GenericForeignKey
    • 如果需要通用关联的话,尝试是否可以通过调整数据模型设计或使用新的 PostgreSQL 项来解决
    • 如果非用不可,最好使用一个现成的第三方应用

    PostgreSQL 特定的项:何时使用 Null 和 Blank

    数据项类型 | 设置 null=True | 设置 blank=True
    -----------------------------------------------------------|-------------------------------------|
    ArrayField | 可以设置 | 可以设置
    HStoreField | 可以设置 | 可以设置
    IntegerRangeField, BigIntegerRangeField 和 FloatRangeField | 可以设置。如果你想在数据库中存储 NULL | 可以设置。如果你允许其对应的表单组件接受空值。同时要设置 null=True
    DatatimeRangeField 和 DateRangeField | 同上 | 同上

    数据模型的 _meta API

    _meta 在 Django 1.8 前只在内部使用的。现成该接口已经公开了。

    _meta 的用途:

    • 获取数据模型的项列表
    • 获取数据模型中特定项的类(或及继承链或者其它衍生信息)
    • 可以确保你获取这些信息所使用的方式在以后的 Django 版本中不会改变

    使用举例:

    • 创建一个 Django 数据模型的自省工具
    • 创建自定义的 form 库
    • 创建一个与 admin 类似的工具来编辑或与 Django 数据模型中的数据交互
    • 创建可视化或分析库,如分析以 "foo" 开头的项的信息

    数据模型管理器

    数据模型管理器用于限制一个数据模型类所有可能的数据记录。Django 为每个数据模型类都提供一个默认的管理器。

    我们可以自己定义数据模型管理器,如:

    from django.db import models
    from django.utils import timezone
    
    class PublishedManager(models.Manager):
    
        use_for_related_fields = True
    
        def published(self, **kwargs):
            return self.filter(pub_date__lte=timezone.now(), **kwargs)
    
    
    class FlavorReview(models.Model):
        review = models.CharField(max_length=255)
        pub_date = models.DateTimeField()
    
        # add our custom model manager
        objects = PublishedManager()
    

    此时,如果我们想先查出所有的评论数,然后再查出已发表的评论数,可以这样做:

    >>> from reviews.models import FlavorReview
    >>> FlavorReview.objects.count()
    35
    >>> FlavorReview.objects.published().count()
    31
    

    对于替换数据模型的默认管理器,要特别小心:

    • 首先,使用数据模型继承时,抽象基类的子类会接收其父类的数据模型管理器,但是使用多表继承方式的子类却不会
    • 其次,第一个应用于数据模型类的数据模型管理器会被当作默认的管理器。这和常规的 Python 模式差别很大,从而会使返回的查询结果与预想的不同

    因此, 应该将 objects = models.Manager() 放在所有的自定义数据模型管理器之前。

    理解胖数据模型

    fat models 的概念是:不要将数据相关的代码分散在视图和模板中,应将这些逻辑封装在数据模型的方法、类方法、属性甚至管理器的方法中。这样,所有的视图和任务都可以重用这些代码。

    这种方式的缺点:会使数据模型的代码量越来越多,从而难以维护和理解。

    因此,当数据模型的代码量变得很大很复杂后,应该将相关重复的代码独立出来放在 Model BehaviorHelper Functions 中。

    数据模型的 Behaviors,又名 Mixins

    数据模型的 Behaviors 通过使用 Mixins 来奉行组合和封装的理念。

    相关资源: 使用纵使来减少重复代码

    无状态的 Helper Functions

    将这些逻辑放在工具函数集中,使其得以分隔开,从而使得测试更加容易。缺点是:因这些函数是无状态的,需要传递所有的参数。

    参考文献: Two Scoops of Django: Best Practices for Django 1.8

    相关文章

      网友评论

          本文标题:Two Scoops Django 推荐的数据模型最佳实践

          本文链接:https://www.haomeiwen.com/subject/wgxayhtx.html