为什么在连接表上不设置主键不好?

28
我正在观看一个屏幕录像,作者说在联接表上不要有主键,但没有解释原因。
在这个例子中,联接表在Rails迁移中定义了两个列,并且作者为每个列添加了索引,但没有设置主键。
为什么在这个例子中不应该有主键呢?
create_table :categories_posts, :id => false do |t|
  t.column :category_id, :integer, :null => false
  t.column :post_id, :integer, :null => false
end
add_index :categories_posts, :category_id
add_index :categories_posts, :post_id

编辑:正如我对Cletus所提到的,我可以理解自动编号字段作为联接表的主键的潜在有用性。但是在我上面列出的示例中,作者明确避免使用语法“:id => false”在“create table”语句中创建自动编号字段。通常情况下,Rails会自动向迁移中创建的表添加一个自动编号id字段,并将其作为主键。但是对于这个联接表,作者特意防止了这种情况。我不确定他为什么决定采用这种方法。


给编辑们:强调这个问题的背景可能很重要。通常情况下,没有主键是不好的做法。 - Mark Canlas
好的文章是Codd于1970年写的论文 http://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf - Matt Rogish
1
有一件事需要考虑,那篇论文是在1970年写的,当时I/O和数据存储相对来说要昂贵得多。然而,在现代,添加额外的主键列的成本几乎总是微不足道的。我很想看到有人提出一个真实世界的案例,证明额外的列会带来可衡量的问题。 - DGM
8个回答

58

