原文地址:http://www.agiledata.org/essays/keys.html

本文概述关系数据库中为表指定主键的策略。主要关注于何时使用自然键或者代理键的问题。有些人会告诉你应该总是使用自然键,而另外一些人会告诉你应该总是使用代理键。这些人总是被证明是错误的,通常他们仅仅是与你分享了他们“数据信仰”的偏见。事实上自然键与代理键具有各自的优缺点,没有在所有情况下都完美的策略。也就是说,你必须清楚你要做的事情才能做好它。本文讨论以下内容:

  • 常用的术语
  • 选择键的策略
  • 代理键实现策略
  • 有效选键的技巧
  • 做出了“错误”选择时如何应对

1 常用的术语

让我们从描述一些关于键的常用术语开始,然后再看一个示例。这些术语包括:

  • 。键是唯一标识一个实体的一个或者多个数据属性。在物理数据库中,键可以由表的一个或者多个列组成,它们的值唯一标识关系表中的一行。
  • 复合键。由两个或者多个属性组成的键。
  • 自然键。由现实世界中已经存在的属性组成的键。例如,美国公民被分配了一个唯一(不保证一定正确,但实际上非常接近唯一)的社会保险号(SSN)。如果隐私法允许的话,SSN可能被用作Person实体的自然键(假设组织的人员范围仅限于美国)。
  • 代理键。不具有业务含义的键。
  • 候选键。在逻辑数据模型中的实体类型可能具有0或多个候选键,简称为唯一标识(注解:某些人不主张在逻辑数据模型中标识候选键,因此没有固定标准)。例如,假设我们只涉及美国公民,那么SSN是Person实体类型的一个候选键,同时姓名与电话号码的组合(假设组合是唯一的)是第二个可能的候选键。这两个键都被称作候选键是因为它们是物理数据模型中主键、次键或者非键的候选目标。
  • 主键。实体类型的首选键。
  • 备用键。也就是次键,是表中行的另一个唯一标识。
  • 外键。在一个实体类型中表示另一个实体类型的主键或者次键的一个或多个属性。

图1显示了使用UML符号描述的实际地址的物理数据模型。在图1中,表Customer使用CustomerNumber作为主键而SocialSecurityNumber作为备用键。这就意味着访问顾客信息的首先方法是通过一个人的顾客编号,虽然软件使用社会保险号也能够获得相同的信息。表CustomerHasAddress拥有一个复合键,由CustomerNumber与AddressID组成。外键是一个实体类型中,代表另一个实体类型的主键或者次键的一个或多个属性。外键用于维护数据行之间的关联。例如,表CustomerHasAddress与表Customer中行之间的关联通过表CustomerHasAddress的CustomerNumber列进行维护。有趣的是列CustomerNumber既是表CustomerHasAddress主键的一部分,又是表Customer的外键。同样,列AddressID既是表CustomerHasAddress主键的一部分,又是表Address的外键,维护这两个表中行的关联。

图1 一个包含Customer与Address的简单PDM

2 比较自然键与代理键策略

为表指定键的策略有两种:

  1. 自然键。自然键是已经存在的一个或多个属性,它在业务概念中是唯一的。对于表Customer来说,存在两个候选键,CustomerNumber与SocialSecurityNumber。
  2. 代理键。引入一个不具有业务含义的列作为键,称作代理键。例如图1中表Address的列AddressID。地址不具有一个“简单”的自然键,因为需要使用Address表的所有列组成一个键(取决于你的问题域,可能仅仅需要组合Street和ZipCode列),所以此时引入一个代理键是一个更好的选择。

自然键的优点是它们已经存在,不需要在数据模式中引入一个新的“非自然”列。然而,自然键的缺点是由于具有业务含义,它们与业务直接耦合:你可能在业务需求变更时重新指定键。例如,当你的用户决定将CustomerNumber列从数字型改为字母数字型,除了更新表Customer的模式(这个是不可避免的)外,你还需要修改每一个使用CustomerNumber作为外键的表。

代理键具有几个优点。首先,它们不与业务耦合,因此更容易维护(假设你选择了一个好的实现策略)。例如,如果表Customer改为使用代理键,修改只需要在表Customer内部进行(此时CustomerNumber只是表的一个非键列)。当然,如果你需要针对代理键策略做相似的变更,可能是由于用完了所有的值而需要增加几个位数,将会面临同样的问题。其次,一个大多数表,最好是全部表,通用的键策略能够减少需要编写的源码数量,减少系统的总体拥有成本(TCO)。代理键根本的缺点是它们通常不是“人可读的”,导致终端用户难以使用。这意味着你可能仍然需要实现代理键用于查找、编辑等等。

