表中主键的最佳实践是什么?

时间:2008-12-03 15:30:23

标签: sql sql-server database relational

在设计表格时,我养成了一个习惯,即只有一个列是唯一的,我制作主键。这取决于要求以三种方式实现:

  1. 自动递增的标识整数列。
  2. 唯一标识符(GUID)
  3. 可用作行标识符列的短字符(x)或整数(或其他相对较小的数字类型)列
  4. 数字3将用于相当小的查找,主要是读取表,可能具有唯一的静态长度字符串代码,或数字值,如年份或其他数字。

    在大多数情况下,所有其他表都将具有自动递增整数或唯一标识符主键。

    问题: - )

    我最近开始使用没有一致行标识符的数据库,并且主键目前已跨不同列聚集。一些例子:

    • 日期时间/字符
    • 日期时间/整数
    • 日期时间/ VARCHAR
    • 炭/ nvarchar的/ nvarchar的

    这是否有效?我总是会为这些案例定义一个标识或唯一标识符列。

    此外,还有许多表没有主键。这有什么正当理由?

    我正在努力理解为什么表格按原样设计,对我来说这似乎是一个很大的混乱,但也许有充分的理由。

    第三个问题可以帮助我解读答案:如果使用多列构成复合主键,这种方法与代理/人工密钥相比有特定的优势吗?我主要考虑的是性能,维护,管理等方面?

21 个答案:

答案 0 :(得分:225)

我遵循一些规则:

  1. 主键应尽可能小。首选数字类型,因为数字类型以比字符格式更紧凑的格式存储。这是因为大多数主键将是另一个表中的外键以及多个索引中使用的外键。密钥越小,索引越小,您将使用的缓存中的页面就越少。
  2. 主键永远不会改变。更新主键始终是不可能的。这是因为它最有可能在多个索引中使用并用作外键。更新单个主键可能会导致更改产生连锁反应。
  3. 请勿使用“您的问题主键”作为逻辑模型主键。例如护照号码,社会安全号码或员工合同号码,因为这些“主键”可以改变现实世界的情况。
  4. 关于代理vs自然键,我参考上面的规则。如果自然键很小并且永远不会改变,则它可以用作主键。如果自然键很大或可能改变,我使用代理键。如果没有主键,我仍然会制作代理键,因为经验显示您将始终向您的架构添加表格并希望您放置主键。

答案 1 :(得分:85)

自然诗歌人工关键词是数据库社区中的一种宗教辩论 - 请参阅this article及其链接到的其他人。我既不赞成总是拥有人工钥匙,也不赞成从不拥有它们。我会根据具体情况做出决定,例如:

  • 美国各州:我会选择state_code(德克萨斯等的'TX'),而不是州内的state_id = 1
  • 员工:我通常会创建一个artifical employee_id,因为很难找到其他有用的东西。 SSN或同等版本可能有效,但可能会出现一些问题,例如尚未提供他/她的SSN的新加入者。
  • 员工薪资历史:(employee_id,start_date)。我会创建一个artifical employee_salary_history_id。它将提供什么点("foolish consistency"除外)

无论何时使用人工密钥,您都应该始终在自然键上声明唯一约束。例如,如果必须,请使用state_id,但是最好在state_code上声明一个唯一约束,否则你肯定最终得到:

state_id    state_code   state_name
137         TX           Texas
...         ...          ...
249         TX           Texas

答案 2 :(得分:25)

对经常被忽视的事情做一个额外的评论。有时不使用代理键在子表中有好处。假设我们的设计允许您在一个数据库中运行多个公司(可能是托管解决方案,或其他任何方式)。

假设我们有这些表格和列:

Company:
  CompanyId   (primary key)

CostCenter:
  CompanyId   (primary key, foreign key to Company)
  CostCentre  (primary key)

CostElement
  CompanyId   (primary key, foreign key to Company)
  CostElement (primary key)

