【问题标题】:MySQL table - designing efficient tableMySQL 表 - 设计高效的表
【发布时间】:2012-11-15 23:17:20
【问题描述】:

我正在设计一个数据库表,它将保存用户最喜欢的食物列表。 我使用以下架构创建了最喜欢的表

id, user_id, food_id

user_id 和 food_id 将是链接到另一个表的外键。

我只是想知道这是否有效且可扩展,因为如果用户有多个最喜欢的东西,那么它将需要多行数据。

即用户有 5 个最喜欢的食物,那么它将包含五行来保存该用户的列表。

这样有效吗?和可扩展?优化此架构的最佳方法是什么?

提前谢谢!!!

【问题讨论】:

  • 如果回答对您有帮助,您应该接受。这对其他人也有帮助。

标签: mysql sql database database-design


【解决方案1】:

tldr; 这称为"join table",是在关系数据库中对 MM 关系建模的正确且可扩展方法。(根据所使用的约束,它还可以建模 1-M “无 NULL FK”模式中的 /1-1 关系。)

但是,我认为 id 列应在此处省略,以便该表 user_id, food_id。在这种情况下,PK 将是 (user_id, food_id)

与有时会争论 surrogate (aka auto-increment) PKs 的其他表不同,代理 PK 通常只会在连接表中增加混乱,因为它具有非常自然的复合 PK。

虽然在这种情况下 PK 本身是复合的,但每个“连接”表仅与 PK 的一部分相关联。根据执行的查询,在food_id(food_id, user_id 上添加覆盖索引也可能是有益的。

【讨论】:

  • 感谢您的快速响应。另一方面,我正在考虑将 food_id 设置为数组。因此,每个用户最喜欢的列表都将保存在一个数组中,而不是添加多行。这样更有效率吗?
  • @mamonluk 不要那样做:这是一种被称为“非规范化数据”的糟糕设计——它消除了参照完整性约束的能力,并且会使 许多 操作变慢和/或更难。出于某种原因,它们被称为关系数据库,并且在过去的 40 多年中一直在没有这种“优化”的情况下使用。如果没有性能测试用例,则没有性能问题
【解决方案2】:

消除代理键:除非您有代理键idspecific reason,否则将其从表中排除。

微调索引:此时,您只有一个复合主键,它是两个外键的组合。 PK 字段应该按什么顺序排列?

  • 如果您的应用程序主要执行诸如“给定用户,给我食物”之类的查询,那么 PK 应该是{user_id, food_id}
  • 如果主要查询是“for given food, give me users”,那么 PK 应该是 {food_id, user_id}
  • 如果两个查询“方向”都相同,则添加一个与 PK 具有相同字段但方向相反的唯一索引。因此,您将在 {user_id, food_id} 上进行 PK,在 {food_id, user_id} 上进行索引。

注意InnoDB tables are clustered,它消除了(在这种情况下是“不必要的”)表堆。然而,上面讨论的二级索引不会导致双重查找(因为它完全covers 查询),也没有隐藏的 PK 字段开销(因为它索引 same 字段作为 PK,只是相反的顺序)。

有关设计联结表的更多信息,请查看this post

【讨论】:

  • 嗨,如果我将 food_id 作为数组,那么我可以将用户最喜欢的列表存储在一行中。这有效率吗?谢谢
  • @mamonluk 您将违反atomicity 的原则,因此违反了 1NF,并且有非常好的理由不这样做,最重要的是参照完整性(即外键)。如果您的 DBMS 本身不支持数组(我认为 MySQL 不支持),那么数据库也无法强制执行域的完整性(即“类型安全”)。如果您只需要在一个方向查询(并且很少修改),您实际上可能会在性能方面侥幸成功,但如果您需要朝相反的方向前进,则会失去灵活性。
  • 谢谢。很好解释。我有一个完全不同的问题。我不确定在这里问是否可以。如果我想设计保存用户事务的数据库怎么办。即用户购买的物品清单。我将如何存储它们。我假设我会有一个名为 transaction 的表,它使用事务 id 保存每个用户的项目?
  • @mamonluk 问题在于价格(以及可能的其他信息,例如送货地址)应该按照购买时的状态进行记忆,即使它们发生了变化稍后。有几种方法可以处理这个问题,一些想法看起来像 herehere
【解决方案3】:

在我看来,您可以通过以下方式优化您的表格:

  1. 作为具有 2 个前键的关系表,您不必使用“id”字段。
  2. 对您的表使用“innodb”引擎
  3. 将关系表命名为“user_2_food”,这样会更清楚。
  4. 尽量使用尽可能小的数据类型,即“smallint”比“int”好,不要忘记“UNSIGNED”属性。

【讨论】:

    【解决方案4】:

    创建以下三个表将产生高效的设计。

    users : userId, username, userdesc
    foods : foodId, foodname, fooddesc
    userfoodmapping : ufid, userid, foodid, rowstate
    

    rowstate的意义在于,如果用户以后不喜欢那个食物,它的状态会变成-1

    【讨论】:

    • 我会避免在 userfoodmapping 表中使用 ufid,除非该表的列数足够大,并且该表在许多表中被引用,这不是通常的情况。
    【解决方案5】:

    我的选项中有 2 个选项:

    1. 去掉 ID 字段,但在这种情况下,将其他两个键(组合)设为主键

    2. 保留您的 ID 键作为表的主键。

    无论哪种情况,我认为这是一种正确的方法。一旦遇到效率低下的问题,您可能会考虑如何加载表的一部分或任何其他技术。现在就可以了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-21
      相关资源
      最近更新 更多