根本问题在于键是关系模式中重要的耦合源,因此它们很难更改。这意味着你通常想要避免具有业务含义的键,因为业务含义存在变化。话虽如此,我倾向于使用自然键查找/引用表,尤其当我认为键值在最近不会改变时,如下文所述。从根本上讲,是否应该优先使用自然键没有明确的答案,不管这个宗教之争的另外一方狂热者如何声称,最好的策略是只要有意义就可以使用任何一个策略。

3 代理键实现策略

实现代理键有几个常用的选择:

  1. 使用数据库赋值。大多数主要的数据库供应商--例如Oracle、Sybase以及Informix--实现了被称为递增键的代理键策略。基本理念是在数据库服务器中维护一个计数器,将当前值写入一个隐藏的系统表来维护一致性,并用于赋值一个新建的数据行。每创建一行,计数器递增并将值作为该行的键值。不同供应商的实现策略不同,有时候值在所有表之间都是唯一的,有时候只在单个表内部是唯一的,但是基本概念相同。
  2. MAX() + 1。一个常用的策略是使用整数列,第一条记录从1开始,然后新行的值设置为该列的最大值加1,最大值用SQL函数MAX获得。虽然这个方法简单,但是对于大表存在性能问题,而且它只能确保表内部的唯一键值。
  3. 全局唯一标识符(UUIDs)。GUIDs是128位值,来自以太网卡ID或等价的软件表示以及系统当前时间的哈希值。该算法是由开放软件基金会定义的。
  4. 全球唯一标识(GUIDs)。GUIDs是微软扩展UUIDs后的标准,遵从相同的策略,如果存在以太网卡使用网卡ID,如果不存在,使用软件ID与当前时间计算一个哈希值,确保在机器内部唯一。
  5. 高低位策略。它的基本思想是键值,通常称为持久化对象标识符(POID)或者简称对象标识符(OID),分为两个逻辑部分:从指定来源获取的唯一HIGH值和应用自身分配的N为LOW值。每获取一个HIGH值,LOW值设置为0。例如,应用请求一个HIGH值并被赋予1701。假设LOW值的位数N为4,那么赋予对象的POID将会由17010000、17010001、17010002等等直到17019999组成。此时,再获取一个新的HIGH值,LOW值设置为0,再次重复。如果另一个应用在之后立即请求了一个HIGH值,它将获得1702,而它创建的对象被赋予OIDs将会是17020000、17020001等等。正如你所看到的,只要HIGH值唯一,所有的POID值将会唯一。在www.theserverside.com上可以找到一个HIGH-LOW发生器的实现。

根本问题在于键是关系模式中重要的耦合源,因此它们很难重构。这意味着你通常想要避免具有业务含义的键,因为业务含义存在变化。然而,同时你需要记住某些数据通常是通过唯一标识进行访问,例如通过顾客编号访问顾客信息,通过社会保险号访问美国雇员信息。在这种情况下你可能想要使用自然键而不是UUID或者POID这样的代理键。

4 有效选键技巧

如何有效地选择键?参考以下提示:

  1. 避免“智能”键。“智能”键是由一个或多个具有业务含义的部分组成的键。例如,美国邮政编码的前两位表示它所在的州。智能键的第一个问题是它具有业务含义。其次是它们的使用通常随着时间变得很复杂。例如,一些大的州拥有多个代码,加利福尼亚的邮编以90和91开头,导致基于州编码的查询更加复杂。第三个问题是它们通常增加了策略需要进行扩展的可能性。考虑长度为9位数字的邮编(后4位数字由建筑所有者自行决定,建筑由邮编唯一标识),在用完2位州代码前用完9位数字的可能性更小。
  2. 考虑为简单的“查找”表指定自然键。“查找”表是用于关联代码与详细信息的表。例如,你可能拥有一个列出了颜色代码对应颜色名称的查找表。例如,代码127代表“郁金香黄色”。简单的查找表通常包含一个代码列和一个描述/名称列,而复杂的查找表包含一个代码列和几个信息列。
  3. 自然键并非总是适用于“查找”表。另一个例子是一个查找表包含北美洲的州、省或者地区。例如美国的加利福尼亚州以及加拿大的安大略省。该表的主要目的是为这些地理位置提供一个正式的列表,它不会随时间变化(最近一次变化是90年代后期,加拿大的西北地区分割为努勒维特和西北地区)。该表的一个有效自然键可以是州代码,一个值唯一的两字符代码--例如加利福尼亚的CA以及安大略的ON。不幸的是这种方法并不适合,因为加拿大政府决定为西北地区两个州使用相同的代码NW。
  4. 应用必须仍然支持“自然键搜索”。如果选择采用代理键,必须不能忘了应用需要支持基于地域列(仍然唯一标识数据行)的搜索。例如,Customer表可能拥有一个Customer_POID的代理键,以及一个Customer_Number列和Social_Security_Number列。你很可能需要支持基于顾客编号和社会保险号的搜索。搜索在关系数据库对象检索最佳实践中详细讨论。
  5. 不要自然化代理键。一旦你向终端用户显示了代理键的值,或者更坏的是允许他们使用该值(例如搜索该值),实际上你已经给它们赋予了业务含义。这实际上是自然化了代理键从而失去了代理键的优点。

