表中主键的最佳做法是什么?
在devise表格的时候,我养成了一个习惯,那就是有一列是唯一的,而且是主键。 这取决于要求以三种方式实现:
- 自动递增的标识整数列。
- 唯一标识符(GUID)
- 可用作行标识符列的短字符(x)或整数(或其他相对较小的数字types)列
数字3将用于相当小的查找,大多数读取的表格可能具有唯一的静态长度string代码,或数字值(例如年份或其他数字)。
大多数情况下,所有其他表将具有自动递增整数或唯一标识符主键。
问题:-)
我最近开始使用没有一致的行标识符的数据库,主键当前在不同的列上聚集在一起。 一些例子:
- date时间/字符
- date时间/整数
- date时间/ VARCHAR
- 炭/ NVARCHAR / nvarchar的
这有没有一个有效的案例? 我会一直为这些情况定义一个身份或唯一标识符列。
另外还有很多没有主键的表格。 如果有的话,有什么理由呢?
我试图理解为什么桌子是按照原样devise的,这对我来说似乎是一个很大的混乱,但也许有充分的理由。
第三个问题可以帮助我解释答案:在使用多列来组成复合主键的情况下,这种方法与代理/人工键有什么特别的优势? 我主要在考虑性能,维护,pipe理等方面?
我遵循一些规则:
- 主键应该尽可能小。 因为数字types以比字符格式更紧凑的格式存储,所以更喜欢数字types。 这是因为大多数主键将是另一个表中的外键以及在多个索引中使用。 键越小,索引越小,caching中使用的页面越less。
- 主键不应该改变。 更新主键应该始终是不可能的。 这是因为它最有可能在多个索引中使用并用作外键。 更新单个主键可能会导致更改的连锁反应。
- 不要使用“你的问题主键”作为你的逻辑模型主键。 例如护照号码,社会安全号码或员工合同号码,因为这些“主键”可以改变现实世界的情况。
关于代理与自然的关键,我参考了上面的规则。 如果自然键很小,并且永远不会改变,则可以将其用作主键。 如果自然键很大或可能改变,我使用代理键。 如果没有主键,我仍然使用代理键,因为经验表明,您将始终向您的模式添加表,并希望您将主键放在适当的位置。
自然经文人造密钥是数据库社区中的一种宗教争论 – 请参阅本文及其链接的其他内容。 我并不赞成总是有人造的钥匙,也不会永远拥有它们。 我会根据具体情况决定,例如:
- 美国:我会去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表。 主键是( InvoiceId , CompanyId )。
在这个模型中,不可能将一个公司的CostElement和另一个公司的CostCentre引用并引用。 如果在CostElement和CostCentre表上使用代理键,则会是。
更糟糕的机会越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议,请随时忽略我!