278

在设计表格时,我养成了一个习惯,即拥有一个唯一的列并且我将其作为主键。这可以根据要求通过三种方式实现:

  1. 自动递增的标识整数列。
  2. 唯一标识符 (GUID)
  3. 可用作行标识符列的短字符 (x) 或整数(或其他相对较小的数字类型)列

数字 3 将用于相当小的查找,主要是读取可能具有唯一静态长度字符串代码或数值(例如年份或其他数字)的表。

在大多数情况下,所有其他表都将具有自动递增的整数或唯一标识符主键。

问题:-)

我最近开始使用没有一致行标识符的数据库,并且主键当前聚集在各个列中。一些例子:

  • 日期时间/字符
  • 日期时间/整数
  • 日期时间/varchar
  • 字符/nvarchar/nvarchar

这有一个有效的案例吗?我总是会为这些情况定义一个身份或唯一标识符列。

此外,还有很多表根本没有主键。有什么正当理由(如果有的话)?

我试图理解为什么桌子被设计成原来的样子,这对我来说似乎是一团糟,但也许有很好的理由。

第三个问题可以帮助我破译答案:在使用多列组成复合主键的情况下,这种方法与代理/人工键相比是否有特定优势?我主要考虑性能、维护、管理等方面的问题?

4

21 回答 21

282

我遵循一些规则:

  1. 主键应尽可能小。首选数字类型,因为数字类型以比字符格式更紧凑的格式存储。这是因为大多数主键将是另一个表中的外键以及用于多个索引。您的键越小,索引越小,您将使用的缓存中的页面就越少。
  2. 主键不应该改变。更新主键应该永远是不可能的。这是因为它最有可能在多个索引中使用并用作外键。更新单个主键可能会导致更改的连锁反应。
  3. 不要使用“你的问题主键”作为你的逻辑模型主键。例如护照号码、社会安全号码或员工合同号码,因为这些“自然密钥”在现实世界中可能会发生变化。确保在必要时为这些添加 UNIQUE 约束以强制保持一致性。

关于代理与自然键,我参考了上面的规则。如果自然键很小并且永远不会更改,则可以将其用作主键。如果自然键很大或可能会更改,我会使用代理键。如果没有主键,我仍然会创建代理键,因为经验表明您将始终将表添加到您的架构中,并希望您将主键放在适当的位置。

于 2008-12-03T19:25:46.970 回答
98

