Django模型选择:IntegerField与CharField

JCJ*_*CJS 10 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 = (...)
Run Code Online (Sandbox Code Playgroud)

我使用了很多基于此字段的查询,并且效率非常低,因为该字段未编入索引.仅使用此字段过滤模型大约需要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
Run Code Online (Sandbox Code Playgroud)

当我意识到这一点,我也做了一个问题对自己说:"不应该在该领域是一个SmallIntegerField因为我只有一小部分的选择,以及基于在整数字段查询比基于文本/ VARCHAR查询更有效率?".

所以,根据我的理解,我有两个选择*:

*我意识到可能存在第三种选择,因为具有低基数的索引字段可能不会导致严重的改进,但由于我的值具有[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)
    
    Run Code Online (Sandbox Code Playgroud)
  • 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)
    
    Run Code Online (Sandbox Code Playgroud)

选项A.

优点:简单

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

选项B.

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

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

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

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

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

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


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

  • 是否有任何情况下沿着Django选择使用CharFields比使用Boolean/Integer/SmallIntegerField更好?

出现这种情况是因为在定义项目模型时,我受到Django文档代码片段的启发:

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

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

e4c*_*4c5 5

计数查询的速度。

UserEvent.objects.filter(event_type=UserEvent.B_EVENT).count()
# elapsed time: 0:00:06.921287
Run Code Online (Sandbox Code Playgroud)

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

如果索引列是数字, Mysql 通过查看索引来优化计数查询。因此,如果您使用的是 mysql,但显然您不是,那么这是使用 SmallIntegeField 而不是 Charfield 的一个很好的理由。您的里程因其他数据库而异。我不是 SQL Server 方面的专家,但我的理解是它在 COUNT(*) 查询上使用索引特别差

分区

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

Char 或 Smallint

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