复合主键与唯一对象ID字段
我inheritance了一个数据库,这个数据库的构build思路是组合键比使用唯一的对象ID字段更理想,并且在构build数据库时, 永远不要将单个唯一ID用作主键。 因为我正在为这个数据库构build一个Rails前端,所以遇到困难使它符合Rails约定(虽然可以使用自定义视图和一些额外的gem来处理组合键)。
写这个特定模式devise背后的原因是,数据库如何以非高效的方式处理ID字段,以及何时build立索引,树的sorting是有缺陷的。 这个解释缺乏深度,我仍然试图围绕这个概念(我熟悉使用组合键,但不是百分之百的时间)的头脑。
任何人都可以提供意见或增加任何更深入的话题?
大多数常用引擎(MS SQL Server,Oracle,DB2,MySQL等)使用代理键系统时不会遇到明显的问题。 有些甚至可能会使用替代品来提升性能,但是性能问题是高度针对平台的。
总的来说,自然的关键(也就是说,复合关键词)与替代关键的辩论有着悠久的历史,看不到“正确的答案”。
自然键(单数或复合)的论据通常包括以下一些:
1) 它们已经在数据模型中可用。 build模的大多数实体已经包含了一个或多个属性或属性组合,这些属性或属性的组合可以满足关键字创build关系的需要。 为每个表添加额外的属性都会导致不必要的冗余。
2) 他们消除了某些联接的需要。 例如,如果客户拥有客户代码和发票编号(两者都是“自然”键)的发票,并且您希望检索特定客户代码的所有发票号码,则可以简单地使用"SELECT InvoiceNumber FROM Invoice WHERE CustomerCode = 'XYZ123'"
。 在经典的代理键方法中,SQL将如下所示: "SELECT Invoice.InvoiceNumber FROM Invoice INNER JOIN Customer ON Invoice.CustomerID = Customer.CustomerID WHERE Customer.CustomerCode = 'XYZ123'"
。
3) 他们有助于更普遍适用的数据build模方法。 使用自然键,相同的devise可以在不同的SQL引擎之间基本保持不变。 许多代理键方法使用特定的SQL引擎技术进行密钥生成,因此需要在不同平台上实现更专业化的数据模型。
代理键的参数往往围绕SQL引擎特定的问题:
1) 当业务需求/规则改变时,它们可以更容易地改变属性。 这是因为它们允许将数据属性隔离到单个表中。 对于不能有效实现标准SQL结构(如DOMAIN)的SQL引擎而言,这主要是一个问题。 当属性由DOMAIN语句定义时,可以使用ALTER DOMAIN语句在模式范围内对属性进行更改。 不同的SQL引擎对于修改域有不同的性能特征,一些SQL引擎根本不实现DOMAINS,所以数据build模者通过增加代理键来弥补这些情况,以提高对属性进行修改的能力。
2) 它们比自然键更容易实现并发。 在自然关键情况下,如果两个用户同时使用相同的信息集(如客户行),并且其中一个用户修改了自然键值,则第二个用户的更新将失败,因为它们的客户代码数据库中不再存在更新。 在代理键情况下,更新将成功处理,因为不可变的ID值用于标识数据库中的行,而不是可变的客户代码。 但是,允许第二次更新并不总是令人满意的 – 如果客户代码改变了,则可能不允许第二个用户继续进行更改,因为该行的实际“身份”已经改变 – 第二个用户可能正在更新错误的行。 代理键和自然键都不能解决这个问题。 全面的并发解决scheme必须在执行密钥之外解决。
3) 他们performance比自然键更好。 性能最直接受SQL引擎的影响。 由于SQL引擎的数据存储和检索机制,使用不同的SQL引擎在相同的硬件上实现的相同的数据库模式通常具有显着不同的性能特征。 有些SQL引擎非常接近平面文件系统,在同一个属性(如客户代码)出现在数据库模式的多个位置时,数据实际上是冗余存储的。 当需要对数据或模式进行更改时,SQL引擎的这种冗余存储可能会导致性能问题。 其他SQL引擎提供了数据模型和存储/检索系统之间更好的分离,允许更快地更改数据和模式。
4) 使用某些数据访问库和GUI框架,代理键function更好。 由于大多数代理键devise(例如:所有关系键都是整数)的同质性,数据访问库,ORM和GUI框架可以与信息一起工作,而不需要对数据有特殊的了解。 自然键由于其不同的性质(不同的数据types,大小等),不适用于自动或半自动工具包和库。 对于专门的场景,如embedded式SQL数据库,devise数据库时考虑到特定的工具包可能是可以接受的。 在其他情况下,数据库是企业信息资源,由多个平台,应用程序,报表系统和设备同时访问,因此在devise时侧重于任何特定的库或框架时不能正常工作。 另外,当引入下一个伟大的工具包时,devise用于使用特定工具包的数据库成为一个负担。
(显然)我倾向于自然键的一面,但我并不是那么狂热。 由于我所处的环境,我所帮助devise的任何给定的数据库可能会被各种应用程序所使用,我使用大部分数据build模的自然键,而很less引入代理。 但是,我并不想尝试重新实现使用代理的现有数据库。 代理键系统工作得很好 – 不需要改变已经运行良好的东西。
有一些优秀的资源讨论每种方法的优点:
http://www.google.com/search?q=natural+key+surrogate+key
我一直在开发数据库应用程序15年,我还没有遇到过一个非代理键比替代键更好的select的情况。
我并不是说这样的情况不存在,我只是说当你考虑实际开发一个访问数据库的应用程序的实际问题时,通常代理键的好处开始压倒非理论纯度 – 远程密钥。
主键应该是不变的,毫无意义的 ; 最终,非代理键通常会失败一个或两个要求
-
如果密钥不是恒定的,那么你将来的更新问题会变得相当复杂
-
如果钥匙不是毫无意义的,那么它更可能改变,即不是恒定的; 往上看
以一个简单的常见示例:一个库存项目表。 将物品编号(sku编号,条形码,零件代码或其他)作为主键可能是很诱人的,但是一年之后, 所有的物品编号都会改变,并且会留下一个非常混乱的更新 – 整体 -数据库问题…
编辑:还有一个比哲学更实际的问题。 在很多情况下,你会以某种方式find一个特定的行,然后再更新它或再次find它(或两者)。 使用组合键可以在WHERE子句中追踪更多的数据和更多的限制条件,以便重新查找或更新(或删除)。 其中一个关键细分也可能在此期间发生变化! 使用代理键,总是只保留一个值(代理ID),根据定义它不能改变,这大大简化了情况。
这听起来像创build数据库的人是在自然键的自然键侧与替代键辩论。
我从来没有听说过在ID领域有任何问题,但我也没有深入研究它。
我在代理键方面:使用代理键时重复性较低,因为您只在其他表中重复单个值。 由于人类很less手工join餐桌,所以我们不在乎是否是数字。 而且,由于只有一个固定大小的列在索引中查找,所以假设代理具有更快的主键查找时间是安全的。
使用“唯一(对象)ID”字段简化了连接,但是您应该旨在让其他(可能是复合)键仍然是唯一的 – 不要放松非空约束,并保持唯一的约束。
如果DBMS不能有效地处理唯一的整数,就会有很大的问题。 但是,同时使用“唯一(对象)ID”和其他关键字,对于索引而言,使用的空间比其他关键字更多,并且每个插入操作都有两个更新索引。 所以这不是免费赠品 – 但只要你保持原来的关键,那么你会没事的。 如果你消除了另一个关键,你打破了你的系统的devise; 所有的地狱最终都会破裂(你可能会或可能不会发现地狱破裂)。
我基本上是代理关键团队的成员,即使我理解和理解诸如JeremyDWill在这里提出的论点,我仍然在寻找“自然”键优于代理的情况。
处理这个问题的其他职位通常是指关系数据库理论和数据库性能。 另一个在这种情况下总是被遗忘的有趣的论点与表规范化和代码生产力有关 :
每次我创造一个桌子,我会失去时间
- 确定其主要关键及其物理特征(types,大小)
- 记住这些特点,每次我想在我的代码中引用它?
- 向团队中的其他开发人员解释我的PKselect?
我的回答不是所有这些问题:
- 在处理人员名单时,我没有时间去尝试找出“最好的主键”。
- 我不想记住我的“
computer
”表的主键是64个字符长的string(Windows是否接受计算机名称的许多字符?)。 - 我不想向其他开发人员解释我的select,他们中的一个最终会说:“是的,但是考虑到你必须在不同的域上pipe理计算机,这64个string是否允许你存储域名+计算机名称?“。
所以我在过去的五年中一直在用一个非常基本的规则工作:每个表(我们称之为“ myTable
”)都有其唯一标识types的第一个字段叫做“ id_MyTable
”。 即使这个表支持“多对多”的关系,比如“ ComputerUser
”表,其中' id_Computer
'和' id_User
'组合形成一个非常可接受的主键,我更喜欢创build这个' id_ComputerUser
'字段作为一个唯一的标识符,只是坚持规则。
主要优点是您不必关心在您的代码中使用主键和/或外键。 一旦你有表名,你知道PK的名字和types。 一旦知道数据模型中实现了哪些链接,就会知道表中可用外键的名称。
我不确定我的规则是否是最好的。 但它是一个非常有效的!
使用自然键会使用任何自动ORM作为持久层进行恶梦。 此外,多列的外键往往会彼此重叠,这将导致以OO方式导航和更新关系时出现更多问题。
你仍然可以在一个独特的约束转换自然键,并添加一个自动生成的ID; 这并不能解决外键的问题,但是这些都必须手工修改。 希望多列和重叠约束将是所有关系中的一小部分,所以你可以专注于最重要的重构。
自然PK有他们的动机和使用场景,并不是一件坏事(TM),他们只是倾向于与ORM不协调。
我的感觉是,作为任何其他的概念,自然的键和表规范化应该使用时,合理的,而不是盲目的devise约束
我将在这里变得简短而甜蜜:现在,复合主键并不好。 如果可以的话,添加代理任意键,并通过唯一约束来维护当前的键scheme。 ORM很高兴,你很高兴,原来的程序员不太高兴,但除非他是你的老板,否则他可以处理它。
…数据库如何以非高效的方式处理ID字段,以及何时build立索引,树sorting有缺陷…
这几乎肯定是无稽之谈,但可能与从不同会话中以高比例分配递增数字时的索引块争用问题有关。 如果是这样的话,那么REVERSE KEY索引可以帮助,尽pipe由于块分割algorithm的改变而牺牲了更大的索引尺寸。 http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/schema.htm#sthref998
去合成,特别是如果它帮助您的工具集更快速的发展。
开发新体系结构的一种实用方法是利用用于表格的替代键,其将包含数千个多列高度独特的logging和用于简短说明表的组合键。 我通常会发现,大学决定使用代理键,而现实世界的程序员更喜欢复合键。 你真的需要将正确的主键types应用到表中 – 而不仅仅是一种方式。
我不是一个有经验的人,但仍然赞成使用主键作为身份证这里是使用示例的解释..
外部数据的格式可能随时间而改变。 例如,你可能会认为一本书的ISBN将成为一本书中的一把好钥匙。 毕竟,ISBN是唯一的。 但是随着这本书的写作,美国的出版业正在为一个重大的变化做准备,因为所有的ISBN都增加了数字。 如果我们使用ISBN作为书籍表中的主键,则必须更新每一行以反映这一变化。 但是,我们会遇到另一个问题。 数据库中会有其他表通过主键引用books表中的行。 除非我们先通读并更新所有这些参考资料,否则我们不能更改书本表中的密钥。 这将涉及到放弃外键约束,更新表格,更新书籍表格,最后重新build立约束条件。 总而言之,这是一种痛苦。 如果我们将自己的内部价值作为主键,问题就会消失。 没有第三方可以随意来任意告诉我们改变我们的模式 – 我们控制自己的密钥空间。 如果像ISBN这样的东西需要改变,它可以改变,而不会影响数据库中的任何现有关系。 实际上,我们已经将这些行中来自外部数据表示的行编织在一起。
虽然解释是相当书籍化的,但我认为它以一种更简单的方式解释事情。
复合键可能是好的 – 它们可能会影响性能 – 但它们不是唯一的答案,就像独特的(代理)键不是唯一的答案一样。
我所关心的是select组合键的推理模糊。 对于任何技术来说,模糊性常常意味着缺乏理解 – 可能是遵循别人的指导方针,在书或文章中。
一个唯一的ID没有什么问题 – 事实上,如果你有一个应用程序连接到数据库服务器,并且你可以select使用哪个数据库,那么这些数据库都是好的,你可以用你的密钥做任何事情,不是真的受到太大的痛苦。
关于这个问题已经写了很多,因为没有单一的答案。 有一些方法和方法需要熟练地应用。
由于数据库自动提供ID,我遇到了很多问题 – 我尽可能避免使用这些ID,但偶尔也会使用它们。
@JeremyDWill
感谢您为辩论提供一些急需的平衡。 特别感谢DOMAIN
的信息。
为了保持一致性,我实际上在系统范围内使用了代理键,但是也存在一些折衷。 我使用代理键进行诅咒的最常见的原因是当我有一个查询表时,有一个简短的规范值列表 – 我会使用更less的空间,如果我刚刚创build值,我所有的查询将更短/更容易/更快而不是必须join到表中。
您可以同时执行这两个操作 – 因为任何大公司数据库都可能被多个应用程序使用,包括运行一次性查询和数据导入的人员DBA,因此纯粹为了ORM系统的利益而进行devise并不总是切实可行的。
我最近倾向于为每个表添加一个“RowID”属性 – 这个字段是一个GUID,对每一行都是唯一的。 这不是主键 – 这是一个自然的关键(如果可能的话)。 但是,任何在此数据库之上工作的ORM层都可以使用RowID来标识其派生对象。
因此你可能有:
CREATE TABLE dbo.Invoice( CustomerId varchar(10), CustomerOrderNo varchar(10), InvoiceAmount不为空, 评论nvarchar(4000), RowId uniqueidentifier不为null默认(newid()), 主键(CustomerId,CustomerOrderNo) )
所以你的DBA很高兴,你的ORM架构师很高兴,并且你的数据库完整性被保留了!
我只是想在这里添加一些我在讨论关系数据库自动生成的整数标识字段时看不到的东西(因为我看到它们很多),也就是说,它的基types可能会在某个时候溢出。
现在我没有试图说这会自动使组合ID成为可能,但事实上,即使更多的数据可以逻辑地添加到表中(仍然是唯一的),单个自动生成的整数身份可以防止这种情况发生。
是的,我意识到,在大多数情况下,这是不可能的,使用64位整数给你很大的空间,现实的数据库可能应该有不同的devise,如果这样的溢出发生。
但是,这并不妨碍某人这样做…一个使用一个自动生成的32位整数的表格,因为它的身份,预计将存储在一个特定的快餐公司在全球范围内的所有交易,正在失败一旦它试图插入它的2147483648交易(这是一个完全可行的场景)。
这只是需要注意的一点,人们往往忽略或完全忽略。 如果要定期插入任何表格,则应该考虑随着时间的推移多less次数据的累积以及是否应该使用基于整数的标识符。