又来了,老争论依然出现……

我们是否最好使用业务键作为主键,或者我们宁愿使用代理 id(即对业务关键字段具有唯一约束的 SQL Server 身份)?

请提供例子或证据来支持你的理论。

有帮助吗?

解决方案

两个都。吃蛋糕吧。

请记住,主键没有什么特别之处,只是它被标记为这样。它只不过是一个 NOT NULL UNIQUE 约束,并且一个表可以有多个。

如果您使用代理键,您仍然需要业务密钥来根据业务规则确保唯一性。

其他提示

使用代理键的几个原因:

  1. 稳定:由于业务或自然需要而更改键会对相关表产生负面影响。代理键很少(如果有的话)需要更改,因为没有与值相关的含义。

  2. 习俗:允许您拥有标准化的主键列命名约定,而不必考虑如何将具有不同名称的表连接到其主键。

  3. 速度:根据 PK 值和类型,整数的代理键可能更小,索引和搜索速度更快。

似乎还没有人说过任何支持非代理(我犹豫说“自然”)键的言论。所以这里...

A 坏处 代理键的一个特点是它们是 无意义的 (被一些人认为是一个优点,但是......)。有时,这会迫使您在查询中加入比实际需要多得多的表。比较:

select sum(t.hours)
from timesheets t
where t.dept_code = 'HR'
and t.status = 'VALID'
and t.project_code = 'MYPROJECT'
and t.task = 'BUILD';

反对:

select sum(t.hours)
from timesheets t
     join departents d on d.dept_id = t.dept_id
     join timesheet_statuses s on s.status_id = t.status_id
     join projects p on p.project_id = t.project_id
     join tasks k on k.task_id = t.task_id
where d.dept_code = 'HR'
and s.status = 'VALID'
and p.project_code = 'MYPROJECT'
and k.task_code = 'BUILD';

除非有人认真认为以下是个好主意?:

select sum(t.hours)
from timesheets t
where t.dept_id = 34394
and t.status_id = 89    
and t.project_id = 1253
and t.task_id = 77;

“但是“有人会说,“当Myproject或有效或人力资源更改的代码时会发生什么?”我的回答是:“你为什么要 需要 改变它?”这些不是“自然”键,因为某些外部机构将立法从此以后“有效”应重新编码为“良好”。只有一小部分“自然”密钥真正属于该类别 - SSN 和邮政编码是常见的示例。我肯定会在“人员”、“地址”等表中使用无意义的数字键 - 但不会在 一切, ,出于某种原因,这里的大多数人似乎都提倡这一点。

也可以看看: 我对另一个问题的回答

代理键(通常是整数)具有使表关系更快、存储和更新速度更经济的附加价值(更好的是,与业务键字段相比,使用代理键时外键不需要更新,时不时会发生变化)。

表的主键应用于唯一标识行,主要用于连接目的。考虑一个 Persons 表:名称可能会更改,并且不保证它们是唯一的。

认为公司:您是一家快乐的 Merkin 公司,与 Merkia 的其他公司开展业务。您很聪明,没有使用公司名称作为主键,因此您使用了 Merkia 政府的唯一公司 ID(由 10 个字母数字字符组成)。然后 Merkia 更改了公司 ID,因为他们认为这是个好主意。没关系,您可以使用数据库引擎的级联更新功能来进行原本不应该涉及您的更改。后来,您的业务扩大了,现在您在弗里多尼亚的一家公司工作。Freedonian 公司 ID 最多 16 个字符。您需要扩大公司 ID 主键(还有 Orders、Issues、MoneyTransfers 等中的外键字段),在主键(也在外键中)添加 Country 字段。哎哟!弗里多尼亚内战,分裂为三个国家。您的同事的国家名称应更改为新名称;级联更新来救援。顺便说一句,你的主键是什么?(国家/地区、公司 ID)还是(公司 ID、国家/地区)?后者有助于连接,前者避免另一个索引(或者可能有多个索引,如果您也希望订单按国家/地区分组)。

