关于数据库:代理与自然/商业密钥

Surrogate vs. natural/business keys

我们再来一次,旧的论点仍然出现......

我们是否更好地将业务密钥作为主键,或者我们是否更愿意在业务键字段上具有唯一约束的代理ID(即SQL Server标识)?

请提供支持您的理论的示例或证明。


使用代理键的几个原因:

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

  • 约定:允许您具有标准化的主键列命名约定,而不必考虑如何连接具有各种PK名称的表。

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


  • 都。吃蛋糕然后吃。

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

    如果使用代理键,则仍需要业务键以确保根据业务规则的唯一性。


    似乎没有人说过支持非代理人(我犹豫地说"自然")键的任何东西。所以这里......

    代理键的缺点是它们没有意义(被一些人称为优势,但......)。这有时会迫使您在查询中加入更多表,而不是真正需要的表。相比:

    1
    2
    3
    4
    5
    6
    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';

    反对:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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';

    除非有人认真考虑以下是个好主意?:

    1
    2
    3
    4
    5
    6
    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和Zip代码。我肯定会对像Person,Address这样的表使用一个没有意义的数字键 - 但不是所有的东西,由于某种原因,这里的大多数人似乎都提倡。

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


    代理密钥永远不会有改变的理由。我不能对自然键说同样的话。姓氏,电子邮件,ISBN nubmers - 他们都可以改变一天。


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

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

    想想公司:你是一个快乐的Merkin公司,与Merkia的其他公司做生意。您很聪明,不要使用公司名称作为主键,因此您可以使用Merkia政府独有的公司ID,其中包含10个字母数字字符。
    然后Merkia更改公司ID,因为他们认为这是一个好主意。没关系,您可以使用数据库引擎的级联更新功能,以便进行首先不应涉及的更改。之后,您的业务扩展,现在您与弗里多尼亚的一家公司合作。 Freedonian公司ID最多16个字符。您需要扩大公司ID主键(也包括Orders,Issues,MoneyTransfers等中的外键字段),在主键中添加Country字段(也在外键中)。哎哟!在弗里多尼亚的内战中,它分裂在三个国家。您的员工的国家/地区名称应更改为新的国家/地区名称;级联更新救援。顺便说一下,你的主要钥匙是什么? (国家,公司ID)或(公司ID,国家)?后者有助于加入,前者避免使用另一个索引(或者许多,如果您希望按国家/地区分组您的订单)。

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


    我一般都讨厌代理密钥。只有在没有可用的优质自然键时才能使用它们。当你想到它时,相当荒谬,认为在你的桌子上添加无意义的数据可以让事情变得更好。

    这是我的理由:

  • 使用自然键时,表格会以最常搜索的方式进行聚类,从而加快查询速度。

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

  • 当代理键用作主键时,自然主键的含义就不太清楚了。开发时,您想知道哪些列使表格独特。

  • 在一对多关系链中,逻辑密钥链。例如,组织有许多帐户,帐户有许多发票。所以Organization的逻辑关键是OrgName。 Accounts的逻辑密钥是OrgName,AccountID。 Invoice的逻辑密钥是OrgName,AccountID,InvoiceNumber。

    当使用代理键时,通过仅具有直接父级的外键来截断密钥链。例如,Invoice表没有OrgName列。它只有AccountID列。如果要搜索给定组织的发票,则需要加入组织,帐户和发票表。如果使用逻辑密钥,则可以直接查询组织表。

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

  • 我有三本不同的数据库书籍。其中没有一个显示使用代理键。


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

    由于似乎许多人将代理键作为瘟疫的近乎完美的解决方案和自然键,我将重点关注另一种观点的论点:

    代理键的缺点

    代理键是:

  • 性能问题的根源:

    • 它们通常使用自动增量列来实现,这意味着:

      • 每次想要获得新Id时往返数据库(我知道这可以使用缓存或[seq] hilo类似的算法进行改进,但这些方法仍有其自身的缺点)。
      • 如果有一天你需要将数据从一个模式移动到另一个模式(至少在我的公司中经常发生)那么你可能会遇到Id碰撞问题。是的我知道您可以使用UUID,但这些持续时间需要32个十六进制数字! (如果您关心数据库大小,那么它可能是一个问题)。
      • 如果您对所有代理键使用一个序列,那么 - 当然 - 您最终会在数据库上发生争用。
  • 容易出错。序列具有max_value限制,因此 - 作为开发人员 - 您必须注意以下几点:

    • 你必须循环你的序列(当达到最大值时,它会回到1,2,......)。
    • 如果您使用序列作为数据的排序(随着时间的推移),那么您必须处理循环的情况(具有Id 1的列可能比具有Id max-value的行更新 - 1)。
    • 确保您的代码(甚至您的客户端接口不应该发生,因为它应该是内部Id)支持您用于存储序列值的32b / 64b整数。
  • 它们不保证不重复的数据。您始终可以拥有包含所有相同列值但具有不同生成值的2行。对我来说,从数据库设计的角度来看,这是代理键的问题。
  • 更多维基百科...
  • 自然键上的神话

  • 复合键的效率低于代理键。没有!这取决于使用的数据库引擎:

    • 神谕
    • MySQL的
  • 现实生活中不存在自然键。抱歉,但确实存在!例如,在航空业中,对于给定的预定航班(航空公司,departureDate,flightNumber,operationalSuffix),以下元组将始终是唯一的。更一般地,当通过给定标准保证一组业务数据是唯一的时,则该组数据是[好]自然密钥候选者。
  • 自然键"污染子表的模式"。对我来说,这更像是一种感觉而非一种真正的问题。具有2个字节的4列主键每个可能比11个字节的单个列更有效。此外,4列可用于直接查询子表(通过使用where子句中的4列)而无需连接到父表。
  • 结论

    在与此相关时使用自然键,并在最好使用时使用代理键。

    希望这有助于某人!


    总是使用没有商业意义的密钥。这只是一种很好的做法。

    编辑:我试图在网上找到它的链接,但我不能。然而,在"企业架构模式"[Fowler]中,它有一个很好的解释,为什么你不应该使用除了钥匙之外没有任何意义的钥匙。归结为它应该只有一份工作和一份工作。


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

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

    我很喜欢在适当时使用uids作为代理键。与他们的主要胜利是你提前知道钥匙,例如你可以创建一个已经设置了ID的类的实例,并保证它是唯一的,而使用一个整数键,你需要默认为0或-1,并在保存/更新时更新为适当的值。

    UID在查找和加入速度方面有惩罚,因此它取决于所讨论的应用程序是否可取。


    在我看来,使用代理键更好,因为它没有变化的可能性。几乎任何我可以想到你可以用作自然键的东西都可以改变(免责声明:并非总是如此,但通常都是如此)。

    一个例子可能是汽车数据库 - 乍一看,您可能会认为车牌可以用作钥匙。但这些可以改变,所以这是一个坏主意。你不会真的想在发布应用程序后找到它,当有人来找你想要知道为什么他们不能将他们的号牌改为他们闪亮的新个性化的。


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

    然后,根据需要,将业务键堆叠为唯一约束或索引。这将使您的数据完整性保持不变。

    业务逻辑/自然键可以改变,但表的物理键永远不会改变。


    在数据仓库方案中,我认为最好遵循代理关键路径。两个原因:

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

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

    使用商业/自然键。
    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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:您的表是一个包含数千个插入的表

    使用代理/自动增量键。例如:

    1
    2
    3
    4
    5
    6
    7
    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)

    在第一种情况下:

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

    在第二种情况:

    • 您的数据库查询更快,因为您的主键是整数
    • 您不需要为找到下一个唯一键而烦恼,因为数据库本身会为您提供下一个自动增量。

    提醒一下,将聚簇索引放在随机代理键(即读取XY8D7-DFD8S的GUID)上并不是一种好的做法,因为SQL Server无法对这些数据进行物理排序。您应该在这些数据上放置唯一索引,尽管为主表操作简单地运行SQL分析器然后将这些数据放入数据库引擎优化顾问也是有益的。

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


    当业务信息可以更改或相同时,代理键可能很有用。毕竟,商业名称在全国范围内不一定是唯一的。假设您涉及两家名为Smith Electronics的企业,一家在堪萨斯州,一家在密歇根州。您可以通过地址区分它们,但这会改变。甚至国家也可以改变;如果堪萨斯州堪萨斯城的史密斯电子公司穿越河流到密苏里州堪萨斯城怎么办?没有明显的方法可以保持这些业务与自然密钥信息不同,因此代理密钥非常有用。

    将代理键视为ISBN号。通常,您按标题和作者识别书籍。然而,我有两本由H. P. Willmott撰写的题为"珍珠港"的书,他们肯定是不同的书,而不仅仅是不同的版本。在这样的情况下,我可以参考书籍的外观,或者早期与后期,但同样我也可以使用ISBN。


    这是代理键几乎总是有意义的情况之一。在某些情况下,您要么选择最适合数据库的内容,要么选择最适合您的对象模型的内容,但在这两种情况下,使用无意义的密钥或GUID都是更好的主意。它使索引更容易,更快,并且它是您的对象的标识,不会改变。


    也许与这个话题并不完全相关,但令人头疼的是我处理了代理键。 Oracle预交付的分析在仓库中的所有维度表上创建自动生成的SK,并且还将这些SK存储在事实中。因此,只要在添加新列或需要为维度中的所有项目填充时需要重新加载它们(维度),在更新期间分配的SK会使SK与存储到事实的原始值不同步,强制完全重新加载加入它的所有事实表。我更希望即使SK是一个毫无意义的数字,也会有某种方式它无法改变原始/旧记录。众所周知,开箱即用很少满足组织的需求,我们必须不断定制。我们现在在仓库中拥有3年的数据,Oracle Financial系统的完全重新加载非常庞大。因此,就我而言,它们不是通过数据输入生成的,而是添加到仓库中以帮助报告性能。我明白了,但是我们确实改变了,这是一场噩梦。


    马课程。陈述我的偏见;我首先是开发人员,所以我主要关注为用户提供有用的应用程序。

    我一直在使用自然键进行系统工作,并且不得不花费大量时间确保价值变化会波及。

    我一直在使用只有代理键的系统,唯一的缺点是缺少用于分区的非规范化数据。

    我使用过的大多数传统PL / SQL开发人员都不喜欢代理键,因为每个连接的表数量都很多,但是我们的测试和生产数据库从来没有出汗过;额外的连接不会影响应用程序的性能。对于不支持"Xa = Yb上的X内连接Y"等子句的数据库方言,或者不使用该语法的开发人员,代理键的额外连接会使查询更难以阅读,并且更长时间来键入和检查:看@Tony安德鲁斯的帖子。但是,如果您使用ORM或任何其他SQL生成框架,您将不会注意到它。触摸打字也可以缓解。


    在时间点数据库的情况下,最好具有代理和自然键的组合。例如您需要跟踪俱乐部的会员信息。成员的某些属性永远不会改变。例如出生日期,但名称可以更改。
    因此,使用member_id代理键创建一个Member表,并为DOB创建一列。
    创建另一个名为person name的表,并为member_id,member_fname,member_lname,date_updated提供列。在此表中,自然键将是member_id + date_updated。