【问题标题】:Should primary key be auto_increment?主键应该是 auto_increment 吗?
【发布时间】:2014-09-12 00:59:36
【问题描述】:

我知道在设计表格时最好使用主键。

但是我想知道在设计主键时,需要设置auto_increment吗?

如果完成,有什么好处?

我听着,这可以保持b-tree的稳定,但我不知道为什么?

如果表有一个唯一列,将唯一列设置为主键或添加新列'id'作为auto_increment主键更好?

你能帮帮我吗?谢谢。

【问题讨论】:

  • 好处是您不必提供值,并且数据库引擎将(通常)确保唯一值....
  • 如果表有唯一列,是设置唯一列为主键还是添加新列'id'作为auto_increment主键?

标签: database database-design


【解决方案1】:

我想知道在设计主键时,需要设置auto_increment吗?

不,这不是绝对必要的。在某些情况下,自然键 很好。

如果做了,有什么好处?

使用自增代理键的优点:

  • 代理键永远不需要更改,即使表中的所有其他列都可以更改。
  • 当您有多个用户同时插入时,RDBMS 可以更轻松地确保自动递增键的唯一性,而无需锁定和竞争条件。
  • 使用整数是可用于主键的最紧凑的数据类型,因此与使用长字符串相比,它会产生更小的索引。
  • 插入 B 树索引的效率(见下文)。
  • 当唯一的其他候选键由多列组成时,引用具有单列的行比引用多列更容易和更整洁。

使用自然键的优点:

  • 该列对实体有一些意义,例如电话号码。您无需为代理键存储额外的列。
  • 使用外键引用自然主键的其他表会获得有意义的值,因此它们可以避免连接。例如,如果您想获取颜色名称,则需要对引用 colorsshoes 表进行连接。但是,如果您使用颜色名称​​作为colors 的主键,那么该值已经是shoes 表的一部分。

不需要代理自增键的其他情况:

  • 您已经拥有为表提供候选键的其他列的组合(无论它们是代理键还是自然键)。在多对多表中可以找到一个很好的例子。如果一个表将电影映射到演员,即使电影和演员都由主键引用,那么您已经在这两列上有一个候选键,并且您不需要另一个自动增量列。

我听着,这可以保持b-tree的稳定,但我不知道为什么?

将值插入 B 树中间的任意位置可能会导致索引重组的代价高昂。

这里有一个动画示例:http://www.bluerwhite.org/btree/

查看示例“将键 33 插入 B-Tree(w/Split)”,其中显示了将值插入过度填充的 B-tree 节点的步骤,以及 B-tree 的响应操作.

现在想象一下,示例插图仅显示了更深的 B-tree 的底部(就像索引 B-tree 有数百万个条目的情况一样),并且填充父节点本身可以​​是溢出,并强制拆分操作继续向上树中的更高级别。如果到树顶的所有祖先节点都已被填满,这可以一直持续到树的最顶端。

由于节点分裂并且必须重组,它们可能需要更多空间,但它们存储在没有空闲空间的数据库文件的某个页面上。因此,存储引擎必须将部分索引重新定位到文件的另一部分,并且可能仅针对单个 INSERT 重写大量索引页。

自动增量值自然总是插入到 B 树的最右边。正如@BrankoDimitrijevic 在下面的评论中指出的那样,这并没有降低它们对索引造成如此费力的节点拆分和重组的可能性。但是 B-tree 实现代码可以通过其他方式针对这种情况进行优化,有些可以。

如果表有唯一列,最好将唯一列设置为主键还是添加新列'id'作为auto_increment主键?

如果唯一列也是不可为空的,那么您可以将其用作主键。主键要求它们的所有列都不可为空。

【讨论】:

  • 最大值存储在 B 树最右边的叶子中这一事实本身并不意味着节点分裂会减少。但是,它确实为 DBMS 提供了针对该特定情况进行优化的机会,并使新的最右边节点(拆分后)为空或几乎为空(预期所有/大多数新值将插入那里),从而延迟下一次分裂。我知道 Oracle 有这个(参见this presentation 的第 31 页)——如果其他 DBMS 实现了类似的优化,我不会感到惊讶。
