为啥没有主键的表是个坏主意?
Posted
技术标签:
【中文标题】为啥没有主键的表是个坏主意?【英文标题】:Why is it a bad idea to have a table without a primary key?为什么没有主键的表是个坏主意? 【发布时间】:2017-01-31 16:15:11 【问题描述】:我对数据建模非常陌生,根据 Microsoft 的实体框架,不允许使用没有主键的表,这显然是个坏主意。我试图弄清楚为什么这是一个坏主意,以及如何修复我的模型以使我没有这个洞。
我的当前模型中有 4 个表:User、City、HelloCity 和 RateCity。它的模型如图所示。这个想法是许多用户可以访问许多城市,一个用户只能对一个城市打分一次,但他们可以多次问候一个城市。因此,我在 HelloCity 表中没有 PK。
关于我如何更改它以符合最佳做法以及为什么这首先违反最佳做法的任何见解?
【问题讨论】:
如果你可以添加一个 PK 来做,它们很便宜,而且真的很好。我拥有的每个表都有一个 autoinc 主键。即使我不使用它。您的问题可能更适合代码审查 只需将 userid 和 cityid 都设置为您的 pk。它将有额外的好处,您不必弄乱重复项。 但是我不能为一个用户添加多个问候语......这个想法是用户可以添加任意数量的问候语...... 主键在内部用于优化查询,但真正的问题是……如果您只想删除 HelloCity 中的一条记录,您会怎么做?您没有任何东西可以唯一地标识一行。假设有一个小故障导致相同的问候被添加了两次;你会有两行包含完全相同的数据,你不能只删除一个。 正如@PaulAbbott 所说,PK 主要用于识别单行以防您想删除它,但更重要的是避免出现不一致。在您的数据库中指定哪些字段组合不能重复总是好的,因为否则您最终可能会遇到一团糟。您的表结构必须反映您的业务逻辑。如果复合 PK 涉及的字段过多,您可能需要创建一个身份字段 (PK) 并为所有这些字段创建唯一索引。 【参考方案1】:此回复主要基于意见/经验,因此我将列出一些想到的原因。请注意,这并不详尽。
以下是您应该使用主键 (PK) 的一些原因:
-
它们使您能够唯一标识表中的给定行,以确保没有重复。
RDBMS 为您强制执行此约束,因此您不必在插入之前编写额外代码来检查重复项,从而避免全表扫描,这意味着此处的性能更好。
PK 允许您创建外键 (FK),从而以 RDBMS 能够“感知”它们的方式创建表之间的关系。没有PKs/FKs,关系只存在于程序员的脑海中,被引用的表可能有一行删除了它的“PK”,而另一个带有“FK”的表仍然认为“PK”存在。这很糟糕,这就引出了下一点。
它允许 RDBMS 强制执行完整性约束。
TableA.id
是否被 TableB.table_a_id
引用?如果是TableB.table_a_id = 5
,那么您保证在TableA
中有一行id = 5
。数据完整性和一致性得到维护,这很好。
它允许 RDBMS 执行更快的搜索 b/c PK 字段已编入索引,这意味着在搜索某些内容(例如二进制搜索树结构)。
在我看来,不有一个PK可能是合法的(即RDBMS会让你),但它不是道德(即你不应该这样做)。我认为你需要有非常好的/强有力的理由来支持不在你的数据库表中使用 PK(我仍然认为它们值得商榷),但基于你目前的经验水平(即您说您是“数据建模新手”),我想说这还不足以证明缺乏 PK 的合理性。
还有更多原因,但我希望这足以让您度过难关。
就您的M:M
关系而言,您需要创建一个名为关联 表的新表,并在其中创建一个复合 PK,该 PK 是 2 个 PK 的组合其他 2 张桌子。
换句话说,如果表A
和B
之间存在M:M
关系,那么我们创建一个表C
,它与A
和B
这两个表都有1:M
关系. “以图形方式”,它看起来类似于:
+---+ 1 M +---+ M 1 +---+
| A |------| C |------| B |
+---+ +---+ +---+
与C
的表PK有点像这样:
+-----+
| C |
+-----+
| id | <-- C.id = A.id + B.id (i.e. combined/concatenated, not addition!)
+-----+
或者像这样:
+-------+
| C |
+-------+
| a_id | <--|
+-------+ +-- composite PK columns instead
| b_id | <--| of concatenation (recommended)
+-------+
【讨论】:
您好,我正在阅读您的回答,并且有一个问题。为什么某些 RDBMS 不允许使用主键?例如,Azure 突触分析不允许以强制方式使用主键。这是docs【参考方案2】:主键的两个主要原因:
-
唯一标识一条记录以供以后参考。
准确高效地加入其他表。
【讨论】:
【参考方案3】:主键本质上用唯一标识符标记行。这可以由一行中的一列或多列组成,但最常见的是只使用一列。使它有用的部分原因是当您有其他表(例如您的场景中的表)时,您可以在其他表中引用此值。由于它是唯一的,因此我可以在另一个表(例如 HelloCity
)中查看具有该唯一 ID 的列,并立即知道在 User 表中查找的位置以获取有关该列所指人员的更多信息。
例如,HelloCity
仅存储 User
和 City
的 ID。为什么?因为当您已经将City
的所有数据和User
的所有数据存储在另一个表中时,将其重新记录在另一个表中是很愚蠢的。它的美妙之处在于,假设用户出于某种原因需要更新他们的DisplayName
。为此,您只需要在用户中更改它。现在,任何引用用户的行都会立即返回新的DisplayName
;否则您将不得不使用旧的 DisplayName
查找每条记录并相应地更新它,这在较大的数据库中可能需要相当长的时间。
请注意,主键仅在该特定表中是唯一的 - 从理论上讲,您可以在 City
和 User
表中看到相同的主键值(如果您使用简单的整数作为 ID,这尤其常见) ) 但您的数据库会根据您在表之间建立的关系以及查询中的 JOIN 语句知道差异。
主键帮助的另一种方式是它们会自动在其列上生成一个索引。这会提高 WHERE 子句搜索主键列值的查询的性能。而且,由于您可能会在其他表中引用该主键,因此它也可以加快查找速度。
在您的数据模型中,我看到一些列中已经包含“Id”。在不知道您的数据集的情况下,我希望那些已经具有所有唯一值,因此在它们上放置 PK 应该没问题。如果您遇到错误,则可能存在重复。
回到你关于 HelloCity
的问题 - 实体框架在键方面有点挑剔。如果你真的想安全起见,你可以为每个条目自动生成一个唯一的 ID,并称之为好。这是有道理的,因为它是多对多的关系,这意味着任何组合都可以出现任意次数,因此理论上没有可靠的方法来区分唯一条目。如果您想在将来删除一个条目,您如何知道要引用哪一行?您可以提出您在所有字段中搜索并且问候语可能不同的论点,但是如果多次访问具有相同问候语的城市,您可能会不小心丢弃所有这些记录,而不是只删除一个。
但是,如果这是一对一的关系,您可以将 CityId
和 UserId
的组合作为主键,因为该组合应该始终是唯一的(因为您永远不会看到多行进行相同的组合)。
【讨论】:
【参考方案4】:迟到了,但我想补充一下,在某些特殊情况下,表不需要主键或任何类型的键。
以 singleton 为例。始终包含单行(或众所周知的行数)的表。 Oracle 中的dual
表就是一种情况。
形式上,单例的主键是()
:即没有列的键。不过,我不知道有任何数据库允许它。
还有其他不需要 PK 的情况,通常是日志表,这些表通常是“结束表”,因为您通常在图表的边界处绘制它们;没有其他表格提及他们(即他们没有孩子)。良好地使用索引就足以处理它们,因为就其性质而言,它们不需要强制行唯一性。
但是,要关闭,是的,关系数据库中 99.99% 的表都应该有 PK。
【讨论】:
以上是关于为啥没有主键的表是个坏主意?的主要内容,如果未能解决你的问题,请参考以下文章