5 做出了“错误”选择时如何应对

首先,不用为此担心:不论你多么擅长数据库设计都可能会犯错。好消息是正如我在数据库重构过程中所说,虽然可能需要许多工作,还是可以使用代理键替换自然键的(反之亦然)。要使用代理键替换自然键,你需要应用引入代理键重构,如图2所示。要使用自然键替换代理键,你需要应用使用自然键替换代理键重构,如图3所示。

图2 替换表Order的自然键

图3 替换表State的代理键

6 参考资料与推荐在线阅读

附加:

Natural key

Jump to: navigation, search

A natural key is a key made from existing attributes -
the opposite of a Surrogate key.

Some natural key examples:

  • National ID number/ Social Security Number
  • Employee's Salary number
  • Car registration number
  • Publication's ISBN
  • City name
  • Airport name

参考:http://www.orafaq.com/wiki/Natural_key

代理键

关系型数据库设计中,代理键是在当数据表中的候选键都不适合当主键时,例如数据太长,或是意义层面太多,就会请一个无意义的但唯一的字段来代为作主键。

Natural key

From Wikipedia, the free encyclopedia

In relational model database design, a natural key is a key that is
formed of attributes that already exist in the real world. For example, a
USA citizen's social security number could be used as a natural key. In
other words, a natural key is a
candidate key that has a logical relationship to the attributes within that row. A natural key is sometimes called domain key.

  • 自然键。由现实世界中已经存在的属性组成的键。例如,美国公民被分配了一个唯一(不保证一定正确,但实际上非常接近唯一)的社会保险号(SSN)。如果隐私法允许的话,SSN可能被用作Person实体的自然键(假设组织的人员范围仅限于美国)。
  • 代理键。不具有业务含义的键。

为表指定键的策略有两种:

  1. 自然键。自然键是已经存在的一个或多个属性,它在业务概念中是唯一的。对于表Customer来说,存在两个候选键,CustomerNumber与SocialSecurityNumber。
  2. 代理键。引入一个不具有业务含义的列作为键,称作代理键。例如图1中表Address的列AddressID。地址不具有一个“简单”的自然键,因为需要使用Address表的所有列组成一个键(取决于你的问题域,可能仅仅需要组合Street和ZipCode列),所以此时引入一个代理键是一个更好的选择。

Natural key

From Wikipedia, the free encyclopedia

In relational model database design, a natural key is a key that is
formed of attributes that already exist in the real world. For example, a
USA citizen's social security number could be used as a natural key. In
other words, a natural key is a
candidate key that has a logical relationship to the attributes within that row. A natural key is sometimes called domain key.

参考:

natural key  oracle  谷歌

自然键 oracle     谷歌