Invoice:
  InvoiceId    (primary key)
  CompanyId    (primary key, in foreign key to CostCentre, in foreign key to CostElement)
  CostCentre   (in foreign key to CostCentre)
  CostElement  (in foreign key to CostElement)

如果最后一位没有意义,Invoice.CompanyId是两个外键的一部分,一个是 CostCentre 表,一个是 CostElement 表。主键是( InvoiceId CompanyId )。

在此模型中,无法从一家公司搞砸和引用 CostElement ,而从另一家公司引用 CostCentre 。如果在 CostElement CostCentre表上使用了代理键,那么它就是。

搞砸的机会越少越好。

答案 3 :(得分:19)

我出于一个简单的原因避免使用自然键 - 人为错误。尽管通常可以使用自然唯一标识符(SSN,VIN,帐号等),但它们需要人员正确输入它们。如果您使用SSN作为主键,有人会在数据输入过程中转换几个数字,并且不会立即发现错误,那么您将面临更改主键的问题。

我的主键全部由后台数据库程序处理,用户从不知道它们。

答案 4 :(得分:13)

从各个字段制作主键没有问题,这是一个自然键

您可以使用Identity列(与候选字段上的唯一索引相关联)来制作代理键

这是一个古老的讨论。在大多数情况下,我更喜欢代理键。

但没有理由缺少钥匙。

RE:编辑

是的,有很多争议:D

除了事实上它们是自然选择之外,我没有看到任何关于自然键的明显优势。你总是会想到 Name,SocialNumber - 或类似的东西 - 而不是 idPerson

代理键是自然键所具有的一些问题的答案(例如传播更改)。

当你习惯代理时,它似乎更干净,更易于管理。

但最终,你会发现这只是一种品味 - 或心态 - 。人们用自然键“思考得更好”,而其他人则不这样做。

答案 5 :(得分:11)

表应始终具有主键。如果不是,它应该是一个AutoIncrement字段。

有时人们会省略主键,因为它们会传输大量数据,并且可能会降低(依赖于数据库)进程的速度。但是,它应该在它之后添加。

关于链接表的一些评论,这是正确的,它是一个例外但是BUT字段应该是FK以保持完整性,并且在某些情况下,如果链接中的重复是那些字段也可以是主键未经授权......但为了保持简单形式,因为异常是编程中经常出现的事情,应该存在主键以保持数据的完整性。

答案 6 :(得分:8)

除了所有这些好的答案之外,我只想分享一篇我刚读过的好文章 The great primary-key debate

引用几点:

开发人员在为每个表选择主键时必须应用一些规则:

  • 主键必须唯一标识每条记录。
  • 记录的主键值不能为空。
  • 创建记录时必须存在主键值。
  • 主键必须保持稳定 - 您无法更改主键字段。
  • 主键必须紧凑且包含尽可能少的属性。
  • 无法更改主键值。

自然键(倾向于)违反规则。代理键符合规则。 (你最好仔细阅读那篇文章,值得你花时间!)

答案 7 :(得分:7)

主键有什么特别之处?

架构中表的目的是什么?桌子钥匙的目的是什么?主键有什么特别之处?围绕主键的讨论似乎忽略了主键是表的一部分,并且该表是模式的一部分。表和表关系的最佳选择应该是驱动所使用的密钥。

表(和表关系)包含有关您要记录的信息的事实。这些事实应该是自足的,有意义的,易于理解的和不矛盾的。从设计角度来看,在模式中添加或删除的其他表不应影响相关表。必须存储仅与信息本身相关的数据的目的。了解表中存储的内容不应要求进行科学研究项目。为同一目的存储的事实不应存储多次。密钥是记录的信息的全部或一部分,它是唯一的,主密钥是专门指定的密钥,它将成为表的主要访问点(即,应选择它用于数据一致性和使用,而不仅仅是插入性能)。

  • ASIDE:不幸的是,大多数数据库的设计副作用 由应用程序员(我有时)开发 什么是最适合应用程序或应用程序框架的 驱动表的主键选择。这导致整数和 GUID键(因为这些键很容易用于应用程序框架)和 单片表设计(因为这些减少了应用的数量) 表示内存中数据所需的框架对象。这些 应用驱动的数据库设计决策导致重要数据 大规模使用时的一致性问题。应用程序框架 以这种方式设计自然导致桌子一次设计。 “部分记录”在表格和数据中创建,随着时间的推移填写。 避免多表交互或使用时导致不一致 应用程序运行不正常时的数据。这些设计领先 对于无意义(或难以理解)的数据,数据传播 在桌子上(你必须看看其他表格才能理解 当前表)和重复数据。