自然与人工钥匙是数据库社区之间的一种宗教辩论 - 请参阅本文及其链接到的其他文章。我既不赞成总是拥有人造钥匙,也不赞成永远拥有它们。我会根据具体情况做出决定,例如:

  • 美国各州:我会选择 state_code(德克萨斯州等的'TX'),而不是德克萨斯州的 state_id=1
  • 员工:我通常会创建一个人工的employee_id,因为很难找到其他有效的方法。SSN 或同等学历可能有效,但可能会出现诸如尚未提供其 SSN 的新加入者之类的问题。
  • 员工工资历史:(employee_id,start_date)。我不会创建人工的employee_salary_history_id。它有什么用(除了“愚蠢的一致性”

无论在何处使用人工键,您都应该始终声明对自然键的唯一约束。例如,如果必须使用 state_id,但最好在 state_code 上声明一个唯一约束,否则您肯定最终会得到:

state_id    state_code   state_name
137         TX           Texas
...         ...          ...
249         TX           Texas
于 2008-12-03T16:19:27.097 回答
28

我避免使用自然键的原因很简单——人为错误。尽管通常可以使用自然唯一标识符(SSN、VIN、帐号等),但它们需要人工正确输入。如果您使用 SSN 作为主键,有人在数据输入期间转置了几个数字,并且没有立即发现错误,那么您将面临更改主键的问题。

我的主键都是由后台的数据库程序处理的,用户从不知道它们。

于 2011-07-12T20:55:41.877 回答
26

只是对经常被忽视的事情的额外评论。有时不使用单个代理键作为主键对子表有好处。假设我们的设计允许您在一个数据库中运行多个公司(可能是托管解决方案,或其他)。

假设我们有这些表和列:

Company:
  CompanyId   (primary key)

CostCenter:
  CompanyId   (primary key, foreign key to Company)
  CostCentre  (primary key)

CostElement
  CompanyId   (primary key, foreign key to Company)
  CostElement (primary key)

Invoice:
  InvoiceId    (primary key)
  CompanyId    (primary key, in foreign key to CostCentre, in foreign key to CostElement)
  CostCentre   (in foreign key to CostCentre)
  CostElement  (in foreign key to CostElement)

如果最后一位没有意义,Invoice.CompanyId则它是两个外键的一部分,一个到CostCentre表,一个到CostElement表。主键是(InvoiceIdCompanyId)。

在此模型中,不可能搞砸并引用一家公司的CostElement和另一家公司的CostCentre如果在CostElementCostCentre 表中使用单个代理键作为主键,并且在Invoice 表中没有外键关系,则可以。

搞砸的机会越少越好。

于 2008-12-05T10:38:47.063 回答
13

从各个领域制作主键没有问题,那就是Natural Key

您可以使用 Identity 列(与候选字段上的唯一索引相关联)来制作Surrogate Key

这是一个古老的讨论。在大多数情况下,我更喜欢代理键。

但是没有钥匙是没有借口的。

回复:编辑

是的,这有很多争议:D

除了它们是自然选择之外,我没有看到自然键有任何明显优势。您总是会想到Name、SocialNumber或类似的东西,而不是idPerson

代理键是自然键所具有的一些问题的答案(例如传播更改)。

当你习惯了代理人时,它似乎更干净,更易于管理。

但最后,你会发现这只是品味问题——或心态——。人们使用自然键“思考得更好”,而其他人则没有。

于 2008-12-03T16:04:11.947 回答
12

表应该始终有一个主键。如果不是,它应该是一个 AutoIncrement 字段。

有时人们会忽略主键,因为他们传输了大量数据,并且可能会减慢(取决于数据库)该过程。但是,它应该在它之后添加。

关于链接表的一些评论,这是正确的,这是一个例外,但字段应该是 FK 以保持完整性,并且在某些情况下,如果链接中的重复未经授权,这些字段也可以是主键......但要保持简单的形式,因为异常在编程中经常出现,所以应该存在主键以保持数据的完整性。

于 2008-12-03T15:33:49.340 回答
11

除了所有这些好的答案,我只想分享我刚刚读到的一篇好文章,伟大的主键辩论

仅引用几点:

开发人员在为每个表选择主键时必须应用一些规则:

  • 主键必须唯一标识每条记录。
  • 记录的主键值不能为空。
  • 创建记录时,主键值必须存在。
  • 主键必须保持稳定——您不能更改主键字段。
  • 主键必须紧凑并且包含尽可能少的属性。
  • 主键值不能更改。

自然键(倾向于)打破规则。代理键符合规则。(你最好通读那篇文章,值得你花时间!)

于 2013-06-03T01:46:43.987 回答
9

这是我在 25 年以上的开发经验后确定的经验法则。

  • 所有表都应该有一个自动递增的单列主键。
  • 将其包含在任何可更新的视图中
  • 主键在您的应用程序上下文中不应该有任何意义。这意味着它不应是 SKU、帐号或员工 ID 或任何其他对您的应用程序有意义的信息。它只是与实体关联的唯一键。

主键由数据库用于优化目的,您的应用程序不应将其用于识别特定实体或与特定实体相关的任何其他用途。

始终具有单值主键使得执行 UPSERT 非常简单。

  • 在多列索引上支持单列上的多个索引。
    例如,如果您有一个两列键,则倾向于在每列上创建一个索引而不是创建一个两列索引。如果我们在 firstname + lastname 上创建一个多列键,我们不能在不提供 firstname 的情况下对 lastname 进行索引查找。在两列上都有索引允许优化器在任一列或两列上执行索引查找,而不管它们在 WHERE 子句中的表达方式。

  • 如果您的表很大,请探索根据最突出的搜索条件将表划分为段。

  • 如果您的表中包含大量 Id 字段,请考虑将除主键之外的所有内容删除到具有 id (PK)、org_id(原始表的 FK)和 id_type 列的单个表中。为新表上的所有列创建索引并将其与原始表相关联。通过这种方式,您现在可以仅使用单个索引执行任意数量的 id 的索引查找。

于 2018-03-05T19:19:39.743 回答
6

主键有什么特别之处?

架构中的表的目的是什么?表的键的用途是什么?主键有什么特别之处?围绕主键的讨论似乎忽略了主键是表的一部分,而表是模式的一部分。什么最适合表和表关系应该驱动所使用的键。

表格(和表格关系)包含有关您希望记录的信息的事实。这些事实应该是独立的、有意义的、易于理解的和不矛盾的。从设计的角度来看,从模式中添加或删除的其他表不应影响相关表。必须有存储仅与信息本身相关的数据的目的。了解表中存储的内容不应该需要进行科学研究项目。为同一目的存储的任何事实都不应存储超过一次。键是被记录的信息的全部或一部分,是唯一的,主键是专门指定的键,作为表的主要访问点(即应该选择它是为了数据的一致性和使用,而不仅仅是插入表现)。

  • 旁白:不幸的是,大多数由应用程序程序员(有时我也是)设计和开发的数据库的副作用是,最适合应用程序或应用程序框架的往往会驱动表的主键选择。这导致了整数和 GUID 键(因为它们很容易用于应用程序框架)和单片表设计(因为它们减少了表示内存中数据所需的应用程序框架对象的数量)。这些应用程序驱动的数据库设计决策在大规模使用时会导致严重的数据一致性问题。以这种方式设计的应用程序框架自然会导致一次表设计。“部分记录”是在表格中创建的,并且随着时间的推移填写数据。避免多表交互,或在应用程序运行不正常时导致数据不一致。这些设计导致数据无意义(或难以理解)、数据分布在表中(您必须查看其他表才能理解当前表)以及重复数据。

据说主键应该尽可能小。我会说密钥应该只有必要的大小。应避免向表中随机添加无意义的字段。更糟糕的是,从随机添加的无意义字段中生成键,尤其是当它破坏从另一个表到非主键的连接依赖关系时。这仅在表中没有好的候选键时才合理,但如果用于所有表,这种情况肯定是模式设计不佳的标志。

也有人说,主键永远不应该改变,因为更新主键应该永远是不可能的。但更新与删除后插入相同。按照这个逻辑,你永远不应该用一个键从表中删除一条记录,然后用第二个键添加另一条记录。添加代理主键并不会消除表中存在其他键的事实。如果其他表通过代理键依赖于该含义,则更新表的非主键可能会破坏数据的含义(例如,状态描述从“已处理”更改为“已取消”的代理键的状态表' 肯定会破坏数据)。永远不可能破坏数据意义。

话虽如此,我感谢当今企业中存在的许多设计不佳的数据库(无意义的代理键数据损坏的 1NF 庞然大物),因为这意味着对于了解正确数据库设计的人来说,有无穷无尽的工作量. 但在可悲的一面,它有时确实让我觉得自己像西西弗斯,但我敢打赌他有一个 401k (在崩溃之前)。远离博客和网站来解决重要的数据库设计问题。如果您正在设计数据库,请查找 CJ Date。您也可以为 SQL Server 参考 Celko,但前提是您必须先捏住鼻子。在 Oracle 方面,请参考 Tom Kyte。

于 2013-01-03T18:57:14.270 回答
5

自然键(如果可用)通常是最好的。因此,如果 datetime/char唯一标识该行并且这两个部分对该行有意义,那就太好了。

如果只是日期时间是有意义的,并且只是添加了字符以使其唯一,那么您不妨只使用一个标识字段。

于 2008-12-03T15:34:08.123 回答
4

我怀疑原始数据结构的设计者需要 Steven A. Lowe 的卷起报纸疗法。

顺便说一句,作为主键的GUID可能会占用性能。我不会推荐它。

于 2008-12-03T15:32:49.967 回答
4

对我来说,自然键与人工键是您想要在数据库中包含多少业务逻辑的问题。社会安全号码(SSN) 就是一个很好的例子。

“我数据库中的每个客户都将并且必须拥有一个 SSN。” Bam,完成,将其设为主键并完成它。请记住,当您的业务规则发生变化时,您就会被烧毁。

由于我在不断变化的业务规则方面的经验,我自己不喜欢自然键。但是,如果您确定它不会改变,它可能会阻止一些关键连接。

于 2008-12-03T19:26:21.757 回答
3

我也总是使用数字 ID 列。在 oracle 中,我无缘无故地使用 number(18,0) 高于 number(12,0) (或者任何是 int 而不是 long 的东西),也许我只是不想担心得到几十亿行分贝!

我还包括一个创建和修改的列(类型时间戳)用于基本跟踪,它似乎很有用。

我不介意对其他列组合设置唯一约束,但我真的很喜欢我的 id、created、modified 基线要求。

于 2008-12-03T15:35:28.633 回答
3

我寻找自然主键并尽可能使用它们。

如果找不到自然键,我更喜欢 GUID 而不是 INT++,因为 SQL Server 使用树,并且总是在树的末尾添加键是不好的。

在多对多耦合的表上,我使用外键的复合主键。

因为我很幸运能够使用 SQL Server,所以我可以使用分析器和查询分析器研究执行计划和统计数据,并非常轻松地了解我的键是如何执行的。

于 2008-12-03T19:33:51.663 回答
2

您应该使用包含多个字段的“复合”或“复合”主键。

这是一个完全可以接受的解决方案,请点击此处了解更多信息 :)

于 2008-12-03T15:35:22.097 回答
2

我总是使用自动编号或身份字段。

我为一个使用 SSN 作为主键的客户工作,然后由于 HIPAA 法规被迫更改为“MemberID”,并且在更新相关表中的外键时导致了大量问题。坚持一致的身份列标准帮助我避免了所有项目中的类似问题。

于 2008-12-03T15:53:28.113 回答
2

GUID可用作主键,但您需要创建正确类型的 GUID 以使其运行良好。

您需要生成 COMB GUID。一篇关于它和性能统计的好文章是 The Cost of GUIDs as Primary Keys

SQL中构建 COMB GUID 的一些代码也在Uniqueidentifier vs identity ( archive )中。

于 2008-12-03T19:49:18.543 回答
1

所有表都应该有一个主键。否则,您所拥有的是 HEAP - 在某些情况下,这可能是您想要的(例如,当数据通过服务代理复制到另一个数据库或表时,插入负载很重)。

对于行数较少的查找表,您可以使用 3 CHAR 代码作为主键,因为这比 INT 占用的空间更少,但性能差异可以忽略不计。除此之外,除非您有一个引用表,该表可能具有由关联表中的外键组成的复合主键,否则我将始终使用 INT。

于 2008-12-03T16:29:58.087 回答
1

如果你真的想反复阅读这场古老辩论的所有内容,请在 Stack Overflow 上搜索“自然键”。您应该返回结果页面。

于 2008-12-03T16:34:54.677 回答
0

我们做了很多连接,复合主键刚刚成为性能猪。即使您要引入第二个候选键,一个简单的 int 或 long 也可以解决许多问题,但是加入一个字段而不是三个字段要容易得多,也更容易理解。

于 2008-12-03T15:41:19.420 回答
0

我将提前说明我对自然键的偏好 - 尽可能使用它们,因为它们会使您的数据库管理生活变得更加轻松。我在我们公司建立了一个标准,所有表格都有以下列:

  • 行 ID (GUID)
  • Creator(字符串;具有当前用户名的默认值(SUSER_SNAME()在 T-SQL 中))
  • 创建(日期时间)
  • 时间戳

行 ID 在每个表上都有一个唯一的键,并且在任何情况下都是每行自动生成的(并且权限会阻止任何人对其进行编辑),并且可以合理地保证在所有表和数据库中都是唯一的。如果任何 ORM 系统需要单个 ID 密钥,则可以使用该密钥。

同时,如果可能的话,实际的 PK 是一个自然密钥。我的内部规则是这样的:

  • 人员 - 使用代理键,例如 INT。如果它是内部的,则 Active Directory 用户 GUID 是可接受的选择
  • 查找表(例如 StatusCodes) - 使用短 CHAR 代码;它比 INT 更容易记住,并且在许多情况下,纸质表格和用户也会为了简洁起见使用它(例如 Status = “E”代表“已过期”,“A”代表“已批准”,“NADIS”代表“未检测到石棉”在样品中”)
  • 链接表 - FK 的组合(例如EventId, AttendeeId

因此,理想情况下,您最终会得到一个自然、人类可读且令人难忘的 PK,以及一个对 ORM 友好的每个表一个 ID 的 GUID。

警告:我维护的数据库倾向于 100,000 条记录,而不是数百万或数十亿条记录,因此,如果您有大型系统的经验而无法接受我的建议,请随时忽略我!

于 2008-12-30T22:34:58.113 回答