代理项与自然/业务密钥

又来了,旧的争论还在继续。

我们是应该使用业务密钥作为主密钥,还是应该使用对业务密钥字段具有唯一约束的代理 ID (即 SQLServer 标识) ?

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

80798 次浏览

一定要用一把没有商业意义的钥匙,这是很好的练习。

编辑: 我试图在网上找到它的链接,但是找不到。然而在 “企业架构模式”[ Fowler ]中有一个很好的解释,为什么你不应该使用除了一个键以外的任何东西,除了作为一个键没有任何意义。归根结底,它应该只有一个工作和一个工作的事实。

代理密钥永远不会有更改的理由。我不能说同样的自然键。姓氏,电子邮件,国际标准书号码-他们都可以改变一天。

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

  • 您独立于源系统,并且那里的更改——比如数据类型的更改——不会影响您。
  • 您的 DW 将需要较少的物理空间,因为您将只对代理键使用整数数据类型。你的索引也会更有效。

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

一个例子可能是一个数据库的汽车-乍一看,您可能认为车牌可以用作关键。但这些可以改变,所以这将是一个坏主意。当有人来找你问为什么他们不能把自己的车牌换成崭新的个性化车牌时,你不会真的想知道 之后发布这款应用的原因。

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

表的主键应该用于唯一标识行,主要用于连接目的。想象一下 Person 表: 名称可以更改,并且不保证它们是唯一的。

思考公司: 你是一家快乐的默金公司,与 Merkia 的其他公司做生意。您很聪明,没有使用公司名称作为主键,所以您使用了 Merkia 政府的唯一公司 ID,其中包含10个字母数字字符。 然后,Merkia 更改了公司的 ID,因为他们认为这是一个好主意。没关系,您可以使用数据库引擎的级联更新特性来进行更改,而这种更改一开始就不应该涉及到您。后来,你的业务扩大了,现在你在 Freedonia 的一家公司工作。Freedonian 公司的 ID 最多可达16个字符。您需要放大公司 id 主键(还包括 Orders、 Affairs、 MoneyTransferers 等中的外键字段) ,并在主键中添加 Country 字段(也包括外键)。哎哟!Freedonia 内战分裂成三个国家。您同伴的国家名称应该更改为新的; 对救援进行级联更新。顺便问一下,你的主钥匙是什么?(国家,公司 ID)还是(公司 ID,国家) ?后者有助于连接,前者避免了另一个索引(或许许多索引,如果您希望您的订单也按国家分组的话)。

所有这些都不是证明,而是表明用于唯一标识所有用途(包括联接操作)的行的代理键优于业务键。

下面是使用代理键的几个原因:

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

  2. 惯例 : 允许您拥有一个标准化的主键列变数命名原则,而不必考虑如何将具有不同名称的表连接到它们的 PKs 中。

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

在这种情况下,代理键几乎就是 一直都是。在某些情况下,您要么选择对数据库最好的方式,要么选择对对象模型最好的方式,但是在这两种情况下,使用无意义的键或 GUID 都是更好的主意。它使得索引更加容易和快速,并且它是对象的标识,不会改变。

如果您计划使用 ORM 工具来处理/生成数据类,那么代理键非常方便。虽然可以将组合键与一些更高级的映射程序(如: hibernate)一起使用,但它会给代码增加一些复杂性。

(当然,数据库纯粹主义者会争辩说,即使是代理密钥的概念也是令人厌恶的。)

我喜欢在合适的时候使用 uids 作为代理键。它们的主要优势在于你提前知道了密钥,例如你可以创建一个类的实例,其 ID 已经设置好并且保证是唯一的,而对于一个整型密钥,你需要默认为0或 -1,并且在保存/更新时更新到一个合适的值。

UID 在查找和连接速度方面有缺陷,因此它取决于所讨论的应用程序是否需要它们。

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

然后,根据需要将业务密钥作为唯一约束或索引进行堆栈。

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

都有,吃你的蛋糕吧。

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

如果使用代理项,您仍然需要一个业务项来根据业务规则确保惟一性。

似乎还没有人说过任何支持非代理(我不愿说“自然”)键的话。开始了。

代理键的 不利条件是指它们是 毫无意义(被一些人引用为优势,但是...)。这有时会迫使您在查询中加入比实际需要的多得多的表。比较:

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、 VALID 或 HR 的代码发生变化时会发生什么?”我的回答是: “你为什么要改变它?”这些不是“自然”的关键,在某种意义上,一些外部机构将要立法,从今以后“有效”应该被重新编码为“良好”。只有一小部分的“自然”键真正属于这个类别-SSN 和邮政编码是通常的例子。对于 Person,Address 这样的表,我肯定会使用一个毫无意义的数字键,但对于 一切则不会,因为出于某种原因,这里的大多数人似乎都提倡使用 一切

参见: 我对另一个问题的回答

