关于sql:表中主键的最佳实践是什么?

What's the best practice for primary keys in tables?

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

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

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

    问题:-)

    我最近开始使用没有一致行标识符的数据库,主键目前在各个列上聚集。一些例子:

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

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

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

    我试图理解为什么桌子的设计原样,对我来说这似乎是一个很大的混乱,但也许有充分的理由。

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


    我遵循一些规则:

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


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

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

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

    1
    2
    3
    4
    state_id    state_code   state_name
    137         TX           Texas
    ...         ...          ...
    249         TX           Texas


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

    假设我们有这些表和列:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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表上使用了代理键,那么它就是。

    搞砸的机会越少越好。


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

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


    从各个领域制作主键没有问题,这是一个自然键。

    您可以使用Identity列(与候选字段上的唯一索引关联)来生成Surrogate Key。

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

    但缺乏关键是没有理由的。

    RE:编辑

    是的,有很多争议:D

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

    代理键是自然键具有的一些问题的答案(例如,传播变化)。

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

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


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

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

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


    除了所有这些好的答案之外,我只想分享一篇我刚读过的好文章,这是一场伟大的主要辩论。

    只是引用几点:

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

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

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


    主键有什么特别之处?

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

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

  • ASIDE:不幸的是,大多数数据库的设计副作用
    由应用程序员(我有时)开发
    什么是最适合应用程序或应用程序框架的
    驱动表的主键选择。这导致整数和
    GUID键(因为这些键很容易用于应用程序框架)和
    单片表设计(因为这些减少了应用的数量)
    表示内存中数据所需的框架对象。这些
    应用驱动的数据库设计决策导致重要数据
    大规模使用时的一致性问题。应用程序框架
    以这种方式设计自然导致桌子一次设计。
    "部分记录"在表格和数据中创建,随着时间的推移填写。
    避免多表交互或使用时导致不一致
    应用程序运行不正常时的数据。这些设计领先
    对于无意义(或难以理解)的数据,数据传播
    在桌子上(你必须看看其他表格才能理解
    当前表)和重复数据。
  • 据说主键应该尽可能小。我会说钥匙应该只有必要的大小。应避免随意向表中添加无意义的字段。从随机添加的无意义字段中创建密钥甚至更糟,特别是当它破坏从另一个表到非主键的连接依赖性时。如果表中没有好的候选键,这是合理的,但如果用于所有表,这种情况肯定是架构设计不佳的标志。

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

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

    好。


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

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


    对我而言,自然键和人工键是您在数据库中需要多少业务逻辑的问题。社会安全号码(SSN)就是一个很好的例子。

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

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


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

    另外,作为主键的GUID可能是一种性能损失。我不推荐它。


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

    这是一个完全可以接受的解决方案,请到这里获取更多信息


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

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

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


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

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

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

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


    以下是我在25年以上的开发经验后确定的经验。

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

    数据库使用主键进行优化,并且您的应用程序不应将其用于识别特定实体或与特定实体相关的任何内容。

    始终使用单值主键使得执行UPSERT非常简单。

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


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

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


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


    GUID可以用作主键,但是您需要创建正确类型的GUID以使其运行良好。

    您需要生成COMB GUID。关于它和性能统计的一篇好文章是
    GUID作为主键的成本。

    在SQL中构建COMB GUID的一些代码也在Uniqueidentifier vs identity(archive)中。


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

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


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


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

    • 行ID(GUID)
    • 创建者(字符串;默认为当前用户的名称(在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。

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