【解决方案2】:

我知道在设计表格时使用主键更好。

事实上,无键表是multiset(因为它允许重复),因此严格来说不是关系(它是一个集合),因此您的数据库不会真正是“关系”。

请注意,“primary”(主键)和“alternate”(唯一约束)键是logically equivalent

但是我想知道在设计主键时,需要设置auto_increment吗?

您实际上是在问多个问题:

  1. 我应该创建一个密钥吗?
  2. 如果是,我应该创建一个代理键吗?
  3. 如果是,应该是整数吗?
  4. 如果是,我应该让它自动递增吗?

(1) 的答案是“几乎总是”。在一些非常罕见的情况下,数据并不“重要”,您可能会出于性能原因跳过它,但这种情况极为罕见。

(2) 的答案是“视情况而定” - 主要优缺点可以在 here 找到。

(3) 的答案取决于您是否需要独立从数据库中生成密钥(例如,在断开连接时,或在连接到不同数据库时)。如果是,您可以使用 GUID(显然不能自动递增,但可以单独生成)。如果不是,那么您可以只使用整数 - 它们更紧凑且通常更快。

最后,如果您达到 (4),那么您几乎肯定会希望使其自动递增,原因如下所述。

如果做了,有什么好处?

  • 使整数代理键自动递增的主要好处是多个并发客户端永远不会收到相同的生成值。如果您只是尝试SELECT MAX(ID) + 1 FROM ...,则无法保证其他客户端不会尝试同时执行相同的操作,并最终得到相同的结果(随后导致密钥违规)。
  • 另一个好处是 DBMS 将使用高度优化的代码路径来生成新的唯一值。
  • 缺点是自动增量机制通常事务感知:如果您生成一个新的 ID 值然后 ROLLBACK 事务,该值将不会再次生成。话虽如此,代理键没有任何意义(如果有,它们就不是代理),所以这样的“漏洞”是无关紧要的。

如果表有唯一列,最好将唯一列设置为主键还是添加新列'id'作为auto_increment主键?

如果属性在“逻辑级别”本质上是唯一的,则相应的表列必须设置为唯一的(通过 PRIMARY KEY 或 UNIQUE 约束),无论您以后决定添加 是否是代理键。

【讨论】:

  • +1 很好的答案,除了 UNIQUE 约束只有在不可为空的列上定义时才等效于 PRIMARY 约束。如果 UNIQUE 约束在可空列上,它允许空值,当值为空时允许多行,并使表成为一个多重集。
  • @BillKarwin 感谢您发现这一点。附带说明:并非所有 DBMS 在 UNIQUE 约束下同等对待 NULL——有些将它们视为“未知”(Oracle、MySQL、PostgreSQL),有些则视为“空”(MS SQL Server),分别允许和禁止重复。在复合 UNIQUE 约束中如何处理混合的 NULL 和非 NULL 也存在差异(是否强制执行非 NULL 部分的唯一性)。
  • 是的,我也观察到了这一点。在任何情况下,根据标准,PRIMARY KEY 仍然必须是非空的。
【解决方案3】:

拥有一个自动递增的 PK 可以很容易地创建一个永远不需要更改的键,这反过来又可以很容易地在其他表中引用。

如果您的数据具有独特且永远不会更改的自然列,您也可以使用它们。请注意,只要有足够的时间,大多数“永远不会改变”的事情都会发生变化,比如某人的社会安全号码......

为简单起见,我总是对 PK 使用自增(标识)列。

【讨论】:

  • 如果其他表使用FOREIGN KEY... ON UPDATE CASCADE,您可以参考其他表中更改的PK值。
  • 如果我们现在正在开发软件特定的功能,也许这个问题是微不足道的,或者出于这个原因应该对建议进行标记。例如,Oracle 不支持ON UPDATE CASCADE 的特性。 Tom Kyte Regarding On Update Cascade:“就我个人而言——我从来没有发现需要或使用更新级联。我反对它。如果你的设计需要它——如果可以的话,现在就改变你的设计。”
