表中主键的最佳做法是什么?

在devise表格的时候,我养成了一个习惯,那就是有一列是唯一的,而且是主键。 这取决于要求以三种方式实现:

  1. 自动递增的标识整数列。
  2. 唯一标识符(GUID)
  3. 可用作行标识符列的短字符(x)或整数(或其他相对较小的数字types)列

数字3将用于相当小的查找,大多数读取的表格可能具有唯一的静态长度string代码,或数字值(例如年份或其他数字)。

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

问题:-)

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

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

这有没有一个有效的案例? 我会一直为这些情况定义一个身份或唯一标识符列。

另外还有很多没有主键的表格。 如果有的话,有什么理由呢?

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

第三个问题可以帮助我解释答案:在使用多列来组成复合主键的情况下,这种方法与代理/人工键有什么特别的优势? 我主要在考虑性能,维护,pipe理等方面?

我遵循一些规则:

  1. 主键应该尽可能小。 因为数字types以比字符格式更紧凑的格式存储,所以更喜欢数字types。 这是因为大多数主键将是另一个表中的外键以及在多个索引中使用。 键越小,索引越小,caching中使用的页面越less。
  2. 主键不应该改变。 更新主键应该始终是不可能的。 这是因为它最有可能在多个索引中使用并用作外键。 更新单个主键可能会导致更改的连锁反应。
  3. 不要使用“你的问题主键”作为你的逻辑模型主键。 例如护照号码,社会安全号码或员工合同号码,因为这些“主键”可以改变现实世界的情况。

关于代理与自然的关键,我参考了上面的规则。 如果自然键很小,并且永远不会改变,则可以将其用作主键。 如果自然键很大或可能改变,我使用代理键。 如果没有主键,我仍然使用代理键,因为经验表明,您将始终向您的模式添加表,并希望您将主键放在适当的位置。

自然经文人造密钥是数据库社区中的一种宗教争论 – 请参阅本文及其链接的其他内容。 我并不赞成总是有人造的钥匙,也不会永远拥有它们。 我会根据具体情况决定,例如:

  • 美国:我会去state_code(德克萨斯等“TX”),而不是德克萨斯州的state_id = 1
  • 员工:我通常会创build一个人造的employee_id,因为很难find其他的工作。 SSN或类似的工具可能会工作,但可能会有一些问题,像一个还没有提供他/她的SSN的新的木匠。
  • 员工薪资logging:(employee_id,start_date)。 我不会创build一个人造的employee_salary_history_id。 它会发挥什么作用(除了“愚蠢的一致性” )

无论使用哪个仿真键,您都应该始终在自然键上声明唯一的约束。 例如,如果你必须使用state_id,那么你最好在state_code上声明一个唯一的约束,否则你最终会得到:

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

只是对经常被忽视的东西进行额外的评论。 有时不使用代理键在子表中有好处。 比方说,我们有一个允许你在一个数据库中运行多个公司的devise(也许它是一个托pipe解决scheme,或其他)。

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

 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表。 主键是( InvoiceIdCompanyId )。

在这个模型中,不可能将一个公司的CostElement和另一个公司的CostCentre引用并引用。 如果在CostElementCostCentre表上使用代理键,则会是。

更糟糕的机会越less越好。

我避免使用自然键出于一个简单的原因 – 人为错误。 虽然自然唯一的标识符通常是可用的(SSN,VIN,账号等),但他们需要人类正确input。 如果您使用SSN作为主键,在数据input过程中某人将转换一些数字,并且错误不会立即发现,那么您将面临更改主键的问题。

我的主键都在后台由数据库程序处理,用户永远不知道它们。

从各个领域制作主键没有任何问题,这是一个自然的关键

您可以使用“标识”列(与候选字段上的唯一索引相关联)创build代理键

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

但是没有借口缺less钥匙。

RE:编辑

是的,这方面有很多争议:D

除了自然select这个事实之外,我没有看到自然键的明显优势。 你总是会考虑在Name,SocialNumber中 – 或类似的东西 – 而不是idPerson

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

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

但最后,你会发现这只是一个品味或思维方式的问题。 人们用自然钥匙“思考得好些”,而其他的则不然。

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

有时人们会忽略主键,因为他们传输了大量数据,并且可能会减慢(取决于数据库)进程。 但是,它应该被添加后。

关于链接表的一些评论 ,这是正确的,这是一个例外,BUT字段应该是FK来保持完整性,有些情况下,如果链接中的重复未经授权,这些字段也可以是主键…但保持在简单的forms,因为exception是编程中经常出现的事情,所以主键应该保持数据的完整性。

主键有什么特别之处?

模式中表格的目的是什么? 桌子的钥匙的目的是什么? 主键有什么特别之处? 围绕主键的讨论似乎错过了主键是表的一部分,该表是模式的一部分。 表和表关系最好是驱动使用的密钥。

表(和表关系)包含有关您希望logging的信息的事实。 这些事实应该是自足的,有意义的,容易理解的,而且是非矛盾的。 从deviseangular度来看,添加或从模式中删除的其他表不应影响相关表。 存储仅与信息本身相关的数据的目的是必须的。 了解存储在表中的内容不应该经历一个科学研究项目。 没有为相同目的存储的事实应该被存储多次。 密钥是被logging的信息的全部或一部分,是唯一的,主密钥是专门指定的密钥,它将成为表的主要接入点(即应该select数据一致性和用法,而不是插入性能)。

  • ASIDE:大多数数据库的不良副作用是由应用程序员devise和开发的(我有时会这样做)是最适合应用程序或应用程序框架的驱动器通常是驱动表的主键select。 这导致了整数和GUID键(因为这些对于应用程序框架来说是简单的)和单一的表devise(因为这些减less了在内存中表示数据所需的应用程序框架对象的数量)。 这些应用程序驱动的数据库devise决策在大规模使用时会导致严重的数据一致性问 以这种方式devise的应用程序框架自然会导致一次devise表格。 在部分表格中创build“部分logging”,并在一段时间内填写数据。 避免多表交互,或者在应用程序运行不正常时导致数据不一致。 这些devise会导致数据无意义(或难以理解),数据遍布表(您必须查看其他表以了解当前表)以及重复的数据。

