Django模型选择:IntegerField与CharField

时间:2016-04-18 19:34:51

标签: sql-server django indexing django-models

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. 删除旧字段。
  5. 总结一下,真正的问题是:

    我将字段迁移到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)
    

    为什么他们在使用整数时会使用字符,因为它只是一个不应该显示的值表示?

1 个答案:

答案 0 :(得分:3)

计数查询速度。

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不会产生任何显着差异。

相关问题