Sql server Django模型选择:IntegerField vs CharField

Sql server Django模型选择:IntegerField vs CharField,sql-server,django,indexing,django-models,Sql Server,Django,Indexing,Django Models,TL;DR:我有一个包含数百万个实例的表,我想知道应该如何为它编制索引 我有一个Django项目,它使用SQL Server作为数据库后端 在生产环境中拥有大约1400万个实例的模型后,我意识到我遇到了性能问题: class UserEvent(models.Model) A_EVENT = 'A' B_EVENT = 'B' types = ( (A_EVENT, 'Event A'), (B_EVENT, 'Event B')

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

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

在生产环境中拥有大约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 = (...)
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
我在这个字段中使用了很多查询,而且效率很低,因为该字段没有索引。仅使用此字段筛选模型几乎需要7秒,而使用索引外键查询不会带来性能问题:

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 = (...)
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的查询效率更高。”

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

*我意识到第三个选项可能存在,因为,但是因为我的值有[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(我不希望它是Boolean字段,因为可以向该字段添加更多选项),然后为该字段编制索引

    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 优点:简单

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

方案B 优点:基于整数的索引比基于整数的索引更有效

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

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

  • 总而言之,这里真正的问题是:

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

    我倾向于尝试选项A,并检查性能改进是否充分


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

    • 在Django选项中使用CharFields是否比使用Boolean/Integer/SmallIntegerField更好
    之所以出现这种情况,是因为在定义项目模型时,我的灵感来自:


    既然它们可以使用整数,为什么还要使用字符,因为它只是一个永远不应该显示的值表示形式?计数查询的速度。

    UserEvent.objects.filter(event_type=UserEvent.B_EVENT).count()
    # elapsed time: 0:00:06.921287
    
    不幸的是,当表有大量条目时,这种性质的查询在数据库中总是很慢

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

    分区

    通过对数据进行分区,可以提高涉及事件类型的查询的总体性能。因为当前索引的基数很差,所以规划者最好进行一次完整的表扫描。如果数据已分区,则只需扫描该特定分区

    Char或Smallint


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

    您遇到性能问题的实际查询将是一个有用的补充。@e4c5我已在问题中添加了查询示例:)我认为它们使用字符是因为可读性。您仍然可以在额外逻辑中使用值表示。如果您有一个测试服务器/db,我建议您尝试一下,看看通过迁移生产是否有任何好处。我认为在使用的存储空间方面不会有太大的差异。我已经更新了我的答案。谢谢你的输入。实际上,我用count查询来证明一个例子。最常见的情况是沿着布尔字段按事件类型进行筛选,以获得实例列表。您和我们已经陷入了一场白费力气的追逐!无法从计数查询判断性能。感谢您的输入。实际上,我用count查询来证明一个例子。最常见的情况是沿布尔字段按事件类型进行筛选,以获得实例列表。我的想法是,索引性能与存储空间无关,而是与数据类型有关。即使一个小的Int占用更多的空间,它不是比Char更有效吗?同时,由于99%的数据没有被应用程序使用,并且我们能够将其存储在该数据库之外,因此我们决定停止存储所有信息,只存储应用程序所需的数据。好的,你是对的,这是一个误导性的示例。对此我深表歉意。但性能问题是实实在在的。我认为关于指数表现和影响的问题是值得的。