当业务信息可能发生更改或相同时,代理项键可能非常有用。毕竟,企业名称不必在全国各地都是独一无二的。假设您处理的是两家名为史密斯电子(Smith Electronics)的企业,一家在堪萨斯州,一家在密歇根州。你可以通过地址区分他们,但那会改变的。即使是这个州也可以改变; 如果堪萨斯州堪萨斯城的史密斯电子公司越过河流到密苏里州的堪萨斯城怎么办?没有明显的方法可以将这些业务与自然密钥信息区分开来,因此代理密钥非常有用。

可以将代理密钥看作 ISBN 号码。通常,你可以通过书名和作者来识别一本书。然而,我有两本惠普 · 威尔莫特的《珍珠港》 ,它们绝对是不同的书,而不仅仅是不同的版本。在这种情况下,我可以参考这些书的外观,或者是前者与后者的区别,但幸好我有 ISBN 可以依靠。

在时间点数据库的情况下,最好结合使用代理键和自然键。例如,你需要追踪一个俱乐部的会员信息。成员的某些属性永远不会更改。出生日期,但名字可以更改。 因此,创建一个具有 Member _ id 代理键的 Member 表,并为 DOB 创建一个列。 创建另一个名为 person name 的表,并为 member _ id、 member _ fname、 member _ lname、 date _ update 创建列。在此表中,自然键为 member _ id + date _ update。

我讨厌代理密钥。它们应该只在没有可用的质量自然键时使用。当你思考这个问题的时候,认为向表格中添加无意义的数据可以使事情变得更好的想法是相当荒谬的。

以下是我的理由:

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

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

  3. 如果使用代理键作为主键,则不太清楚自然主键是什么。在开发时,需要了解哪组列使表具有唯一性。

  4. 在一对多关系链中,逻辑键链。例如,组织有许多帐户,帐户有许多发票。所以组织的逻辑关键就是 OrgName。Account 的逻辑键是 OrgName,AccountID。发票的逻辑键是 OrgName、 AccountID、 InvoiceNumber。

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

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

  6. 我有三本不同的数据库书,没有一本是用代理密钥的。

赛马。说明我的偏见; 我首先是一个开发人员,所以我主要关心的是给用户一个可工作的应用程序。

我曾经在使用自然键的系统上工作过,并且不得不花费大量的时间来确保值的变化能够传播开来。

我曾经研究过只使用代理密钥的系统,唯一的缺点是缺乏用于分区的非规范化数据。

与我共事过的大多数传统 PL/SQL 开发人员都不喜欢代理键,因为每个连接有很多表,但是我们的测试和生产数据库从来没有出过汗; 额外的连接不会影响应用程序的性能。由于数据库方言不支持像“ X 内连接 Y on X.a = Y.b”这样的子句,或者开发人员不使用这种语法,代理键的额外连接确实会使查询更难读,输入和检查的时间更长: 见@Tony Andrews post。但是如果您使用 ORM 或任何其他 SQL 生成框架,您不会注意到它。触摸打字也减轻了。

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

请参阅线程@http://social.msdn.microsoft.com/Forums/en-us/sqlgetstarted/thread/27bd9c77-ec31-44f1-ab7f-bd2cb13129be

案例1: 您的表是一个少于50条记录(50种类型)的 查找表

在这种情况下,使用 手动命名键,根据每个记录的含义

例如:

Table: JOB with 50 records
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: 您的表是一个包含数千条记录的 < strong > 表

使用 代理项/自动增量键

例如:

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


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

在第一种情况下:

  • 您可以选择表 PEOPLE中的所有程序员,而不使用与表 JOB的连接,只使用: SELECT * FROM PEOPLE WHERE JOBCODE = 'PRG'

在第二种情况下:

  • 由于主键是整数,因此数据库查询速度更快
  • 您不需要费心寻找下一个唯一键,因为数据库本身提供了下一个自增量。

也许和这个话题不完全相关,但是处理代理键的问题让我很头疼。Oracle 提前交付的分析在仓库中的所有维度表上创建自动生成的 SK,并将这些 SK 存储在事实上。因此,当添加新列或需要为维度中的所有项填充新列时,任何时候都需要重新加载它们(维度) ,在更新期间分配的 SK 使得 SK 与存储到事实的原始值不同步,从而迫使所有连接到事实表的事实表完全重新加载。我希望即使 SK 是一个毫无意义的数字,也会有某种方式不能改变原始/旧的记录。正如许多人所知,开箱即用很少能满足组织的需求,我们必须不断定制。现在我们的仓库中有3年的数据,从 Oracle 财务系统完成的重新加载非常大。因此,在我的例子中,它们不是从数据输入生成的,而是添加到一个仓库中以帮助报告性能。我明白,但我们的生活会改变,这简直是噩梦。

我想和你们分享我在这场无休止的战争中的经验: D 关于自然与代理关键的困境。我认为 都有代理键(人工自动生成的键)和自然键(由具有域意义的列组成的键)具有 专业人士缺点。因此,根据您的情况,选择一种或另一种方法可能更相关。< br/>

因为似乎很多人认为代理键是近乎完美的解决方案,而自然键是瘟疫,所以我将集中讨论另一种观点的论点:

代理键的缺点

代理密钥是:

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

自然键上的神话

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

结论

如果需要使用自然键,则使用自然键; 如果最好使用代理键,则使用代理键。

希望这对谁有帮助!