代理与自然/商业钥匙
这里我们再去,旧的观点仍然出现…
我们最好有一个商业密钥作为主键,或者我们宁愿有一个代理ID(即SQL Server身份)在商业密钥字段上的唯一约束?
请提供例子或证据来支持你的理论。
都。 把你的蛋糕吃掉。
请记住,主键没有什么特别之处,除非它被标记为这样。 它不过是一个NOT NULL UNIQUE约束,而一个表可以有多个。
如果您使用代理键,您仍然需要一个业务键,以确保根据业务规则的唯一性。
使用代理键的几个原因:
-
稳定性 :因业务或自然需要而更改密钥会对相关表格产生负面影响。 替代键很less,如果有的话,需要改变,因为没有意义的价值。
-
约定 :允许你有一个标准化的主键列命名约定,而不必考虑如何连接具有不同名称的表的PK。
-
速度 :根据PK值和types,整数的代理键可能更小,更快的索引和search。
看来,还没有人说任何支持非代理人(我毫不犹豫地说,“自然”)的钥匙。 所以这里…
代理键的一个缺点是它们没有意义 (被某些人列为优势,但是…)。 这有时会迫使你join更多的表到你的查询中,而不是真的有必要。 比较:
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这样的表格,但是对于所有的东西来说都不是这样,因为大多数人在这里似乎都提倡这个function。
另请参阅: 我对另一个问题的回答
代理键永远不会有改变的理由。 我不能说自然的关键。 最后的名字,电子邮件,国际标准书号nubmers – 他们都可以改变一天。
代理键(通常是整数)具有使表关系更快,存储和更新速度更经济的附加价值(更好的是,使用代理键时不需要更新外键,与业务键字段相比,不时变化)。
应该使用表的主键来唯一标识行,主要用于联接目的。 想一个人表:名称可以改变,并不能保证唯一。
认为公司:你是一个快乐的Merkin公司在Merkia与其他公司做生意。 您足够聪明,不要使用公司名称作为主键,因此您使用Merkia的政府独特的公司ID(全部为10个字母数字字符)。 然后Merkia更改公司ID,因为他们认为这是个好主意。 没关系,你使用你的数据库引擎的级联更新function,对于不应该首先涉及到你的更改。 之后,您的业务将扩大,现在您与Freedonia的一家公司合作。 Freedonian公司ID最多16个字符。 您需要放大公司ID主键(也是订单,问题,MoneyTransfers等外键字段),在主键(也在外键)中添加一个国家/地区字段。 哎哟! 在弗里多尼亚内战,它在三个国家分裂。 你的同事的国名应改为新的; 级联更新的救援。 顺便说一下,你的主要关键是什么? (国家,公司ID)还是(公司ID,国家)? 后者帮助连接,前者避免了另一个索引(或者许多,如果你想要你的订单也按国家分组)。
所有这些都不是certificate,但是表明用于唯一标识所有用途(包括联合操作)的行的替代键优于业务键。
我讨厌一般的代理键。 只有在没有可用的质量自然键时才能使用它们。 当你考虑这个问题时,相当荒唐,认为在桌面上添加无意义的数据会使事情变得更好。
这是我的原因:
-
当使用自然键时,表以最常search的方式聚集,从而使查询更快。
-
使用代理键时,您必须在逻辑键列上添加唯一索引。 您仍然需要防止逻辑重复数据。 例如,即使pk是代理ID列,也不能在组织表中允许具有相同名称的两个组织。
-
当使用代理键作为主键时,不太清楚自然主键是什么。 开发时,您想知道哪些列使表独一无二。
-
在一对多的关系链中,逻辑钥匙链。 例如,组织有许多帐户和帐户有很多发票。 所以Organization的逻辑键是OrgName。 帐户的逻辑键是OrgName,AccountID。 发票的逻辑键是OrgName,AccountID,InvoiceNumber。
当使用代理键时,通过仅有直接父键的外键来截断键链。 例如,“发票”表没有OrgName列。 它只有一个AccountID的列。 如果要search给定组织的发票,则需要join组织,帐户和发票表。 如果您使用逻辑键,那么您可以直接查询组织表。
-
存储查找表的代理键值导致表格被填充无意义的整数。 要查看数据,必须创build连接到所有查找表的复杂视图。 查找表是为了保存一列可接受的值。 它不应该通过存储一个整数代理键代码。 规范化规则中没有任何内容表明您应该存储替代整数而不是值本身。
-
我有三个不同的数据库书籍。 没有一个显示使用代理键。
我想和你们分享我在这场无休止的战争中的经历:D关于自然与代理关键的困境。 我认为,代理键(人工自动生成的)和自然键(由具有领域意义的列组成)都有优点和缺点 。 所以根据你的情况,select一种方法或者另一种方法可能更有意义。
由于似乎很多人把代理键作为瘟疫的近乎完美的解决scheme和自然的关键,所以我将着重于另一种观点的论点:
代理键的缺点
代理键是:
- 性能问题的根源:
- 它们通常使用自动递增的列来实现,这意味着:
- 每次你想得到一个新的数据库往返数据库(我知道这可以通过使用caching或hilo类似的algorithm来改善,但是这些方法仍然有它们自己的缺点)。
- 如果有一天你需要将数据从一个模式转移到另一个模式(至less在我的公司经常发生),那么你可能会遇到Id冲突问题。 是的,我知道你可以使用UUID,但最后需要32个hex数字! (如果你关心数据库的大小,那么这可能是一个问题)。
- 如果你对所有代理键都使用了一个序列,那么 – 当然 – 你最终会争夺你的数据库。
- 它们通常使用自动递增的列来实现,这意味着:
- 容易出错。 一个序列有一个max_value的限制,所以 – 作为一个开发者 – 你必须注意以下几点:
- 你必须循环你的序列(当达到最大值时,它会回到1,2,…)。
- 如果您将序列用作数据的sorting(随着时间的推移),那么您必须处理循环的情况(Id 1的列可能比Id最大值 – 1的行更新)。
- 确保你的代码(甚至你的客户端接口不应该发生,因为它应该是一个内部ID)支持你用来存储序列值的32b / 64b整数。
- 他们不保证没有重复的数据。 你可以总是有2行,所有相同的列值,但具有不同的生成值。 对我来说,这是从数据库devise的angular度来看代理键的问题。
- 更多维基百科…
神话在自然钥匙
- 组合键比代理键效率低。 没有! 这取决于使用的数据库引擎:
- 神谕
- MySQL的
- 自然键不存在于现实生活中。 对不起,但他们确实存在! 例如,在航空工业中,对于给定的预定航class(航空公司,出发date,航class号,运营单位),以下元组将始终是唯一的。 更一般地说,当一组商业数据被保证是唯一的一个给定的标准,那么这组数据是一个[良好]自然的关键人选。
- 自然键“污染子表”的模式。 对我来说,这是一种感觉,而不是一个真正的问题。 具有每个2字节的4列主键可能比单个11字节的列更有效。 此外,可以使用4列直接查询子表(通过使用where子句中的4列)而无需连接到父表。
结论
使用自然键时,如果使用自然键,使用代理键时更好。
希望这有助于某人!
一直使用一个没有商业意义的钥匙。 这只是一个好习惯。
编辑:我试图find一个在线链接,但我不能。 然而,在“企业架构模式” [Fowler]中,它有一个很好的解释,为什么除了作为一个关键之外,你不应该使用其他任何东西以外的其他东西。 归结起来,它只应该有一份工作,一份工作。
如果您打算使用ORM工具处理/生成数据类,则代理键非常方便。 虽然您可以将复合键与更高级的映射器一起使用(请参阅:hibernate),但它会增加代码的复杂性。
(当然,数据库纯粹主义者会认为,即使代理键的概念是可憎的。
我很喜欢在适当的时候使用uid代理键。 与他们的主要胜利是,你提前知道关键,例如你可以创build一个类的实例已经设置的ID,并保证是唯一的,而与一个整数键,你需要默认为0或 – 1并在保存/更新时更新为适当的值。
UID在查询和join速度方面有处罚,所以它依赖于所讨论的应用程序是否合意。
使用代理键在我看来更好,因为它没有改变的机会。 几乎我所能想到的任何可以用作自然钥匙的东西都可能会改变(免责声明:并非总是如此,但通常是这样)。
一个例子可能是汽车数据库 – 乍一看,你可能会认为车牌可以作为关键。 但是这些可以改变,所以这是一个坏主意。 当你想要知道为什么他们不能改变他们的车牌号码到他们个性鲜明的车牌时,你真的不希望在发布应用程序之后发现这一点。
如果可能的话,总是使用单列,代理键。 这使得连接以及插入/更新/删除更清洁,因为您只负责跟踪单个信息来维护logging。
然后,根据需要,将业务密钥堆叠为唯一的限制或索引。 这将使您的数据完整性保持不变。
业务逻辑/自然键可以改变,但表的物理关键字永远不会改变。
在数据仓库场景中,我认为最好遵循代理键path。 两个原因:
- 您独立于源系统,并且在那里进行更改(如数据types更改)不会影响您。
- 您的DW将需要更less的物理空间,因为您将只使用整数数据types代替您的代理键。 你的索引也会更好。
当业务信息可以改变或相同时,代理键可能很有用。 毕竟,企业名称在全国不一定是唯一的。 假设你处理两个名为史密斯电子公司,一个在堪萨斯州和一个在密歇根州。 你可以通过地址区分它们,但是会改变的。 即使国家可以改变, 如果堪萨斯州堪萨斯城的史密斯电子公司移动到密苏里州堪萨斯城呢? 使用自然关键信息保持这些业务的明显方式并不明显,因此代理键非常有用。
想象一下代理键就像一个ISBN号码。 通常,你通过标题和作者来确定一本书。 但是,我有两本书,分别是惠普威尔莫特的“珍珠港”,它们绝对是不同的书,不仅仅是不同的版本。 在这样的情况下,我可以参考书籍的外观,或者更早或更晚的书籍,但是我也可以参考国际标准书号。
作为一个提醒,将聚簇索引放在随机代理键(即读取XY8D7-DFD8S的GUID)上是不好的做法,因为SQL Server无法对这些数据进行物理sorting。 您应该在这些数据上放置唯一的索引,但是也可以为主表操作简单地运行SQL事件探查器,然后将这些数据放到数据库引擎优化顾问中。
请参阅thread @ http://social.msdn.microsoft.com/Forums/en-us/sqlgetstarted/thread/27bd9c77-ec31-44f1-ab7f-bd2cb13129be
这是代理键几乎总是有意义的情况之一。 在某些情况下,您可能会select最适合数据库的模式,或者最适合您的对象模型,但是在这两种情况下,使用无意义的密钥或GUID是一个更好的主意。 它使得索引变得更容易和更快,并且它是不改变对象的标识。
马的课程。 陈述我的偏见; 我是第一个开发者,所以我主要关心给用户一个工作的应用程序。
我已经在使用自然键的系统上工作了,而且不得不花费大量的时间来确保价值变化会波及整个市场。
我只使用代理键进行系统工作,唯一的缺点是缺less用于分区的非规格化数据。
大多数传统的PL / SQL开发人员都不喜欢代理键,因为每个连接的表的数量很多,但是我们的testing和生产数据库从来没有提出过汗水。 额外的连接不会影响应用程序的性能。 使用不支持子句的数据库方言,比如“Xa = Yb中的X内连接Y”,或者不使用该语法的开发人员,替代键的额外连接确实使查询难以阅读,检查:请参阅@托尼安德鲁斯后。 但是,如果您使用ORM或任何其他SQL生成框架,您将不会注意到它。 触摸input也可以缓解。
情况1:您的表是less于50个types的查找表 (插入)
使用业务/自然密钥 。 例如:
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)
在第一种情况下:
- 你可以select表PEOPLE中的所有程序员,而不使用与表JOB的连接,但只需要:“SELECT * FROM PEOPLE WHERE JOBCODE ='PRG'”
在第二种情况下:
- 您的数据库查询更快,因为您的主键是一个整数
- 你不需要打扰自己find下一个唯一的键,因为数据库本身给你下一个自动增量。
也许不完全相关的这个话题,但我有处理代理键头痛。 Oracle预先交付的分析会在仓库的所有维度表上创build自动生成的SK,并且还会将这些信息存储在事实中。 因此,随着新列的添加或维度中所有项目的填充,需要重新加载它们(维度)时,更新期间分配的SK会使SK与存储的事实的原始值不同步,迫使所有事实表的完整重新加载。 我宁愿即使SK是一个毫无意义的数字,也会有某种方式不能改变原来的/旧的logging。 众所周知,开箱即使很less满足组织的需求,我们也必须不断地进行定制。 我们现在在仓库中拥有3年的数据,从Oracle Financial系统完成的重新加载量非常大。 所以在我的情况下,它们不是从数据input生成的,而是添加到仓库中以帮助报告性能。 我明白了,但是我们的确改变了,这是一场噩梦。
在时间点数据库的情况下,最好是有代理和自然键的组合。 例如,您需要跟踪俱乐部的会员信息。 成员的一些属性永远不会改变。 例如出生date,但名字可以改变。 因此,使用member_id代理键创build成员表,并为DOB创build一个列。 创build另一个名为person name的表,并具有member_id,member_fname,member_lname,date_updated的列。 在这个表中,自然键是member_id + date_updated。