(009)每日SQL学习:Oracle各个键说明(转)的更多相关文章

  1. (013)每日SQL学习:日期的各种计算

    1.确定两个日期之间的工作日天数 --确定两个日期之间的工作日天数with x0 as (select to_date('2018-01-01','yyyy-mm-dd') as 日期 from du ...

  2. (014)每日SQL学习:oracle下lag和lead分析函数

    /*语法*/ lag(exp_str,offset,defval) over() Lead(exp_str,offset,defval) over() --exp_str要取的列 --offset取偏 ...

  3. (008)每日SQL学习:Oracle Not Exists 及 Not In 使用

    今天遇到一个问题,not in 查询失效,我以为是穿越了,仔细查了点资料,原来理解有误! select value from temp_a a where a.id between 1 and 100 ...

  4. (004)每日SQL学习:物化视图之二

    一.    物化视图概述 Oracle的物化视图是包括一个查询结果的数据库对像,它是远程数据的的本地副本,或者用来生成基于数据表求和的汇总表.物化视图存储基于远程表的数据,也可以称为快照. 物化视图可 ...

  5. (003)每日SQL学习:普通视图和物化视图

    关于这一点一直就是很懵懂的状态,今天特意网上查了一下资料,以下摘抄网上比较好的答案.以作记录. 普通视图和物化视图的区别答曰:普通视图和物化视图根本就不是一个东西,说区别都是硬拼到一起的,首先明白基本 ...

  6. SQL学习:主键,外键,主键表,外键表,数据库的表与表之间的关系;

    在数据库的学习中,对于一个表的主键和外键的认识是非常重要的. 主键:在一个表中,能唯一的表示一个事物(或者一条记录)的字段,我们称之为主键 注意: 主键的设置可以不只是用一个字段,也可以用若干个字段的 ...

  7. (011)每日SQL学习:SQL开窗函数

    开窗函数:在开窗函数出现之前存在着很多用 SQL 语句很难解决的问题,很多都要通过复杂的相关子查询或者存储过程来完成.为了解决这些问题,在 2003 年 ISO SQL 标准加入了开窗函数,开窗函数的 ...

  8. (012)每日SQL学习:TO_CHAR(DATE,FORMAT)

    SYSDATE 2009-6-16 15:25:10 TRUNC(SYSDATE) 2009-6-16 TO_CHAR(SYSDATE,'YYYYMMDD') 20090616 到日 TO_CHAR( ...

  9. (002)每日SQL学习:删除名称重复的数据

    create table A ( id VARCHAR2(36), name VARCHAR2(100), sl VARCHAR2(36) ); insert all into a (id,name) ...

随机推荐

  1. [leetcode]205. Isomorphic Strings同构字符串

    哈希表可以用ASCII码数组来实现,可以更快 public boolean isIsomorphic(String s, String t) { /* 思路是记录下每个字符出现的位置,当有重复时,检查 ...

  2. 容器编排系统K8s之Dashboard部署

    前文我们了解了k8s的访问控制第三关准入控制相关插件的使用,回顾请参考:https://www.cnblogs.com/qiuhom-1874/p/14220402.html:今天我们来了解下k8s的 ...

  3. Spring3 MVC 注解(一)---注解基本配置及@controller和 @RequestMapping 常用解释(转)

      一:配置web.xml 1)问题:spring项目中有多个配置文件mvc.xml   dao.xml 2)解决:在web.xml中 <init-param> <param-nam ...

  4. druid监控

    1 @ConfigurationProperties(prefix = "spring.datasource") 2 @Bean 3 public DataSource druid ...

  5. 路由器开启远程控制(ssh或telent)

    • 远程控制        ○ 开启远程控制            § conf t             § line vty 0 4                □ 0 4 意思是最多允许5个 ...

  6. Java类的加载过程-重点!!

    java类的加载过程有以下几步共同完成: 加载->连接->初始化.连接又分为验证.准备.解析 一个非数组类的加载阶段(加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,这一步我们可以 ...

  7. JavaScript入门-函数function(二)

    JavaScript入门-函数function(二) 递归函数 什么是递归函数? 递归简单理解就是,在函数体里,调用自己. //我们在求一个10的阶乘的时候,可能会这么做 //写一个循环 var to ...

  8. 跟我一起学Redis之加个哨兵让主从复制更加高可用

    前言 主从复制的实现在上一篇已经分享过,虽然主从复制本身的确让读写分离更加高效,但是对于整体高可用存在很大的劣势:当主节点宕机了之后还需要人为重新进行主从关系配置:这不是开玩笑嘛,这样人为干预,故障恢 ...

  9. Java高并发与多线程(二)-----线程的实现方式

    今天,我们开始Java高并发与多线程的第二篇,线程的实现方式. 通常来讲,线程有三种基础实现方式,一种是继承Thread类,一种是实现Runnable接口,还有一种是实现Callable接口,当然,如 ...

  10. Lambda表达式你会用吗?

    函数式编程 在正式学习Lambda之前,我们先来了解一下什么是函数式编程 我们先看看什么是函数.函数是一种最基本的任务,一个大型程序就是一个顶层函数调用若干底层函数,这些被调用的函数又可以调用其他函数 ...