一些注意事项:

  1. category_id和post_id的组合本身就是唯一的,因此额外添加ID列是多余和浪费的
  2. 在视频中,“不好设置主键”这个说法是不正确的。你仍然有一个主键——它只是由两列组成(例如CREATE TABLE foo(cid,pid,PRIMARY KEY(cid、pid))。对于习惯于到处添加ID值的人来说,这可能看起来很奇怪,但在关系理论中,这是完全正确和自然的;视频作者最好说“不好将隐式整数属性称为主键”。
  3. 额外的列是多余的,因为你无论如何都会在category_id和post_id的组合上放置唯一索引,以确保不插入重复行
  4. 最后,虽然常见的说法是将其称为“复合键”,但这也是多余的。在关系理论中,“键”实际上是用于唯一标识行的零个或多个属性集,因此说主键是category_id、post_id是可以的
  5. 在主键声明中,将最具选择性的列放在最前面。b(+/*)树的构造讨论超出了本答案的范围(有关一些低级别讨论,请参见:http://www.akadia.com/services/ora_index_selectivity.html),但在你的情况下,您可能希望将其放在post_id、category_id上,因为post_id在表中出现的次数较少,因此使索引更有用。当然,由于表非常小且索引实际上是数据行,所以这并不是非常重要。它对于表更宽时会很重要。

这个解释涵盖了我卡住的所有不清晰点。谢谢。"视频作者最好说一下,将隐式整数属性称为'ID'作为主键是'不好的'",并且感谢您明确说明:"因为您将在类别_id和帖子_id的组合上放置唯一索引以确保不插入重复行,所以拥有额外的列是多余的"。 - pez_dispenser
除了基本集合论之外,说“零个或多个”是不正确的。参见:http://www.seas.upenn.edu/~zives/03f/cis550/codd.pdf。 - Matt Rogish
在关系理论中,这是可以的(空集作为键),但在SQL中不行。 - Matt Rogish
4
第六个原因是使用合成主键会导致向表中写入数据时有轻微的性能损失。这是因为每次插入表格时都必须生成下一个键并更新另一个索引,但由于你很可能永远不会引用合成键,所以即使创建了索引也不会加快读取速度。得不偿失是一种不好的交换。 - neuronaut

4

在任何表上都没有主键是一个坏主意,这一点毋庸置疑(如果DBMS是关系型DBMS或SQL DBMS)。主键是数据库完整性的重要组成部分。

我想,如果您不介意数据库偶尔出现不准确并提供错误答案,那么您可以不用主键......但大多数人希望从他们的DBMS获得准确的答案,对于这样的人来说,主键是至关重要的。


1
这个联结表中的主键可以防止哪些不准确的数据?我不怀疑你可能是对的,但我不明白这里可能出现什么样的无效数据。你能给我举个例子吗? - pez_dispenser
我想到的是重复行。您也可以找到其他例子,特别是在自由地散布标识列并且没有唯一约束条件的情况下。如果表格除了键列之外还有其他列,实际上可以使数据库包含矛盾 - 如果您了解经典逻辑,就会知道从矛盾中争辩会导致谬误。 - Jonathan Leffler

3

一位数据库管理员会告诉你,在这种情况下,主键实际上是两个外键列的组合。由于Rails/ActiveRecord默认情况下无法很好地处理复合主键,这可能是原因。


当你说“默认情况下”时,你的意思是有一种方法让Rails表现良好,但这很复杂吗? - pez_dispenser
q-tip:看一下 has_and_belongs_to_many(以及 has_many_through)http://blog.hasmanythrough.com/2007/1/15/basic-rails-association-cardinality - 它会神奇地(有点)利用连接表。 - Matt Rogish
@po 我听说有插件可以使AR与自然键(包括复合键)一起工作,而不是替代键。我不知道它们是否有效。 - Hank Gay

3
外键的组合可以成为主键(称为复合主键)。个人而言,我更喜欢使用技术主键而非它(自动编号字段、序列等)。为什么呢?因为这样可以更容易地识别记录,如果你要删除它,则可能需要这样做。
想一想:如果你要展示所有链接的网页,有一个主键来标识记录会让事情变得更加容易。

我明白你所说的关于自动编号字段作为联接表主键的有用性。然而,在我上面列出的例子中,作者明确避免使用“:id => false”语法创建自动编号字段。通常情况下,Rails会自动为在迁移中创建的表添加自动编号id字段,并将其作为主键。但是对于这个联接表,作者特别防止了这种情况。我不确定他为什么决定采取这种方法。 - pez_dispenser

3
基本上是因为没有必要。两个外键字段的组合足以唯一标识任何行。
但这只是说明了为什么不是好主意...但为什么会是一个坏主意呢?
考虑添加自增列所带来的开销。表将占用更多的磁盘空间。更糟糕的是索引情况。使用自增列,您必须维护自增计数器和第二个索引。您需要将磁盘空间增加三倍,并在每次插入时执行三倍的工作量。而其唯一好处是DELETE命令中WHERE子句稍微更短。
另一方面,如果复合键字段是整个表,则索引可以是表。

1
所有的主键都不是自增列! - Jonathan Leffler
抱歉,我一直跟得上你的思路,直到最后一句话。我不明白你所说的:“另一方面,如果复合键字段是整个表格,则索引可以是表格。”的意思。 - pez_dispenser
q-tip: 将索引添加为整个表的索引并没有任何优势,例如如果您的主键是由表中每一列组成的复合键。这样做时,表充当索引。希望这对您有用。如果不是,请见谅。 - Lucas Wilson-Richter
2
一些数据库管理系统支持索引只表的概念。这些在所有列组成主键(例如,连接表)并且第二列不需要索引时非常有用(正如詹姆斯所说)。 - Jonathan Leffler

2
将最具有选择性的列放在首位只应在索引声明中才相关。在关键字声明中,这并不重要(因为,正如正确指出的那样,关键字是一个集合,在集合内,顺序并不重要 - 集合{a1,a2}与{a2,a1}是相同的集合)。
如果数据库管理系统产品使得关键字声明内属性的顺序有所不同,则该数据库管理系统产品没有正确区分数据库的逻辑设计(您进行关键字声明的部分)和数据库的物理设计(您进行索引声明的部分)。

我使用过的大多数DBMS产品(MySQL、Sybase ASE、SQL Server、Oracle)在PRIMARY KEY声明中隐式地按照您指定的顺序创建唯一索引。是的,这违反了逻辑/物理独立性,但这是唯一的方法(除非您创建不带主键的表,创建唯一索引,然后以某种方式“标记”主键)。 - Matt Rogish
此外,按照定义,SQL 违反了大量关系模型的规定,其中包括 :D - Matt Rogish

2
我想评论以下评论:“说零个或多个是不正确的”。
我想指出,这条评论所附加的文本根本没有包含“零个或多个”这样的文本,因此,我想评论的评论者在批评别人未曾说过的东西。
我还想评论,说“零个或多个”是不正确的也不正确。今天普遍知道的关系理论实际上要求可能存在一个没有属性的键。
但当我按下“评论”按钮时,系统对我回应说评论需要50(或类似的)声望分数。
这是一个悲哀的例子,说明世界似乎已经忘记了科学不是民主,而在科学中,真理不是由凭借“足够的声誉”的人或凭借多数决定的。

我看到重新阅读Date的数据库词典,说空PK用于将关系变量约束为单行。好的,我理解了--虽然Codd的书写并不明确,但除了那个有限的边缘情况之外,什么情况下会有人使用空键呢? - Matt Rogish

1

单一主键的优点

  • 使用单个值唯一标识一行
  • 如果需要,易于从其他地方引用关系
  • 某些工具要求您拥有单个整数值pk

单一主键的缺点

  • 使用更多磁盘空间
  • 需要3个索引而不是1个
  • 如果没有唯一约束条件,则可能会出现多个相同关系的行

注意事项

  • 如果要避免重复项,则需要定义唯一约束条件
  • 在我看来,如果您的表将非常庞大,请不要使用单个pk,否则可以为方便起见交换一些磁盘空间。是的,这很浪费,但在实际应用程序中,谁会在意磁盘上的几MB。

需要3个索引而不是1个 - 我猜你的意思是在我的上面例子中,自动编号字段上的主键加上另外两个索引(而不是在我的示例中未列出的两个附加索引)。"如果没有唯一约束条件,您可能会得到相同关系的多行记录" -> 因此,在这种情况下,PK必须引用此连接表中的两个列。换句话说,仅由自动编号字段组成的主键将无法工作。希望我理解正确。 - pez_dispenser

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接