【解决方案4】:

支持在表定义中使用代理键

感谢@Branko Dimitrijevic 通过描述SURROGATE KEYS 的角色并进入讨论中心来打开关系数据库主键(PK)的主题。根据定义,代理键除了它们在表的每条记录中的值之间的唯一性之外没有任何内在含义。

也感谢@Mattias Åslund 的额外智慧:

请注意,只要有足够的时间,大多数“永远不会改变”的事情都会改变,比如某人的社会安全号码......

我补充说,即使选择为“不可更改”的值确实没有改变,受支持的业务或组织本身的规则也很可能随着时间的推移以可能影响核心假设的方式漂移和变化给定的设计。

Computer Professionals for Social ResponsibilityChoosing an Appropriate Key for New Databases 上的本部分中可以找到有关集成人口统计和生物特征关键值以跟踪个人的有用讨论。

备用唯一数据库键的案例:示例架构

我计划与这篇文章中的 cmets 讨论一个特定的示例设计,以解释分配 不是 代理键的主键可能会出错的各种事情。其中许多假设取自实际应用的观察结果。由于其他系统和数据源变得依赖于他们的假设,他们的设计引入其他业务流程的复杂性被人们铭记。

设计和示例数据如下,大致借鉴了 Oracle 臭名昭著的 Scott/TIGER 数据库设计。

SQL Fiddle

MySQL 5.5.32 架构设置

CREATE TABLE employee 
    (
     fake_ssn  varchar(15) primary key,
     last_name varchar(40),
     first_name varchar(40),
     dept_id  varchar(15),
     hire_date  date,
     salary int,
     email varchar(100)
    );

INSERT INTO employee
(fake_ssn, last_name, first_name, dept_id, hire_date, salary,
 email)
VALUES
('130-60-0101', 'MARLOWE', 'JACOB', '1200-05', date('2009/01/25'),
 8000, 'jacob@some-company.com'),
('967-22-5025', 'CRACHITT', 'BOB', '1200-05', date('2010/02/05'),
 500, 'bobc@some-company.com'),
('040-36-5555', 'PERRY', 'VICTORIA', '1200-02', date('2011/05/25'),
 2700, 'vperry@some-company.com'),
('203-89-1010', 'STEVENS', 'KEVIN', '2955-03', date('2007/04/25'),
 1800, 'kevin.stevens@some-company.com'),
('409-99-1111', 'MCLANE', 'JONATHAN', '2955-03', date('2009/03/02'),
 4200, 'jon.j.mclane@some-company.com');

CREATE TABLE department
    (
     dept_id  varchar(15) primary key,
     dept_manager varchar(40),
     dept_title varchar(40)
    );

INSERT INTO department
(dept_id, dept_manager, dept_title)
VALUES
('1200-05', 'MARLOWE', 'FINANCE'),
('1200-02', null, 'HR'),
('2955-03', 'JOHNM', 'MARKETING');

COMMIT;

查询 1

SELECT fake_ssn, last_name, first_name, dept_id, hire_date,
   salary, email    
FROM employee

Results

|    FAKE_SSN | LAST_NAME | FIRST_NAME | DEPT_ID |                       HIRE_DATE | SALARY |                          EMAIL |
|-------------|-----------|------------|---------|---------------------------------|--------|--------------------------------|
| 040-36-5555 |     PERRY |   VICTORIA | 1200-02 |      May, 25 2011 00:00:00+0000 |   2700 |        vperry@some-company.com |
| 130-60-0101 |   MARLOWE |      JACOB | 1200-05 |  January, 25 2009 00:00:00+0000 |   8000 |         jacob@some-company.com |
| 203-89-1010 |   STEVENS |      KEVIN | 2955-03 |    April, 25 2007 00:00:00+0000 |   1800 | kevin.stevens@some-company.com |
| 409-99-1111 |    MCLANE |   JONATHAN | 2955-03 |    March, 02 2009 00:00:00+0000 |   4200 |  jon.j.mclane@some-company.com |
| 967-22-5025 |  CRACHITT |        BOB | 1200-05 | February, 05 2010 00:00:00+0000 |    500 |          bobc@some-company.com |