所有这些都不是证据,但表明用于唯一标识所有用途(包括连接操作)的行的代理键比业务键更可取。

代理键永远不会有理由改变。我不能对自然键说同样的话。姓氏、电子邮件地址、ISBN 数字 - 它们有一天都可能改变。

总的来说,我讨厌代理键。仅当没有可用的优质自然键时才应使用它们。当您考虑这一点时,认为向表中添加无意义的数据可以使事情变得更好是相当荒谬的。

以下是我的理由:

  1. 使用自然键时,表会按照最常搜索的方式进行聚集,从而使查询速度更快。

  2. 使用代理键时,必须在逻辑键列上添加唯一索引。您仍然需要防止逻辑重复数据。例如,即使 pk 是代理 id 列,您也不能允许组织表中存在两个具有相同名称的组织。

  3. 当代理键用作主键时,自然主键是什么就不太清楚了。开发时,您想知道哪组列使表独一无二。

  4. 在一对多关系链中,逻辑钥匙链。例如,组织有许多帐户,帐户有许多发票。因此 Organization 的逻辑键是 OrgName。Accounts 的逻辑键是 OrgName、AccountID。Invoice 的逻辑键是 OrgName、AccountID、InvoiceNumber。

    当使用代理键时,密钥链会被截断,因为只有直接父级的外键。例如,Invoice 表没有 OrgName 列。它只有 AccountID 列。如果您想要搜索给定组织的发票,则需要连接“组织”、“帐户”和“发票”表。如果您使用逻辑键,那么您可以直接查询组织表。

  5. 存储查找表的代理键值会导致表中填充无意义的整数。要查看数据,必须创建连接到所有查找表的复杂视图。查找表旨在保存列的一组可接受的值。不应通过存储整数代理键来对其进行编码。规范化规则中没有任何内容建议您应该存储代理整数而不是值本身。

  6. 我有三本不同的数据库书籍。其中没有一个显示使用代理键。

我想与您分享我在这场无休止的战争中的经验:D 关于自然键与代理键困境。我觉得 两个都 代理键(人工自动生成的)和自然键(由具有域含义的列组成)具有 优点缺点. 。因此,根据您的情况,选择一种方法或另一种方法可能更相关。

由于似乎许多人将代理键视为近乎完美的解决方案,而将自然键视为瘟疫,因此我将重点讨论另一种观点的论点:

代理键的缺点

代理键是:

  1. 性能问题的根源:
    • 它们通常使用自动递增列来实现,这意味着:
      • 每次您想要获取新的 Id 时都会往返数据库(我知道这可以使用缓存或 [seq]hilo 类似算法来改进,但这些方法仍然有其自身的缺点)。
      • 如果有一天您需要将数据从一种模式移动到另一种模式(至少在我的公司经常发生这种情况),那么您可能会遇到 ID 冲突问题。是的,我知道您可以使用 UUID,但最后需要 32 个十六进制数字!(如果您关心数据库大小,那么这可能是一个问题)。
      • 如果您对所有代理键使用一个序列,那么 - 当然 - 您最终将在数据库上出现争用。
  2. 容易出错。序列有 max_value 限制,因此作为开发人员,您必须注意以下几点:
    • 您必须循环序列(当达到最大值时,它会返回到 1,2,...)。
    • 如果您使用序列作为数据的排序(随着时间的推移),那么您必须处理循环的情况(Id 为 1 的列可能比 Id 最大值 - 1 的行更新)。
    • 确保您的代码(甚至您的客户端接口,这不应该发生,因为它应该是内部 Id)支持用于存储序列值的 32b/64b 整数。
  3. 他们不保证数据不重复。您始终可以有 2 行具有所有相同的列值,但具有不同的生成值。对我来说这是 从数据库设计的角度来看代理键问题。
  4. 更多内容请参见维基百科...

