【问题标题】:MySQL - Should I use multi-column primary keys on every child table?MySQL - 我应该在每个子表上使用多列主键吗?
【发布时间】:2011-09-08 20:46:39
【问题描述】:

设置:

当我在 stackexchange 上发现这篇很棒的文章时,我试图了解识别关系和非识别关系之间的区别。 What's the difference between identifying and non-identifying relationships?

在阅读了几篇 cmets 之后,我想到了另一个关于我一直遇到的问题的问题。


问题:

我应该在每个子表上使用多列主键吗?这样做的优点/缺点是什么?

为了更好地说明我的问题,我在下面创建了一个示例。我还包括了导致我提出这个问题的 cmets。


示例:

在我的情况下,我知道building_id,我需要得到bed.data

#1 - 我当前的数据库结构:

TABLE { FIELDS }
-----------------------------------------------------------------------
building { id, data } 
floor { id, building_id, data }
room {id, floor_id, data }
bed {id, room_id, data }

这种类型的表结构需要我使用一些连接来获取我需要的数据。没什么大不了的,但有点痛苦,因为我经常遇到这种情况。

#2 - 我对 Bill Karwin 建议的数据库结构的解释(参见下面的文章 cmets):

TABLE { FIELDS }
-----------------------------------------------------------------------
building { id, data } 
floor { id, building_id, data }
room {id, building_id, floor_id, data }
bed {id, building_id, floor_id, room_id, data }

在我的情况下,这种表结构似乎消除了对连接的需要。那么这种表结构有什么缺点呢?我真的很喜欢不做这么多连接语句的想法。


来自文章的评论:

What's the difference between identifying and non-identifying relationships?

@hobodave:这是“约定优于配置”的论点。一些思想流派是,每个表都应该为一个名为 id 的单列伪键定义其主键,该伪键会自动生成其值。 Rails 等应用程序框架已将其作为默认设置进行普及。他们将自然键和多列键视为与其约定不同,在使用“遗留”数据库时需要。许多其他框架都效仿了这一做法。 – 比尔·卡尔文 2010 年 3 月 10 日 23:06

似乎“正确”构建识别关系会导致令人讨厌的巨大主键。例如楼有楼有房有床。床的 PK 将是 (bed_id, floor_id, room_id, building_id)。奇怪的是,我从未在实践中看到过这种情况,也没有听说过将其作为一种做任何事情的方法。那是PK中的大量冗余数据。 – hobodave 2010 年 3 月 10 日 23:34

@hobodave:我见过更大的多列主键。但我同意你的观点。考虑多列主键传达更多信息;您可以查询 Beds 表以获取特定建筑物中的所有床位,而无需进行任何连接。 – 比尔·卡尔文 2010 年 3 月 11 日 1:00

【问题讨论】:

  • 有一点需要考虑。建筑物 ID 字段是否可以更改? (在这种情况下,您的更新代码需要使用 building_id 字段更新所有表)。如果 Building ID 是增量字段或标识字段,则不应更改,因此唯一的缺点是保留额外字段会使表变大。
  • 我尝试保持两个 FK 列的名称相同,因此表构建将是:building_id, data,而地板表将是:floor_id, building_id, data。当您使用由许多人长期开发的大型系统时,在您加入相同名称的情况下读取和编写查询会更容易。更不用说您的结果集不会包含您每次都必须别名的多个 id 列。
  • 也许我们可以让@Bill Karwin 加入我们并指出我们是否误解了什么。
  • 那太好了。有没有办法做到这一点?

标签: mysql database database-design database-schema mysql-management


【解决方案1】:

这个数据是标准化的

TABLE { FIELDS }
-----------------------------------------------------------------------
building { id, data } 
floor { id, building_id, data }
room {id, floor_id, data }
bed {id, room_id, data }

这张桌子不是(坏主意)

TABLE { FIELDS }
-----------------------------------------------------------------------
building { id, data } 
floor { id, building_id, data }
room {id, building_id, floor_id, data }
bed {id, building_id, floor_id, room_id, data }
  1. 在第一个(好的)表中没有不需要的重复数据。
  2. 在第一个表中插入会快得多。
  3. 第一个表将更容易放入内存中,从而加快查询速度。
  4. InnoDB 的优化考虑了模型 A,而不是模型 B。
  5. 后一个(坏的)表有重复的数据,如果不同步,你会一团糟。 DB A 不能不同步,因为数据只列出一次。
  6. 如果我想合并来自建筑物、楼层、房间和床的数据,我需要合并模型 A 和模型 B 中的所有四张表,您如何在这里节省时间。
  7. InnoDB 将索引数据存储在自己的文件中,如果您select 仅索引,则表本身将永远不会被访问。那么为什么要复制索引呢?无论如何,MySQL 永远不需要读取主表。
  8. InnoDB 将 PK 存储在每个二级索引中,使用复合且因此较长的 PK,您会减慢使用索引的每个选择并膨胀文件大小;一无所获。
  9. 您是否有严重的速度问题?如果没有,你是在非规范化你的表吗?
  10. 甚至不要考虑使用受这些问题影响较小的 MyISAM,它没有针对多连接数据库进行优化,不支持引用完整性或事务,并且不适合这种工作负载。
  11. 使用复合键时,您只能使用键的最右边部分,即除了使用 id+building_id+floor_id 之外,您不能在表 bed 中使用 floor_id,这意味着您可能需要使用更多比模型 A 中需要的键空间。要么,要么您需要添加一个额外的索引(这将拖动 PK 的完整副本)。

