【问题标题】:Django Model Choices: IntegerField vs CharFieldDjango 模型选择:IntegerField 与 CharField
【发布时间】:2016-08-10 17:08:07
【问题描述】:

TL;DR:我有一个包含数百万个实例的表,我想知道如何索引它。

我有一个使用 SQL Server 作为数据库后端的 Django 项目。

在生产环境中拥有一个包含大约 1400 万个实例的模型后,我意识到我遇到了性能问题:

class UserEvent(models.Model)

    A_EVENT = 'A'
    B_EVENT = 'B'

    types = (
        (A_EVENT, 'Event A'),
        (B_EVENT, 'Event B')
    )

    event_type = models.CharField(max_length=1, choices=types)

    contract = models.ForeignKey(Contract)

    # field_x = (...)
    # field_y = (...)

我使用了很多基于该字段的查询,但效率非常低,因为该字段没有被索引。仅使用此字段过滤模型大约需要 7 秒,而通过索引外键查询不会带来性能问题:

UserEvent.objects.filter(event_type=UserEvent.B_EVENT).count()
# elapsed time: 0:00:06.921287

UserEvent.objects.filter(contract_id=62).count()
# elapsed time: 0:00:00.344261

当我意识到这一点时,我也对自己提出了一个问题:“这个字段不应该是 SmallIntegerField 吗?由于我只有一小部分选择,并且基于整数字段的查询比基于 text/varchar 的查询效率更高查询。”

所以,据我了解,我有两个选择*:

*我意识到可能存在第三个选项,因为 indexing fields with low cardinality may not cause severe improvements,但由于我的值具有 [1%-99%] 分布(我正在寻找 1% 部分),索引该字段似乎是一个有效的选项。

  • A) 只需索引该字段,并将其保留为 CharField。

    A_EVENT = 'A'
    B_EVENT = 'B'
    
    types = (
        (A_EVENT, 'Event A'),
        (B_EVENT, 'Event B')
    )
    
    event_type = models.CharField(max_length=1, choices=types, db_index=True)
    
  • B) 执行迁移以在 SmallIntegerField 中转换此字段(我不希望它成为 BooleanField,因为可能会向该字段添加更多选项),然后索引字段。

    A_EVENT = 1
    B_EVENT = 2
    
    types = (
        (A_EVENT, 'Event A'),
        (B_EVENT, 'Event B')
    )
    
    event_type = models.SmallIntegerField(choices=types, db_index=True)
    

选项 A

优点:简单

缺点:基于CharField 的索引效率低于基于整数的索引

选项 B

优点:基于整数的索引比基于CharField 的索引更有效

缺点:我必须执行复杂的操作:

  1. 架构迁移以创建新的SmallIntegerField
  2. 数据迁移将数百万个实例从旧字段复制(和转换)到新字段。
  3. 更新项目代码以使用新字段或执行另一个架构迁移以将新字段重命名为以前的字段。
  4. 删除旧字段。

总结一下,这里真正的问题是:

将字段迁移到 SmallIntegerField 所带来的性能提升值得冒险吗?

我倾向于尝试选项 A,并检查性能改进是否足够。


我也向 StackOverflow 提出了这个问题,因为出现了一个更通用的问题:

  • 在 Django 选项中使用 CharFields 是否比使用 Boolean/Integer/SmallIntegerField 更好?

之所以出现这种情况,是因为我在定义项目模型时受到了Django documentation code snippet的启发:

YEAR_IN_SCHOOL_CHOICES = (
     ('FR', 'Freshman'),
     ('SO', 'Sophomore'),
     ('JR', 'Junior'),
     ('SR', 'Senior'),
)

year_in_school = models.CharField(max_length=2,
                                  choices=YEAR_IN_SCHOOL_CHOICES,
                                  default=FRESHMAN)

为什么在可以使用整数的情况下使用字符,因为它只是一种不应该显示的值表示形式?

【问题讨论】:

  • 您遇到性能问题的实际查询将是一个有用的补充。
  • @e4c5 我在问题中添加了查询示例:)
  • 我会说他们使用字符是因为可读性。您仍然可以在额外的逻辑中使用值表示。如果您有一个测试服务器/数据库,我建议您尝试一下,看看迁移生产是否有任何好处。我认为在使用的存储空间方面不会有太大差异。
  • 我已经更新了我的答案。

标签: sql-server django indexing django-models


【解决方案1】:

计数查询速度。

UserEvent.objects.filter(event_type=UserEvent.B_EVENT).count()
# elapsed time: 0:00:06.921287

不幸的是,当表中有大量条目时,这种性质的查询在数据库中总是很慢。

Mysql 通过查看索引provided the indexed columns are numeric 来优化计数查询。因此,如果您使用的是 mysql,那么这是使用 SmallIntegeField 而不是 Charfield 的一个很好的理由,但显然您不是。您的里程因其他数据库而异。我不是 SQL 服务器方面的专家,但我的理解是它在 COUNT(*) 查询上是 particularly poor at using indexes

分区

您可以通过对数据进行分区来提高涉及 event_type 的查询的整体性能。因为当前索引的基数很差,所以规划器通常最好进行全表扫描。如果数据已分区,则只需扫描该特定分区。

Char 或 Smallint

char(2) 和 int 哪个占用空间大?答案是这取决于你的字符集。如果字符集每个字符只需要一个字节,则小整数和 char(2) 将占用相同数量的空间。由于该字段的基数非常低,因此在这种情况下使用 char 或 smallint 不会产生任何显着差异。

【讨论】:

  • 感谢您的意见。实际上,我使用计数查询来证明一个例子。最常见的情况是沿布尔字段按 event_type 过滤以获取实例列表。
  • 你带领自己和我们进行了一场大戏!无法从计数查询中判断性能。
  • 感谢您的意见。实际上,我使用计数查询来证明一个例子。最常见的情况是沿布尔字段按 event_type 过滤以获取实例列表。我的想法是索引性能与存储空间无关,而是与数据类型有关。即使 Small Int 占用更多空间,难道不比 Char 更高效吗?同时,由于 99% 的数据没有被应用程序使用,而且我们能够将其存储在这个数据库之外,我们决定停止存储所有信息,而只存储应用程序所需的数据。
  • 好吧,你是对的,这是一个误导性的例子。我为此道歉。但性能问题是真实存在的。而且我认为关于索引性能和影响的问题是值得的。
猜你喜欢
  • 1970-01-01
  • 2011-12-24
  • 1970-01-01
  • 2019-05-01
  • 2016-09-05
  • 2020-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多