据说主键应该尽可能小。我会说钥匙应该只有必要的大小。应避免随意向表中添加无意义的字段。从随机添加的无意义字段中创建密钥甚至更糟,特别是当它破坏从另一个表到非主键的连接依赖性时。如果表中没有好的候选键,这是合理的,但如果用于所有表,这种情况肯定是架构设计不佳的标志。

还有人说,主键永远不会改变,因为更新主键应始终是不可能的。但更新与删除后跟插入相同。通过这种逻辑,您不应该从具有一个键的表中删除记录,然后使用第二个键添加另一个记录。添加代理主键不会删除表中另一个键存在的事实。如果其他表通过代理键依赖于该含义,则更新表的非主键可能会破坏数据的含义(例如,具有代理键的状态表,其状态描述已从“已处理”更改为“已取消” '肯定会破坏数据)。永远不可能的是破坏数据意义。

话虽如此,我很感激今天企业中存在的许多设计糟糕的数据库(无意义的代理键控数据损坏的1NF庞然大物),因为这意味着对于那些理解的人来说,有无数的工作适当的数据库设计但在悲伤的一面,它确实有时让我感觉像西西弗斯,但我打赌他有一个401k(崩溃之前)。远离博客和网站,以获取重要的数据库设计问题。如果您正在设计数据库,请查看CJ Date。你也可以参考Celko for SQL Server,但前提是你先嗤之以鼻。在Oracle方面,参考Tom Kyte。

答案 8 :(得分:6)

自然键如果可用,通常是最好的。因此,如果datetime / char 唯一标识该行,并且这两个部分对该行有意义,那就太棒了。

如果只是日期时间是有意义的,并且刚刚添加了char以使其唯一,那么您也可以使用标识字段。

答案 9 :(得分:5)

对我而言,自然与人工关键是您在数据库中需要多少业务逻辑的问题。 Social Security number(SSN)是一个很好的例子。

“我的数据库中的每个客户端都必须拥有SSN。” Bam,完成后,将其作为主键并完成它。请记住,当您的业务规则发生变化时,您就会被烧毁。

由于我改变业务规则的经验,我自己不喜欢自然键。但如果你确定它不会改变,它可能会阻止一些关键的连接。

答案 10 :(得分:4)

我怀疑Steven A. Lowe卷起的报纸疗法是原始数据结构的设计者所必需的。

顺便说一句,GUIDs作为主键可能是一种表现难题。我不推荐它。

答案 11 :(得分:3)

您应该使用包含多个字段的“复合”或“复合”主键。

这是一个完全可以接受的解决方案,请访问here获取更多信息:)

答案 12 :(得分:3)

我也总是使用数字ID列。在oracle中我使用数字(18,0)没有真正的原因高于数字(12,0)(或者无论是int而不是long),也许我只是不想担心获得几十亿行db!

我还为基本跟踪添加了一个创建和修改的列(类型时间戳),它似乎很有用。

我不介意在其他列组合上设置唯一约束,但我真的很喜欢我的id,创建,修改基线要求。

答案 13 :(得分:3)

我寻找自然的主键并尽可能地使用它们。

如果找不到自然键,我更喜欢GUID到INT ++,因为SQL Server使用树,并且总是在树的末尾添加键是不好的。

在多对多联接的表上,我使用外键的复合主键。