简而言之
我在 Model B 中看到绝对零好处和一大堆缺点,永远不要使用它!

【讨论】:

  • 好的,有道理。假设我们添加了一个新表:“User { id }”。像您建议的那样遵循模型 A 是否合理,但仍然在几乎每个表中都包含“user_id”以便快速访问以验证用户是否有权访问给定的“床”、“地板”或“建筑物”?我知道这是一个广泛的问题,但在我的实时系统中,我混合了模型 A 和 B。也就是说,我有一个用户表,几乎可以在每个表中找到其 ID,原因正是我所描述的。
  • @zechdc,我建议你不要去规范化,直到速度变慢并且你已经用完了选项,所以如果没有更多相反的信息,我会说stick to model A。你描述的想法听起来很浪费,如果 2 个用户可以访问同一行,你会加倍行吗?
  • 谢谢!我想你刚刚回答了我的另一个问题......你如何允许多个用户访问相同的数据?谢谢!
  • OP 的模型 #2 不正确。请参阅下面我对 Bill Karwin 的其他 SO 答案的解释。
【解决方案2】:

我认为你的#2 不太可能是 Bill Karwin 的意思。通常,“id”表示自动数字序列。我认为他更有可能是在这些方面的意思。构成主键的列在星号之间。

TABLE    { COLUMNS }
-----------------------------------------------------------------------
building { *building_id*, other columns } 
floor    { *building_id, floor_num*, other columns }
room     { *building_id, floor_num, room_num*, other columns }
bed      { *building_id, floor_num, room_num, bed_num* (?), other columns }

不过,我不确定您可能还有哪些其他列用于“床”。双胞胎、全包、皇后、国王?这可能是有道理的。如果是这样的话,那么这个表

bed      { *building_id, floor_num, room_num, bed_num*, bed_size }

远非“非规范化”。事实上,它在 5NF 中。

如果您测试这两种模式的性能,您可能会发现这个模式大部分时间都围绕着您的 #1 运行。在我运行的这批查询中,它快了大约 30 倍。

【讨论】:

  • 这是有道理的。我确实误解了他在说什么,但幸运的是我得到了我正在寻找的答案。感谢您的帮助。
  • 如果您在接受答案之前测试了标准化和性能,这可能会更有意义 - 并且对您来说是更好的实践。维基百科关于规范化和范式的文章还不错。
  • 我练习标准化已经有一段时间了,有时我的大脑会停止工作,我会问这样愚蠢的问题。但是,我从您的两个答案中收集了一些我本来不会收到的重要信息。他们都帮助我对当前项目做出了一些最终决定。我投了你的票,但我更喜欢约翰的回答,因为它回答了一个我无法表达的问题。你是对的,我确实误解了比尔的回答。感谢您清除它。
  • 这假设 *_nums 重复,这样你就可以保持小键。
  • @Johan:假设您通过建筑物、楼层、房间和床号来识别一张床,这些都在问题中给出,但它遵循酒店和汽车旅馆的工作方式。这些数字是否重复并不重要。 (如果您只有一栋只有一层、只有一个房间和一张床的建筑物,这并不重要。尽管在这种情况下您可能不需要数据库。)
【解决方案3】:

第一个表结构是规范化的经典结构。但不幸的是,这个不适用于大项目。因为如果您的表格构建包含许多数据行,例如百万取决于您使用的城市或国家,您的加入将非常缓慢。 因此,在实际项目中,使用包含所有聚合信息的非规范化表。您可以直接使用此类表,也可以使用 sphinx 等独立服务器来搜索数据。关于三个字段的主键,我认为在这种情况下这是多余的。因为

  1. 如果您使用 innodb,此键将添加到此表中的所有辅助键中。
  2. 如果您使用管理床位的界面,使用一个字段 ID 来处理特定行比使用三个字段更方便。
  3. 如果您想保证行的唯一性,您可以在这 3 个字段上使用 UNIQUE KEY。

【讨论】:

  • 奇怪,拥有 TB 大小数据库的人不会通过非规范化来摆脱连接。当然,他们的设计师知道自己在做什么并进行适当的索引。
  • @HLGEM 看来您没有参与通过分层数据搜索的大项目。一个简单的例子,你有地址对象,如城市、地区、街道、建筑物等,并为此使用 mysql(不确定这是一个最佳解决方案),并且你向用户提供建议框以快速搜索对象内部。所以尝试使用连接表来实现这一点:) 例如,我们使用所有地址对象的聚合表并将它们放入 sphinx 以便快速搜索。
猜你喜欢
  • 1970-01-01
  • 2011-12-22
  • 2010-10-24
  • 2011-11-12
  • 2023-04-07
  • 2016-06-30
  • 2016-05-10
  • 1970-01-01
相关资源
最近更新 更多