复合主键与唯一对象ID字段
我继承了一个数据库,该数据库的构想是复合键比使用唯一的对象ID字段更理想,并且在构建数据库时,永远不要将单个唯一的ID用作主键。因为我正在为此数据库构建Rails前端,所以我遇到了使其符合Rails约定的困难(尽管可以使用自定义视图和一些其他gem来处理复合键)。
编写此特定模式的人背后的推理,与数据库如何以非有效方式处理ID字段以及在构建索引时,树排序有缺陷有关。这种解释没有任何深度,我仍在尝试围绕这个概念(我熟悉使用复合键,但并非100%的时间)。
任何人都可以对此主题发表意见或者增加深度吗?
解决方案
使用"唯一(对象)ID"字段可简化联接,但我们应力争使另一个(可能是复合键)仍然唯一-不要放宽非空约束,并要保持唯一约束。
如果DBMS无法有效处理唯一整数,则存在很大的问题。但是,同时使用"唯一(对象)ID"和另一个键确实比另一个键占用更多的空间(用于索引),并且每个插入操作都有两个索引要更新。因此,这不是免费赠品-但只要我们也维护原始密钥,就可以了。如果我们取消其他键,则将破坏系统的设计;否则,我们将无法使用它。最终所有地狱都会崩溃(并且我们可能会也可能不会发现地狱崩溃了)。
听起来好像创建数据库的人在大自然键与替代键之争的自然键方面。
我从未听说过ID字段上的btree有任何问题,但我也没有进行任何深入的研究...
我属于代理键方面:使用代理键时,重复次数较少,因为我们仅在其他表中重复了一个值。由于人类很少手动加入桌子,因此我们不在乎它是否是数字。另外,由于在索引中只需要查找一个固定大小的列,因此可以安全地假设代理也通过主键具有更快的查找时间。
使用自然键会使使用任何自动ORM作为持久层的噩梦。同样,多列上的外键往往会相互重叠,这在以OO方式导航和更新关系时会带来进一步的问题。
我们仍然可以在唯一约束中转换自然键并添加自动生成的ID;但这并不能消除外键的问题,但是必须手动更改这些外键。希望多列和重叠约束将是所有关系中的一小部分,因此我们可以集中精力在最重要的地方进行重构。
自然的pk有其动机和使用场景,并且不是一件坏事(tm),他们只是往往与ORM相处得不好。
我的感觉是,与其他任何概念一样,在合理的情况下应使用自然键和表规范化,而不应将其作为盲目的设计约束
我从事数据库应用程序开发已有15年了,但我还没有遇到过这样一个案例:非代理键比代理键是更好的选择。
我并不是说这种情况不存在,我只是说,当我们考虑实际开发访问数据库的应用程序的实际问题时,通常,代理键的好处开始压倒了non的理论纯度。 -代理键。
主键应该是恒定且无意义的;非代理密钥通常不能满足一项或者两项要求,最终
- 如果密钥不是恒定的,那么将来会有一个更新问题,可能会变得非常复杂
- 如果密钥不是无意义的,则它更有可能改变,即不是恒定的;看上面
举一个简单的常见示例:一个清单项目表。将项目编号(SKU编号,条形码,部件代码或者其他)作为主键可能很诱人,但是一年之后,所有项目编号都发生了变化,我们将获得非常混乱的"整个更新"数据库问题...
编辑:还有一个比哲学更实际的问题。在许多情况下,我们将以某种方式找到特定的行,然后再对其进行更新或者再次查找(或者同时查找)。使用组合键,可以在WHERE子句中跟踪和更新更多数据,以进行重新查找或者更新(或者删除)。同时,其中一个关键段也可能已更改!使用代理密钥,总是只保留一个值(代理ID),并且根据定义,它不能更改,从而大大简化了这种情况。
复合键可能会影响性能,但它并不是唯一的答案,这与唯一(替代)键不是唯一的答案非常相似。
使我担心的是选择组合键的原因中的含糊不清。对任何技术的模糊不清往往表示缺乏理解,也许是在书或者文章中遵循了别人的指导...。
如果我们已经将一个应用程序连接到数据库服务器,并且可以选择要使用的数据库,那么唯一的唯一ID实际上并没有什么问题,我们几乎可以使用任何键来执行操作真的受不了了
关于这个问题,已经有并且将会有很多文章,因为没有单一的答案。有一些方法和方法需要以熟练的方式仔细地应用。
我在数据库自动提供ID方面遇到很多问题,我会尽可能避免使用它们,但仍会偶尔使用它们。
在这里,我将简短有趣:组合主键现在已经不好了。如果可以,请添加替代密钥,并通过唯一约束维护当前密钥方案。 ORM很高兴,我们很高兴,原来的程序员不太高兴,但是除非他是老板,否则他就可以解决。
... how the database handles ID fields in a non-efficient manner and when it's building indexes, tree sorts are flawed ...
几乎可以肯定这是胡说八道,但是可能与在不同会话中以较高的速率向PK分配递增编号时的索引块争用问题有关。如果是这样,那么REVERSE KEY索引可以为我们提供帮助,尽管由于块分割算法的更改而导致索引大小变大。 http://download.oracle.com/docs/cd/B19306_01/server.102/b14220/schema.htm#sthref998
进行合成,特别是如果它有助于工具集更快地进行开发。
使用代理密钥系统,大多数常用引擎(MS SQL Server,Oracle,DB2,MySQL等)不会遇到明显的问题。有些人甚至可以通过使用代理来提高性能,但是性能问题是特定于平台的。
一般而言,自然键(以及扩展为复合键)与替代键的辩论历史悠久,没有明显的正确答案。
自然键(单数或者复合键)的参数通常包括以下内容:
1)它们已经在数据模型中可用。已建模的大多数实体已经包括一个或者多个属性或者属性组合,这些属性可以满足出于创建关系的目的而需要使用的键。向每个表添加其他属性会合并不必要的冗余。
2)他们消除了某些联接的需要。例如,如果客户具有客户代码,并且发票具有发票编号(两者都是"自然"键),并且想要检索特定客户代码的所有发票编号,则只需使用"" SELECT InvoiceNumber在发票上,其中CustomerCode ='XYZ123'"`。在经典的代理键方法中,SQL看起来像这样:""从发票内联接中选择客户Invoice.InvoiceNumber Invoice.CustomerID = Customer.CustomerID WHERE Customer.CustomerCode ='XYZ123'"。
3)它们为数据建模提供了一种更为通用的方法。使用自然键,可以在不同的SQL引擎之间大体上不变地使用相同的设计。许多代理密钥方法使用特定的SQL引擎技术来生成密钥,因此需要对数据模型进行更多的专业化处理才能在不同的平台上实现。
代理键的争论通常围绕SQL引擎特定的问题展开:
1)当业务需求/规则更改时,它们使属性更改变得更容易。这是因为它们允许将数据属性隔离到单个表中。对于没有有效实现标准SQL结构(例如DOMAIN)的SQL引擎,这主要是一个问题。当通过DOMAIN语句定义属性时,可以使用ALTER DOMAIN语句在架构范围内对属性进行更改。不同的SQL引擎对于更改域具有不同的性能特征,并且某些SQL引擎根本不实现DOMAINS,因此数据建模人员通过添加代理键来提高对属性进行更改的能力,从而弥补了这些情况。
2)与自然键相比,它们使并发的实现更容易。在自然键情况下,如果两个用户同时使用同一信息集(例如客户行),并且其中一个用户修改了自然键值,则第二个用户的更新将失败,因为他们是客户代码数据库中不再存在更新。在代理键的情况下,更新将成功处理,因为不可变的ID值用于标识数据库中的行,而不是可变的客户代码。但是,如果客户代码已更改,则并不总是希望进行第二次更新,因为该行的实际身份已更改,第二个用户可能正在更新错误,因此有可能不应该允许第二个用户继续其更改排。代理键或者自然键都无法单独解决此问题。全面的并发解决方案必须在密钥的实现之外解决。
3)它们的性能比自然键好。性能最直接受到SQL引擎的影响。由于SQL引擎的数据存储和检索机制,在使用不同SQL引擎的同一硬件上实现的同一数据库架构通常会具有截然不同的性能特征。一些SQL引擎非常接近平面文件系统,当相同的属性(例如客户代码)出现在数据库模式中的多个位置时,实际上会冗余地存储数据。当需要对数据或者架构进行更改时,SQL引擎的这种冗余存储可能会导致性能问题。其他SQL引擎在数据模型与存储/检索系统之间提供了更好的隔离,从而可以更快地更改数据和架构。
4)代理键在某些数据访问库和GUI框架中的功能更好。由于大多数代理键设计的同类性质(例如:所有关系键都是整数),数据访问库,ORM和GUI框架可以使用该信息,而无需对数据有特殊的了解。由于自然键的异构性质(不同的数据类型,大小等),自然键在自动或者半自动工具箱和库中无法正常工作。对于诸如嵌入式SQL数据库之类的特殊方案,可以在考虑特定工具箱的情况下设计数据库。在其他情况下,数据库是企业信息资源,可以同时由多个平台,应用程序,报表系统和设备访问,因此,当设计针对任何特定的库或者框架时,数据库也无法正常运行。此外,当引入下一个出色的工具箱时,旨在与特定工具箱一起使用的数据库将成为一种责任。
我倾向于倾向于自然键(显然),但是我并不狂热。由于我工作的环境的原因,我帮助设计的任何给定数据库都可以由各种应用程序使用,因此我将自然键用于大多数数据建模,而很少引入替代方法。但是,我不会竭尽全力尝试重新实现使用代理的现有数据库。代理密钥系统可以很好地工作,而无需更改已经运行良好的功能。
有一些出色的资源讨论了每种方法的优点:
http://www.google.com/search?q=natural+key+surrogate+key
http://www.agiledata.org/essays/keys.html
http://www.informationweek.com/news/software/bi/201806814
@JeremyDWill
感谢我们为辩论提供一些急需的平衡。特别感谢我们提供有关" DOMAIN"的信息。
为了保持一致性,我实际上在整个系统范围内都使用代理密钥,但是要权衡利弊。我使用代理键进行诅咒的最常见原因是当我有一个包含规范值简短列表的查找表时我将使用较少的空间,并且如果我只是将值设置为PK,我的所有查询都将更短/更轻松/更快。无需加入表格。
我基本上是代理密钥团队的成员,即使我理解并理解JeremyDWill此处提出的论点,我仍在寻找"自然"密钥比代理更好的情况...
处理此问题的其他帖子通常涉及关系数据库理论和数据库性能。在这种情况下始终被遗忘的另一个有趣的参数与表规范化和代码生产率有关:
每次创建表格时,我都应该
失去时间
- 识别其主键及其物理特性(类型,大小)
- 每次想在代码中引用这些特性时,还记得这些特性吗?
- 向团队中的其他开发人员解释我的PK选择?
我对所有这些问题的回答都是"否":
- 在与人员名单打交道时,我没有浪费时间尝试确定"最佳主键"。
- 我不想记住我的"计算机"表的主键是64个字符长的字符串(Windows是否接受这么多字符作为计算机名?)。
- 我不想向其他开发人员解释我的选择,他们中的一个最终会说"是的,但是我们认为我们必须管理不同域的计算机?这64个字符的字符串是否允许我们存储域名+计算机名称?"。
因此,过去五年来我一直遵循一个非常基本的规则:每个表(我们称其为" myTable")都有其第一个字段" id_MyTable",该字段具有uniqueIdentifier类型。即使此表支持"多对多"关系,例如" ComputerUser"表,其中" id_Computer"和" id_User"的组合构成了一个非常可接受的主键,我还是更喜欢创建此" id_ComputerUser"字段作为uniqueIdentifier,仅用于遵守规则。
主要优点是我们不必关心代码中主键和/或者外键的使用。获得表名后,便知道PK名称和类型。一旦知道了数据模型中实现了哪些链接,便会知道表中可用外键的名称。
我不确定我的规则是最好的。但这是一个非常有效的!
开发新体系结构的一种实用方法是利用表的替代键,该键将包含成千上万个多列的高度唯一记录和简短描述表的组合键。我通常会发现大学决定使用代理键,而现实世界中的程序员更喜欢使用组合键。我们确实需要将正确的主键类型应用于表,而不仅仅是一种方法。
我们可以同时执行这两种操作,因为任何大型公司数据库都可能会被多个应用程序使用,包括运行一次性查询和数据导入的人工DBA,因此仅出于ORM系统的利益而设计数据库并不总是可行或者理想的。
这些天,我倾向于为每个表添加一个" RowID"属性,该字段是GUID,因此每一行都是唯一的。这不是作为自然键的主键(如果可能)。但是,在此数据库之上工作的任何ORM层都可以使用RowID来标识其派生对象。
因此,我们可能具有:
CREATE TABLE dbo.Invoice ( CustomerId varchar(10), CustomerOrderNo varchar(10), InvoiceAmount money not null, Comments nvarchar(4000), RowId uniqueidentifier not null default(newid()), primary key(CustomerId, CustomerOrderNo) )
因此,DBA很高兴,ORM架构师也很高兴,并且数据库完整性得到了保留!
我不是一个有经验的人,但是我仍然赞成使用主键作为id,这里是使用示例的说明。
外部数据的格式可能会随时间变化。例如,我们可能认为一本书的ISBN将成为一本书的主键。毕竟,ISBN是唯一的。但是随着本书的写作,美国的出版业正为重大变化做准备,因为在所有ISBN中都添加了额外的数字。
如果星期三使用ISBN作为书籍表中的主键,则星期三必须更新每一行以反映此更改。但是,然后结婚还有另一个问题。数据库中还会有其他表通过主键引用books表中的行。除非先阅读并更新所有这些参考,否则我们无法更改books表中的键。这将涉及删除外键约束,更新表,更新books表以及最后重新建立约束。总而言之,这很痛苦。
如果我们使用自己的内部值作为主键,问题就会消失。任何第三方都不能随便告诉我们更改架构,而是控制我们自己的键空间。而且,如果确实需要更改ISBN之类的内容,则可以更改它而不会影响数据库中的任何现有关系。实际上,我们已经将行的编织与这些行中数据的外部表示分离了。
尽管解释是很活泼的,但是我认为它以一种更简单的方式解释了事情。