因为我很幸运能够使用SQL Server,所以我可以使用分析器和查询分析器研究执行计划和统计数据,并且可以非常轻松地了解我的密钥的执行情况。

答案 14 :(得分:2)

这是我自己的经验法则,经过25年以上的发展经验,我已经确定了这一点。

  • 所有表都应该有一个自动的列主键 增量。
  • 将其包含在任何可更新的视图中
  • 主键在您的应用程序上下文中没有任何意义。这意味着它不应该是SKU,帐号或员工ID或对您的应用程序有意义的任何其他信息。它只是与实体关联的唯一密钥。

数据库使用主键进行优化,除了标识特定实体或与特定实体相关之外,您的应用程序不应使用主键。

始终使用单个值主键可以非常直接地执行UPSERT。

使用其他索引来支持在您的应用程序中有意义的多列键。

答案 15 :(得分:2)

我总是使用自动编号或身份字段。

我为一个使用SSN作为主键的客户工作,然后由于HIPAA规则被迫更改为“MemberID”,并且在更新相关表中的外键时引起了大量问题。坚持一致的标准列标准有助于我避免在我的所有项目中出现类似的问题。

答案 16 :(得分:1)

GUIDs可以用作主键,但是您需要创建正确类型的GUID才能使其表现良好。

您需要生成COMB GUID。关于它和性能统计的一篇好文章是 The Cost of GUIDs as Primary Keys

有关在SQL中构建COMB GUID的一些代码位于 Uniqueidentifier vs identityarchive

答案 17 :(得分:1)

如果你真的想在这个古老的辩论中来回阅读所有来回,请在Stack Overflow上搜索“自然键”。你应该得到结果页面。

答案 18 :(得分:1)

所有都有一个主键。否则,你拥有的是一个HEAP - 在某些情况下,这可能是你想要的(当数据然后通过服务代理复制到另一个数据库或表时,重载插入负载)。

对于行数较少的查找表,可以使用3 CHAR代码作为主键,因为这比INT占用的空间少,但性能差异可以忽略不计。除此之外,我总是使用INT,除非你有一个参考表,它可能有一个由来自关联表的外键组成的复合主键。

答案 19 :(得分:0)

我会对我对自然键的偏好保持警惕 - 尽可能使用它们,因为它们将使您的数据库管理生活变得更加容易。我在公司建立了一个标准,所有表都有以下列:

  • 行ID(GUID)
  • Creator(string;默认为当前用户的名字(T-SQL中为SUSER_SNAME()))
  • 已创建(日期时间)
  • 时间戳

行ID在每个表上都有一个唯一键,并且无论如何都是每行自动生成的(并且权限可以阻止任何人编辑它),并且可以合理地保证在所有表和数据库中都是唯一的。如果任何ORM系统需要一个ID密钥,则可以使用该密钥。

同时,如果可能,实际PK是一个自然键。我的内部规则如下:

  • 人 - 使用代理键,例如INT。如果它是内部的,则Active Directory用户GUID是可接受的选择
  • 查找表(例如StatusCodes) - 使用简短的CHAR代码;它比INT更容易记住,并且在许多情况下纸质表格和用户也将使用它以简洁(例如Status =“E”代表“Expired”,“A”代表“Approved”,“NADIS”代表“No Isbestos Detected”在样本“)
  • 链接表格 - FK的组合(例如EventId, AttendeeId

理想情况下,您最终会得到一个自然的,人类可读且令人难忘的PK,以及一个ORM友好的一个ID-per-table GUID。

警告:我维护的数据库往往是数十万条记录,而不是数百万或数十亿,所以如果你有较大系统的经验,这是禁忌我的建议,请随意忽略我!

答案 20 :(得分:0)

我们做了很多连接,复合主键刚刚成为一个性能问题。即使您正在引入第二个候选键,一个简单的int或long也会解决许多问题,但是加入一个字段而不是三个字段会更容易,也更容易理解。