关于自然键的神话

  1. 复合键的效率低于代理键。不!这取决于所使用的数据库引擎:
  2. 自然键在现实生活中不存在。抱歉,但它们确实存在!例如,在航空业中,对于给定的情况,以下元组始终是唯一的 预定的 航班(航空公司、出发日期、航班号、运营后缀)。更一般地说,当给定的一组业务数据被保证是唯一的时 标准 那么这组数据就是一个[好]自然的关键候选者。
  3. 自然键“污染了子表的架构”。对我来说,这更多的是一种感觉,而不是一个真正的问题。拥有 4 列、每列 2 字节的主键可能比单列 11 字节更有效。此外,这 4 列可用于直接查询子表(通过在 where 子句中使用 4 列),而无需连接到父表。

结论

在相关时使用自然键,在更适合使用代理键时使用代理键。

希望这对某人有帮助!

始终使用没有业务意义的密钥。这只是一个很好的做法。

编辑:我试图在网上找到它的链接,但找不到。然而在 《企业架构模式》 [Fowler] 它很好地解释了为什么你不应该使用除了钥匙之外的任何东西,因为钥匙除了作为钥匙之外没有任何意义。归根结底,它应该有一份工作,而且只有一份工作。

如果您打算使用 ORM 工具来处理/生成数据类,代理键会非常方便。虽然您可以将复合键与一些更高级的映射器一起使用(请阅读:hibernate),它会增加代码的复杂性。

(当然,数据库纯粹主义者会认为,即使是代理键的概念也是令人厌恶的。)

我喜欢在合适的时候使用 uid 作为代理键。他们的主要胜利是您提前知道关键,例如您可以创建一个类的实例,其 ID 已设置并保证是唯一的,而使用整数键时,您需要默认为 0 或 -1,并在保存/更新时更新为适当的值。

不过,UID 在查找和连接速度方面会受到影响,因此它们是否合适取决于相关应用程序。

我认为使用代理键更好,因为它改变的可能性为零。几乎我能想到的任何你可以用作自然键的东西都可能会改变(免责声明:并不总是如此,但通常如此)。

一个例子可能是汽车数据库 - 乍一看,您可能会认为车牌可以用作钥匙。但这些可能会改变,所以这不是一个好主意。你不会真的想找出答案 当有人来找你想知道为什么他们不能将他们的车牌更改为闪亮的新个性化车牌时,发布该应用程序。

如果可能的话,始终使用单列和代理键。这使得连接以及插入/更新/删除更加清晰,因为您只负责跟踪一条信息来维护记录。

然后,根据需要,将您的业务键堆叠为唯一的约束或索引。这将使您的数据完整性保持完整。

业务逻辑/自然键可以更改,但表的物理键永远不应该更改。

在数据仓库场景中,我认为最好遵循代理键路径。两个原因:

  • 您独立于源系统,那里的更改(例如数据类型更改)不会影响您。
  • 您的 DW 将需要更少的物理空间,因为您将仅使用整数数据类型作为代理键。而且你的索引也会工作得更好。

当业务信息可能发生变化或相同时,代理键会很有用。毕竟,企业名称在全国范围内不必是唯一的。假设您经营两家名为 Smith Electronics 的企业,一家位于堪萨斯州,另一家位于密歇根州。您可以通过地址来区分它们,但这会改变。甚至国家也可以改变;如果堪萨斯州堪萨斯城的史密斯电子公司跨河搬到密苏里州堪萨斯城怎么办?没有明显的方法可以使这些业务与自然密钥信息区分开来,因此代理键非常有用。

将代理键想象成 ISBN 号。通常,您可以通过标题和作者来识别一本书。不过,我有两本 H. 题为《珍珠港》的书。P。威尔莫特,它们绝对是不同的书,而不仅仅是不同的版本。在这种情况下,我可以参考书籍的外观,或者较早的书籍与较晚的书籍,但最好有 ISBN 可以依靠。

提醒一下,将聚集索引放置在随机代理键上并不是一个好习惯,即读取 XY8D7-DFD8S 的 GUID,因为 SQL Server 无法对这些数据进行物理排序。相反,您应该在这些数据上放置唯一索引,尽管简单地为主表操作运行 SQL 探查器,然后将这些数据放入数据库引擎优化顾问中也可能是有益的。

