规范化或不规范化user_id

时间:2009-12-24 10:20:07

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

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

我应该对user_id进行非规范化并将其包含在每个表中,以提高性能吗?


以下是一个例子:

  • 地址属于用户,并且具有user_id
  • 信封属于用户,并且具有user_id
  • AddressesEnvelopes连接地址和信封,因此它有envelope_id和address_id - 它没有user_id,但可以通过信封或地址(必须属于同一个用户)来访问它。

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


这是一个不同的场景:

  • Letter属于用户,并且具有user_id
  • Recepient属于Letter,并且有一个letter_id
  • RecepientOption属于Recepient,并且具有recepient_id

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


一些注意事项:

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

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

3 个答案:

答案 0 :(得分:2)

我想指出,如果您愿意使用复合主键,则无需非规范化。 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查询时,这很好,但如果你用另一个对象查询它会损害性能(在这种情况下,像你想要的重复可能是一个更好的解决方案)

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

答案 1 :(得分:1)

只要你

a)获得可衡量的绩效改善

b)知道数据库的哪些部分是真实的标准化数据,哪些是多余的改进

没有理由不这样做!

答案 2 :(得分:1)

您确实有测量的性能问题吗? 50万行表不是很大。如果选择不是很复杂并且列上有适当的索引,那么您的选择应该是合理的。

我首先会看到是否有慢查询并尝试使用索引优化它们。如果这还不够,那么我才会考虑非规范化。

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