我们又来了,旧的争论仍然存在......
我们最好有一个业务键作为主键,还是我们宁愿有一个代理 id(即 SQL Server 标识)对业务键字段具有唯一约束?
请提供例子或证据来支持你的理论。
我们又来了,旧的争论仍然存在......
我们最好有一个业务键作为主键,还是我们宁愿有一个代理 id(即 SQL Server 标识)对业务键字段具有唯一约束?
请提供例子或证据来支持你的理论。
使用代理键的几个原因:
稳定性:由于业务或自然需要而更改键将对相关表产生负面影响。代理键很少(如果有的话)需要更改,因为值没有任何意义。
约定:允许您拥有标准化的主键列命名约定,而不必考虑如何为它们的 PK 连接具有不同名称的表。
速度:根据 PK 值和类型,整数的代理键可能更小,索引和搜索速度更快。
两个都。吃你的蛋糕。
请记住,主键没有什么特别之处,只是它被标记为这样。它只不过是一个 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 之类的表使用无意义的数字键 - 但不是为所有表使用,出于某种原因,这里的大多数人似乎都提倡。
另见:我对另一个问题的回答
代理键永远不会有改变的理由。我不能对自然键说同样的话。姓氏、电子邮件、ISBN 号码——它们都可以在一天内改变。
代理键(通常是整数)具有使您的表关系更快的附加值,并且在存储和更新速度方面更经济(更好的是,与业务键字段相比,使用代理键时不需要更新外键,时不时会改变)。
表的主键应该用于唯一标识行,主要用于连接目的。想想 Persons 表:名称可以更改,并且不能保证它们是唯一的。
Think Companies:您是一家快乐的 Merkin 公司,与 Merkia 的其他公司开展业务。您很聪明,不会使用公司名称作为主键,因此您使用 Merkia 政府的唯一公司 ID,全部由 10 个字母数字字符组成。然后 Merkia 更改了公司 ID,因为他们认为这是个好主意。没关系,您可以使用数据库引擎的级联更新功能来进行最初不应该涉及您的更改。后来,您的业务扩大了,现在您在弗里多尼亚的一家公司工作。Freedonian 公司 ID 最多 16 个字符。您需要扩大公司 id 主键(也是 Orders、Issue、MoneyTransfers 等中的外键字段),在主键中添加 Country 字段(也在外键中)。哎哟! 弗里多尼亚的内战,它' s 分裂在三个国家。您的同事的国家名称应更改为新名称;级联更新救援。顺便说一句,你的主键是什么?(国家,CompanyID)还是(CompanyID,国家)?后者有助于加入,前者避免了另一个索引(或者可能很多,如果您希望您的订单也按国家/地区分组)。
所有这些都不是证据,而是表明为所有用途(包括连接操作)唯一标识行的代理键优于业务键。
我一般讨厌代理键。只有在没有可用的优质自然密钥时才应使用它们。当您考虑它时,认为向您的表中添加无意义的数据可以使事情变得更好是相当荒谬的。
以下是我的理由:
使用自然键时,表以最常被搜索的方式聚集,从而使查询更快。
使用代理键时,您必须在逻辑键列上添加唯一索引。您仍然需要防止逻辑重复数据。例如,即使 pk 是代理 id 列,您也不能在组织表中允许两个具有相同名称的组织。
当代理键用作主键时,自然主键是什么就不太清楚了。在开发时,您想知道哪些列集使表独一无二。
在一对多关系链中,逻辑键链。例如,组织有很多账户,账户有很多发票。所以Organization 的逻辑键是OrgName。Accounts 的逻辑键是 OrgName、AccountID。Invoice 的逻辑键是 OrgName、AccountID、InvoiceNumber。
当使用代理键时,键链被截断,只有一个外键指向直接父级。例如,Invoice 表没有 OrgName 列。它只有一个 AccountID 列。如果要搜索给定组织的发票,则需要加入组织、帐户和发票表。如果您使用逻辑键,那么您可以直接查询组织表。
存储查找表的代理键值会导致表被无意义的整数填充。要查看数据,必须创建连接到所有查找表的复杂视图。查找表旨在保存一组可接受的列值。它不应该通过存储一个整数代理键来编码。规范化规则中没有任何内容建议您应该存储代理整数而不是值本身。
我有三本不同的数据库书籍。其中没有一个显示使用代理键。
我想与您分享我在这场无休止的战争中的经验:D 关于自然键与替代键的困境。我认为代理键(人工自动生成的)和自然键(由具有域含义的列组成)都有优点和缺点。因此,根据您的情况,选择一种方法或另一种方法可能更相关。
由于似乎很多人将代理键视为近乎完美的解决方案,而将自然键视为瘟疫,我将重点介绍另一种观点的论点:
代理键是:
在相关时使用自然键,并在最好使用代理键时使用它们。
希望这对某人有所帮助!
始终使用没有商业意义的密钥。这只是一个好习惯。
编辑:我试图在网上找到它的链接,但我做不到。但是,在“企业架构模式” [Fowler] 中,它很好地解释了为什么您不应该使用除了作为键之外没有任何意义的键以外的任何东西。它归结为一个事实,它应该只有一份工作和一份工作。
如果您打算使用 ORM 工具来处理/生成数据类,则代理键非常方便。虽然您可以将复合键与一些更高级的映射器(阅读:hibernate)一起使用,但它会给您的代码增加一些复杂性。
(当然,数据库纯粹主义者会争辩说,即使是代理键的概念也是可憎的。)
我喜欢在合适的时候使用 uids 作为代理键。他们的主要胜利是您提前知道密钥,例如,您可以创建一个已设置 ID 并保证唯一的类的实例,而使用整数密钥,您需要默认为 0 或 - 1 并在保存/更新时更新为适当的值。
不过,UID 在查找和连接速度方面会受到惩罚,因此取决于相关应用程序是否需要它们。
在我看来,使用代理键更好,因为它改变的可能性为零。几乎我能想到的任何你可以用作自然键的东西都可能改变(免责声明:并不总是正确的,但通常是这样)。
一个例子可能是汽车数据库 - 乍一看,您可能认为车牌可以用作钥匙。但是这些可以改变,所以这是一个坏主意。你不会真的想在发布应用程序后发现这一点,当有人来找你想知道为什么他们不能将他们的车牌更改为他们闪亮的新个性化车牌时。
如果可能,请始终使用单列代理键。这使得连接以及插入/更新/删除更加清晰,因为您只负责跟踪一条信息以维护记录。
然后,根据需要,将您的业务密钥堆叠为唯一的约束或索引。这将使您保持数据完整性。
业务逻辑/自然键可以更改,但表的物理键不应该更改。
在数据仓库场景中,我认为最好遵循代理键路径。两个原因:
案例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:你的表是一张有数千条记录的表
使用代理/自动增量键。
例如:
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
而无需使用 join with table JOB
,但只需:SELECT * FROM PEOPLE WHERE JOBCODE = 'PRG'
在第二种情况下:
当业务信息可以更改或相同时,代理键可能很有用。毕竟,企业名称在全国范围内不必是唯一的。假设您与两家名为 Smith Electronics 的企业打交道,一家在堪萨斯州,一家在密歇根州。您可以通过地址区分它们,但这会改变。甚至状态也可以改变;如果堪萨斯州堪萨斯城的史密斯电子公司渡河到密苏里州堪萨斯城会怎样?没有明显的方法可以使这些业务与自然密钥信息区分开来,因此代理密钥非常有用。
将代理键想象成 ISBN 号。通常,您通过书名和作者来识别一本书。但是,我有两本书,HP Willmott 的《珍珠港》,它们绝对是不同的书,而不仅仅是不同的版本。在这种情况下,我可以参考书籍的外观,或者早期与后期,但我也可以使用 ISBN。
这是代理键几乎总是有意义的情况之一。在某些情况下,您要么选择最适合数据库的方式,要么选择最适合您的对象模型的方式,但在这两种情况下,使用无意义的键或 GUID 是一个更好的主意。它使索引更容易和更快,并且它是您的对象的标识,不会改变。
提醒一下,将聚集索引放置在随机代理键(即读取 XY8D7-DFD8S 的 GUID)上并不是一个好习惯,因为它们 SQL Server 无法对这些数据进行物理排序。相反,您应该在这些数据上放置唯一索引,尽管简单地为主表操作运行 SQL 分析器,然后将这些数据放入数据库引擎优化顾问中也可能是有益的。
见线程@http ://social.msdn.microsoft.com/Forums/en-us/sqlgetstarted/thread/27bd9c77-ec31-44f1-ab7f-bd2cb13129be
在时间点数据库的情况下,最好将代理键和自然键结合起来。例如,您需要跟踪俱乐部的会员信息。成员的某些属性永远不会改变。例如出生日期,但姓名可以更改。因此,使用 member_id 代理键创建一个 Member 表,并有一个 DOB 列。创建另一个名为 person name 的表,并包含 member_id、member_fname、member_lname、date_updated 列。在这个表中,自然键是 member_id + date_updated。
课程用马。陈述我的偏见;我首先是一名开发人员,所以我主要关心为用户提供一个工作应用程序。
我曾在具有自然键的系统上工作过,并且不得不花费大量时间来确保值的变化会波及。
我曾在只有代理键的系统上工作过,唯一的缺点是缺乏用于分区的非规范化数据。
与我合作过的大多数传统 PL/SQL 开发人员都不喜欢代理键,因为每次连接的表数量众多,但我们的测试和生产数据库从未出过汗。额外的连接不会影响应用程序的性能。对于不支持“X 内连接 Y on Xa = Yb”等子句的数据库方言,或者不使用该语法的开发人员,代理键的额外连接确实使查询更难阅读,并且更长的输入和检查:见@Tony Andrews 帖子。但是,如果您使用 ORM 或任何其他 SQL 生成框架,您将不会注意到它。触摸打字也减轻了。
也许与这个主题不完全相关,但我在处理代理键时很头疼。Oracle 预交付分析在其仓库中的所有维度表上创建自动生成的 SK,并将这些数据存储在事实中。因此,只要在添加新列或需要为维度中的所有项目填充新列时需要重新加载它们(维度)时,更新期间分配的 SK 会使 SK 与存储到事实的原始值不同步,从而强制完全重新加载加入它的所有事实表。我希望即使 SK 是一个毫无意义的数字,也有某种方式无法改变原始/旧记录。众所周知,开箱即用很少能满足组织的需求,我们必须不断地进行定制。现在我们的仓库中有 3 年的数据,Oracle 财务系统的完全重新加载非常大。因此,就我而言,它们不是从数据输入中生成的,而是添加到仓库中以帮助报告性能。我明白了,但我们的确实发生了变化,这是一场噩梦。