参见主题@ http://social.msdn.microsoft.com/Forums/en-us/sqlgetstarted/thread/27bd9c77-ec31-44f1-ab7f-bd2cb13129be

情况1: 你的桌子是一个 查找表 少于 50 种(插入)

使用 业务/自然键。例如:

Table: JOB with 50 inserts
CODE (primary key)       NAME               DESCRIPTION
PRG                      PROGRAMMER         A programmer is writing code
MNG                      MANAGER            A manager is doing whatever
CLN                      CLEANER            A cleaner cleans
...............
joined with
Table: PEOPLE with 100000 inserts

foreign key JOBCODE in table PEOPLE
looks at
primary key CODE in table JOB

案例2: 你的桌子是一个 具有数千个插入的表

使用 代理/自动增量键. 。例如:

Table: ASSIGNMENT with 1000000 inserts
joined with
Table: PEOPLE with 100000 inserts

foreign key PEOPLEID in table ASSIGNMENT
looks at
primary key ID in table PEOPLE (autoincrement)

在第一种情况下:

  • 您可以选择表 PEOPLE 中的所有程序员,而不使用与表 JOB 的连接,而只需使用:“从职位代码 = 'PRG' 的人员中选择 *”

在第二种情况下:

  • 您的数据库查询速度更快,因为您的主键是整数
  • 您无需费心查找下一个唯一键,因为数据库本身会为您提供下一个自动增量。

这是代理键几乎可以使用的情况之一 总是 说得通。在某些情况下,您要么选择最适合数据库的选项,要么选择最适合对象模型的选项,但在这两种情况下,使用无意义的键或 GUID 是更好的主意。它使索引变得更容易、更快,并且它是您的对象的一个​​不会改变的标识。

马为课程。陈述我的偏见;我首先是一名开发人员,所以我主要关心为用户提供一个可用的应用程序。

我曾经在使用自然键的系统上工作过,并且不得不花费大量时间来确保值的变化会产生连锁反应。

我曾经在仅具有代理键的系统上工作过,唯一的缺点是缺乏用于分区的非规范化数据。

与我合作过的大多数传统 PL/SQL 开发人员都不喜欢代理键,因为每个连接的表数量太多,但我们的测试和生产数据库从来没有引起任何注意;额外的连接不会影响应用程序的性能。对于不支持“X inner join Y on X.a = Y.b”之类的子句的数据库方言,或者不使用该语法的开发人员,代理键的额外联接确实会使查询更难阅读,并且输入和输入的时间更长。查看:请参阅@Tony Andrews 帖子。但如果您使用 ORM 或任何其他 SQL 生成框架,您将不会注意到它。盲打也会减轻这种情况。

也许与这个主题不完全相关,但我在处理代理键时感到头疼。Oracle 预交付分析在仓库中的所有维度表上创建自动生成的 SK,并将这些存储在事实中。因此,每当添加新列或需要填充维度中的所有项目时需要重新加载它们(维度)时,更新期间分配的 SK 都会使 SK 与存储到事实的原始值不同步,从而迫使完全重新加载所有连接到它的事实表。我希望即使 SK 是一个毫无意义的数字,也有某种方式可以使其无法更改原始/旧记录。众所周知,现成的很少能满足组织的需求,我们必须不断定制。现在,我们的仓库中拥有 3 年的数据,并且从 Oracle 财务系统完全重新加载的数据量非常大。因此,就我而言,它们不是从数据输入中生成的,而是添加到仓库中以帮助报告性能。我明白,但我们的确实发生了变化,这是一场噩梦。

对于时间点数据库,最好结合代理键和自然键。例如您需要跟踪俱乐部的会员信息。成员的某些属性永远不会改变。例如出生日期,但姓名可以更改。因此,使用 member_id 代理键创建一个 Member 表,并有一列用于 DOB。创建另一个名为 person name 的表,并包含 member_id、member_fname、member_lname、date_updated 列。在此表中,自然键为member_id + date_updated。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top