24

我们正在尝试为我们正在创建的资产系统提出一个编号系统,在办公室对此话题进行了一些激烈的讨论,所以我决定询问 SO 的专家。

考虑下面的数据库设计会是更好的选择。

替代文字

示例 1:使用自动代理键。

=================   ==================
Road_Number(PK)     Segment_Number(PK)
=================   ==================
 1                   1

示例 2:使用程序生成的 PK

=================   ==================
Road_Number(PK)     Segment_Number(PK)
=================   ==================
 "RD00000001WCK"     "00000001.1"

(这00000001.1意味着它是道路的第一段。每次添加新路段时都会增加,例如00000001.2

示例 3:两者都使用(添加新列)

=======================    ==========================
ID(PK) Road_Number(UK)     ID(PK)  Segment_Number(UK)
=======================    ==========================
 1     "RD00000001WCK"       1       "00000001.1"

只是一点背景信息,我们将在报告和其他文档中使用Road Numberand Segment Number,因此它们必须是唯一的

我一直喜欢保持简单,所以我更喜欢示例 1,但我一直在读到你不应该在报告/文档中公开你的主键。所以现在我更多地按照示例 3 的思路进行思考。

我也倾向于示例 3,因为如果我们决定更改资产编号的生成方式,则不必对主键进行级联更新。

你认为我们应该怎么做?

谢谢。

编辑:感谢大家的精彩回答,对我帮助很大。

4

13 回答 13

65

这实际上是关于代理(也称为技术或合成)与自然主键的讨论,该主题已被广泛讨论。我在AppDevelopers 所犯的数据库开发错误中对此进行了介绍。

自然键是基于(表面上)唯一的外部有意义数据的键。常见的例子是产品代码、两个字母的州代码(美国)、社会安全号码等。代理或技术主键是那些在系统之外绝对没有意义的主键。它们纯粹是为了识别实体而发明的,通常是自动递增的字段(SQL Server、MySQL 等)或序列(最著名的是 Oracle)。

在我看来,您应该始终 使用代理键。这个问题出现在这些问题中:

自动编号字段是要走的路。如果您的密钥在您的数据库之外有意义(例如资产编号),那么这些密钥很可能会发生变化,并且更改密钥是有问题的。只需将这些内容的索引用于相关表即可。

于 2009-04-05T01:51:37.047 回答
7

我个人会说保持简单并使用自动递增的主键。如果您在程序中的显示方面需要更多“可读”的东西,那么可能是您的其他想法之一,但我认为这只是给主键字段增加了不必要的复杂性。

于 2009-04-01T22:57:10.867 回答
7

我也非常强烈地支持“不要将主键用作有意义的数据”阵营。每次我违反该政策时,都会以眼泪收场。有意义的数据迟早需要更改,如果这意味着您必须更改主键,那可能会很痛苦。主键可能会在外键约束中使用,您可能会花费很长时间尝试将其全部整理出来,只是为了进行简单的数据更改。

在我创建的每个表中,我总是使用 GUID/UUID 作为我的主键,但这只是个人喜好序列等也很好。

于 2009-04-01T23:31:51.080 回答
4

除非...

  • 价值永远不会改变是 100% 完全不可能的

  • 没有两个人会合理
    地争论应该
    为特定行使用哪个值。

使用选项一并格式化应用程序中的值,使其在显示时看起来像选项二或三。

于 2009-04-02T00:10:24.620 回答
3

我认为这里要记住的重要一点是,数据库/设计中的每个表都可能有多个键。这些是候选键请参阅 wikipedia entry for Candidate Keys

根据定义,所有候选键都是平等的。它们都是相关表的唯一标识符。

然后,您的工作是从候选键池中选择最佳候选键作为主键。其他表将使用主键来建立关系约束,但您可以继续使用候选键来查询表。

因为主键被其他结构引用,因此在连接操作中使用,主键选择的标准对我来说归结为以下(按重要性排序):

  • 不可变/稳定- 主键值不应更改。如果他们这样做,您将面临引入更新异常的风险
  • 不为空- 大多数 DBMS 平台要求主键属性不为空
  • 简单- 用于物理存储和性能的简单数据类型和值。整数值在这里工作得很好,这是大多数代理/自动生成键的选择数据类型

一旦您确定了候选键,就可以使用上述标准来选择主键。如果没有符合标准的“自然”候选键,则可以创建并使用符合标准的代理键,如其他答案中所述。

于 2009-04-10T14:55:47.357 回答
1

遵循不使用政策。

您可能会遇到的一些问题:

您需要从多个主机生成密钥。

有人会想要保留连续的号码一起使用。

人们希望它有多有意义?战争为此而战,而您已经处于第一次小规模冲突中。“它已经很有意义了,如果我们再添加两个数字,我们就可以......”即您正在建立一种(应该)可扩展的设计风格。

如果您将两者连接起来,那么您正在执行类型转换,这可能会弄乱您的查询优化器。

您需要重新分类道路,并重新定义它们的边界(即移动道路),这意味着更改主键并可能丢失链接。

所有这些都有解决方法,但这是一种解决方法激增并失控的问题。只需要一对夫妇就可以超越“简单”。

于 2009-04-01T23:09:53.783 回答
1

如前所述,无论您的平台上的最佳数据类型是什么,都将您的内部主键保留为键。

但是,您确实需要解决编号系统参数,因为这实际上是业务需求,也许我们将其称为资产的识别系统。

如果只有一个标识符,则将其作为一列添加到主表中。如果可能有很多识别系统(资产通常有很多),您将需要另外两个表

    标识符类型表 Identifier-cross-ref 表
      type-id ------------> type-id(唯一
      类型名称标识符字符串键)
                                        内部标识


这样,需要访问资产的不同人员可以以自己的方式进行识别。例如,服务器团队将识别与网络团队不同的服务器,并再次与项目管理、帐户等不同。

此外,您还可以参加每个人都互相争论的所有会议。

于 2009-04-05T10:50:33.083 回答
0

要记住的另一件事是,如果您将大量数据导入此系统,您可能会发现诸如此类的事情Road_Number并不像您想象的那么独特,并且可能存在解决问题的操作障碍(重新粉刷路标, ETC。) 。

于 2009-04-01T23:28:32.070 回答
0

虽然自然密钥对业务用户可能具有重要意义,但如果您不同意这些密钥是神圣的并且不应更改,那么您很可能会在维护“产品代码具有进行更改以适应公司收购的新产品线。” 您需要保护数据的 RI,而整数作为具有自动增量的主键是最好的方法。索引和遍历整数时的性能也比 char 列更好。

虽然不适合作为主键,但自然键非常适合用户使用,您可以通过索引强制执行唯一性。它们为数据带来了背景,使各方更容易理解。此外,在您需要重新加载数据时,自然键可以帮助验证您的查找仍然有效。

于 2009-04-10T10:16:41.960 回答
0

我会使用代理键,但如果这样可以改善您的报告,您可能希望有一个计算列将代理键“格式化”为更“可读”的值。计算的列可以从代理键生成示例 2,例如用于显示目的。

我认为代理键路由是要走的路,我为此做的唯一例外是连接表,其中主键可以由外键引用组成。即使在这些情况下,我发现拥有代理主键比没有更有用。

于 2009-04-10T13:42:26.457 回答
0

我怀疑您真的应该使用选项#3,正如这里许多人已经说过的那样。即使有足够的业务密钥,代理 PK(整数或 GUID)也是一种很好的做法。代理人将减少维护方面的麻烦(正如您自己已经指出的那样)。

话虽如此,您可能需要考虑的是您的数据库是否是:

  1. 专注于数据维护和事务处理(即创建/更新/删除操作)
  2. 面向分析和报告(即查询)

换句话说,用户是否关心维护活动数据或查询大部分静态数据以找到答案?

如果您非常专注于构建一个分析和报告数据库(例如数据仓库/集市),该数据库暴露给对业务词汇有很好掌握的技术业务用户(例如报表设计人员),那么您可能需要考虑使用自然基于有意义的业务价值的密钥。它们通过消除对复杂连接的需求来帮助降低查询复杂性,并帮助用户专注于他们的任务,而不是与数据库结构抗争。

否则,您可能专注于必须在某种程度上涵盖所有基础的完整 CRUD 数据库 - 这是绝大多数情况。在这种情况下,请使用您的选项#3。将来您始终可以针对可查询性进行优化,但您将很难为可维护性进行改造。

于 2009-04-10T17:36:36.567 回答
0

我希望你会同意我的观点,即每个设计元素都应该有一个单一的目的。

问题是你认为PK的目的是什么?如果要识别表中的唯一记录,那么代理键就没有太多麻烦了。这是简单而直接的。

就选项 3 中的新列而言,您应该检查是否可以计算这些列(最好是在模型层中进行计算,以便它们可以比在 RDBMS 中进行的计算更容易更改)而不会造成太多的性能损失其他元素。例如,您可以将路段号和道路号存储在相应的表中,然后使用它们生成“00000001.1”。这将允许即时更改资产编号。

于 2009-04-11T21:08:14.707 回答
0

首先,选项 2 是绝对最差的选项。作为一个索引,它是一个string, 这让它变慢了。它是根据业务规则生成的——这可能会发生变化并引起相当大的头痛。

就个人而言,我总是使用单独的主键列;我总是使用 GUID。由于硬盘空间的原因,一些开发人员更喜欢简单的 INT 而不是 GUID。但是,如果出现需要合并两个数据库的情况,GUID 几乎不会发生冲突(而 INT 肯定会发生冲突)。

用户永远不应该看到主键。让它对用户可读不应该是一个问题。主键应该用于与外键链接。这是他们的目的。该值应该是机器可读的,并且一旦创建,就永远不会改变。

于 2009-04-12T00:59:58.693 回答