查询 2

SELECT dept_id, dept_manager, dept_title    
FROM department

Results

| DEPT_ID | DEPT_MANAGER | DEPT_TITLE |
|---------|--------------|------------|
| 1200-02 |       (null) |         HR |
| 1200-05 |      MARLOWE |    FINANCE |
| 2955-03 |        JOHNM |  MARKETING |

假社会安全号码

FAKE 名称只是提醒一下,这些都是随机生成的值。)虽然这通常是人事记录和数据库中流行的“unqiue”值,但根据美国社会保障局的说法,这个值不是独特的。这也是有问题的,因为最近通过了隐私法,该值及其转移受到严格监管。

名称组合

即使通过包含中间名首字母创建了额外的组合,不知何故,同名的人仍然太多。看看社会保障局对 2012 年出生婴儿的注册姓名的说法:

从现在起的二十年后,当 2012 年的 JACOB'sSOPHIA's 毕业时,他们将与成千上万像他们一样的人一起涌入职场…… p>

由于婚姻或法律原因导致的姓名更​​改也会威胁到数据库记录的参照完整性,因为数据库记录依赖于它们作为业务关键值的值。

部门编号

一些公司会尝试从其他值派生密钥以生成SMART KEYS。在实践中观察到这些类型的键根本不聪明。示例中的值:1200-021200-052955-03 旨在类似于“智能钥匙”。第一个值可能是公司园区或多地点业务的街道地址或建筑物编号。第二个值(“-02”、“-03”、“-05”)可能是部门所在建筑物的楼层。

更改建筑物、移动部门或完全搬迁业务将使DEPARTMENT ID 的这种位置依赖关系无用。

部门经理

这个很微妙,但在这种关系连接中有一个漏洞。 MANAGER 也是一个员工,这使得 EMPLOYEEDEPARTMENT 之间的关系连接成为循环连接:

  • MANAGER(来自DEPARTMENT)是EMPLOYEE 表的外键约束,还是
  • DEPT_ID (FROM EMPLOYEE) 是 DEPARTMENT 表的外键约束吗?

如果您放弃 MANAGEREMPLOYEE 上的某个键列(LAST_NAMEFIRST_NAME + LAST_NAME)之间的外键约束,您将面临MANAGER 的值不一致的风险。

...看着

Query of The DEPARTMENT Table

| DEPT_ID | DEPT_MANAGER | DEPT_TITLE |
|---------|--------------|------------|
| 1200-02 |       (null) |         HR |
| 1200-05 |      MARLOWE |    FINANCE |
| 2955-03 |        JOHNM |  MARKETING |

DEPT_MANAGERDEPARTMENT 表中的错位,因为部门经理的姓名有三种不同的表示方式:无(空)、全大写姓氏、全大写名字、姓氏首字母.

结论

从这篇文章中吸取的一个重要教训是,通过集成派生值来使键更多而不是键,使值基于业务规则的假设会限制数据库设计的灵活性,因为如果业务规则发生变化,Primary Keys 或 Joining Key 值等值也会发生变化。

作为业务应用程序的开发人员或维护人员,如果您控制并拥有代表业务应用程序本身内部结构的部分,您(或您的团队)将能够更好地支持当前的业务条件。主键可能永远不会真正出现在客户或面向用户的情况下,但它应该受到保护,以便它所代表的关系不会随着时间而改变。

特别感谢:

来自 2012 年流行的婴儿名字页面的图片来源:

http://www.ssa.gov/OACT/babynames/#ht=0

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-17
    • 2011-12-22
    • 2017-04-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多