据说主键应该尽可能小。 我会说,钥匙应该只有必要的大。 应该避免在表中随意添加无意义的字段。 更糟糕的是,从一个随机添加的无意义字段中取出一个键,尤其是当它从另一个表中销毁连接依赖关系到非主键时。 如果表中没有合适的候选键,这是唯一合理的,但如果用于所有表,这肯定是一个糟糕的模式devise的标志。

也有人说,主键不应该改变,因为更新主键应该永远是不可能的。 但是更新与删除,然后插入相同。 通过这个逻辑,你永远不应该从一个表中用一个键删除一个logging,然后再用另一个键添加另一个logging。 添加代理主键不会消除表中其他键存在的事实。 如果其他表通过代理键具有对该含义的依赖关系,则更新表的非主键可能会破坏数据的含义(例如,具有代理键的状态表的状态描述已从“已处理”更改为“已取消'肯定会破坏数据)。 什么都不应该是破坏数据的意义。

说了这些之后,我很感谢现在企业中存在的许多devise不佳的数据库(无意义代理键控数据损坏的1NF巨无霸),因为这意味着对于理解正确的数据库devise的人来说有无尽的工作量。 但悲伤的一面,它有时让我觉得像西西弗斯,但我敢打赌他有一个401K(崩溃之前)。 重要的数据库devise问题远离博客和网站。 如果你正在devise数据库,查找CJdate。 您也可以参考Celko for SQL Server,但前提是您先保持鼻子。 在Oracle方面,请参阅Tom Kyte。

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

只要引用几点:

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

  • 主键必须唯一标识每条logging。
  • logging的主键值不能为空。
  • logging创build时,主键值必须存在。
  • 主键必须保持稳定 – 您不能更改主键字段。
  • 主键必须紧凑,并且包含尽可能less的属性。
  • 主键值不能更改。

自然键(倾向于)违反规则。 代理键符合规则。 (你最好通读这篇文章,这是值得你的时间!)

一个自然的关键,如果可用,通常是最好的。 所以,如果datetime / char 唯一标识行,并且这两个部分都对行有意义,那很好。

如果只有date时间是有意义的,并且字符被加上以使其独特,那么你可能只需要一个识别字段。

对我来说,自然与人为的关键是在数据库中需要多less业务逻辑。 社会安全号码 (SSN)就是一个很好的例子。

“我的数据库中的每个客户都必须拥有SSN。” Bam,做完了,把它作为主要的关键,并且用它来完成。 只要记住当你的商业规则改变你被烧毁。

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

我怀疑史蒂文·A·洛维(Steven A. Lowe)卷起报纸疗法是原始数据结构的devise者所需要的。

另外,作为主键的GUID可能是一个性能问题。 我不会推荐它。

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

这是一个完全可以接受的解决scheme,去这里获得更多信息:)

我也经常使用数字ID列。 在oracle中我使用数字(18,0)没有真正的理由超过数字(12,0)(或者什么是一个int而不是一个长),也许我只是不想担心获得几十亿行db!

我还包括一个创build和修改的列(types时间戳)进行基本跟踪,看起来有用。

我不介意设置其他组合的独特约束,但我真的很喜欢我的ID,创build,修改的基准要求。

我寻找自然主键并在我所能使用的地方使用它们。

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

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

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

我总是使用自动编号或标识字段。

我曾经为一个曾经使用SSN作为主键的客户工作过,然后因为HIPAA的规定被迫更改为“MemberID”,并且在更新相关表中的外键时导致了很多问题。 坚持标准专栏的一致标准帮助我避免了我所有项目中的类似问题。

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

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

如果你真的想仔细阅读这个古老的争论,那就在Stack Overflow上search“自然键”。 你应该回到结果页面。

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

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

还有一些在SQL中构buildCOMB GUID的代码是Uniqueidentifier vs identity ( archive )

我们做了很多连接,复合主键刚刚变成了一个表演猪。 即使你引入了第二个候选键,一个简单的int或者long也可以处理很多问题,但是join一个字段比三个字段要容易得多,也更容易理解。

我会预先考虑我对自然键的偏好 – 在可能的情况下使用它们,因为它们将使您的数据库pipe理更加轻松。 我在我们公司制定了一个标准,所有的表格都有以下几栏:

  • 行ID(GUID)
  • Creator(string;具有当前用户名称的缺省值SUSER_SNAME() T-SQL中的SUSER_SNAME() ))
  • 创build(DateTime)
  • 时间戳

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

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

  • 人 – 使用代理键,例如INT。 如果是内部的,则Active Directory用户GUID是可以接受的select
  • 查找表(例如StatusCodes) – 使用一个简短的CHAR代码; 它比INT更容易记忆,在很多情况下,纸张表格和用户也会使用它(例如Status =“E”表示“已过期”,“A”表示“已批准”,NADIS表示“未检测到石棉在样品中“)
  • 链接表 – FK的组合(例如EventId EventId, AttendeeId

所以理想情况下,您最终会得到一个自然的,人类可读的和令人难忘的PK,以及一个ORM友好的一个每个表的GUID。

警告:我维护的数据库往往是10万条logging,而不是数百万甚至数十亿,所以如果你有更大系统的经验,禁止我的build议,请随时忽略我!