【问题标题】:To normalize or not to normalize user_ids规范化或不规范化 user_ids
【发布时间】:2010-12-29 18:54:11
【问题描述】:

在我的 Rails 应用程序中,我有各种包含用户数据的数据库表。其中一些表有很多行(在某些情况下,每个用户多达 500,000 行)并且经常被查询。每当我查询任何表的任何内容时,当前用户的 user_id 都在查询中的某个位置 - 如果表与用户有直接关系,则直接,如果它们通过其他表相关,则通过连接。

我是否应该对 user_id 进行非规范化并将其包含在每个表中,以获得更快的性能?


这是一个例子:

  • 地址属于用户,并且有一个user_id
  • 信封属于用户,并且有一个user_id
  • AddressesEnvelopes 连接了地址和信封,因此它具有信封_id 和地址_id - 它没有用户id,但可以通过信封或地址(必须属于同一用户)来获取它。李>

一个常见的昂贵查询是为特定用户选择所有 AddressesEnvelopes,我可以通过加入 Address 或 Envelope 来完成,即使我不需要这些表中的任何内容。或者我可以在此表中复制用户 ID。


这是一个不同的场景:

  • 字母属于用户,并且有一个 user_id
  • 收件人属于 Letter,并且有一个 letter_id
  • RecepientOption 属于 Recepient,并且有一个 recepient_id

在 Recepient 和 RecepientOption 中复制 user_id 是否有意义,即使我总是可以通过关联、通过 Letter 来获得它?


一些注意事项:

  • 从来没有任何对象是 用户之间共享。一个完整的 相关对象的层次结构总是 属于同一用户。
  • 对象的用户所有者永远不会改变。
  • 数据库性能很重要,因为它是一个数据密集型应用程序。有很多查询和很多表。

那么我应该在每个表中包含 user_id 以便在创建索引时使用它吗?或者那会是糟糕的设计?

【问题讨论】:

    标签: ruby-on-rails database-design normalization denormalization


    【解决方案1】:

    我想指出,如果您愿意使用复合主键,则没有必要进行非规范化。 AddressEnvelop 案例示例:

    user(
        #user_id
    )
    address(
        #user_id
    ,   #addres_num
    )
    envelope(
        #user_id
    ,   #envelope_num
    )
    address_envelope(
        #user_id
    ,   #addres_num
    ,   #envelope_num
    )
    

    (#表示主键列)

    如果可以避免的话,我不是这种设计的粉丝,但考虑到您说所有这些对象都与用户相关联这一事实,这种类型的设计将使您的数据分区相对简单(无论是逻辑上的,将用户范围放在单独的表中或物理上,使用多个数据库甚至机器)

    对这种设计有意义的另一件事是使用聚集索引(在 MySQL 中,InnoDB 表的主键是从聚集索引构建的)。如果您确保 user_id 始终是索引中的第一列,它将确保对于每个表,一个用户的所有数据都紧密地存储在磁盘上。当您总是按 user_id 查询时,这很好,但如果您按另一个对象查询,它可能会损害性能(在这种情况下,像您建议的那样重复可能是更好的解决方案)

    无论如何,在您更改设计之前,首先要确保您的架构已经优化,并且您的外键列上有正确的索引。如果性能真的很重要,您应该尝试几种解决方案并进行基准测试。

    【讨论】:

    • 谢谢,罗兰。这正是我想做的。也许 denormalize 是一个错误的词,因为我实际上并没有将用户表中的数据非规范化到其他表中,只是将 user_id 作为表中的一个键包含在可以通过另一个键获取 user_id 的表中(比如在address_envelopes 示例,您可以在其中通过地址或信封获取 user_id)。用户在表/机器之间进行聚集索引和分区数据听起来是个好主意!
    【解决方案2】:

    只要你

    a) 获得可衡量的性能提升

    b) 知道数据库的哪些部分是真正的规范化数据,哪些是冗余改进

    没有理由不这样做!

    【讨论】:

    • 酷!很高兴听到它没有什么明显的错误。谢谢。
    【解决方案3】:

    你真的有测量性能问题吗? 500 000 行不是很大的表。如果它们不是很复杂并且您的列上有适当的索引,那么您的选择应该是合理的快速。

    我会先看看是否有慢查询,然后尝试使用索引来优化它们。如果这还不够,那么我会研究非规范化。

    如果您无法通过其他方式获得所需的性能,您建议的非规范化似乎是合理的。只需确保使非规范化字段保持最新即可。

    【讨论】:

    • 我同意这一点。 50万不算多。你知道事情应该多快,以及你想提高多少性能?
    • 请注意,每个用户有 500,000 条记录,而不是总共 500,000 条记录。总用户数应该能够增长到至少 100,000 而不会出现扩展问题,尽管并发用户可能会小得多(不到总数的 1%)。因此,如果有 10 万活跃用户和每个用户 50 万条记录,那就是 50,000,000,000 条记录。这就是为什么我认为按 user_id 进行分区最终可能会有所帮助。目前还没有测量到的性能问题。我只是想知道假设用 user_id 对每个表进行分区并将其用作每个复合索引中的第一项是否是一个很好的举措。
    猜你喜欢
    • 1970-01-01
    • 2013-08-21
    • 2012-05-22
    • 2013-01-18
    • 2016-05-13
    • 2018-06-06
    • 2021-06-13
    • 2013-06-29
    相关资源
    最近更新 更多