在设计表时,我养成了一个习惯,就是让一个列是唯一的,并且我是主键 . 这取决于要求以三种方式实现:
-
自动递增的标识整数列 .
-
唯一标识符(GUID)
-
可用作行标识符列的短字符(x)或整数(或其他相对较小的数字类型)列
数字3将用于相当小的查找,主要是可能具有唯一静态长度字符串代码的读取表,或诸如年份或其他数字的数字值 .
在大多数情况下,所有其他表将具有自动递增整数或唯一标识符主键 .
问题:-)
我最近开始使用没有一致行标识符的数据库,主键目前在各个列上进行聚类 . 一些例子:
-
datetime / character
-
datetime / integer
-
datetime / varchar
-
char / nvarchar / nvarchar
这有一个有效的案例吗?我总是会为这些案例定义一个标识或唯一标识符列 .
此外,还有许多表没有主键 . 这有什么正当理由?
我试图理解为什么 table 的设计原样,这对我来说似乎是一个很大的混乱,但也许有充分的理由 .
第三个问题可以帮助我解释答案:如果使用多列来构成复合主键,这种方法与代理/人工密钥相比有特定的优势吗?我主要考虑的是性能,维护,管理等方面?
21 回答
我怀疑Steven A. Lowe卷起的报纸疗法是原始数据结构的设计者所必需的 .
另外,作为主键的GUIDs可能是一场表现难堪 . 我不推荐它 .
如果你真的想在这个古老的辩论中来回阅读所有来回,请在Stack Overflow上搜索“自然键” . 你应该得到结果页面 .
我遵循一些规则:
主键应尽可能小 . 首选数字类型,因为数字类型以比字符格式更紧凑的格式存储 . 这是因为大多数主键将是另一个表中的外键以及多个索引中使用的外键 . 密钥越小,索引越小,您将使用的缓存中的页面越少 .
主键永远不会改变 . 更新主键始终是不可能的 . 这是因为它最有可能在多个索引中使用并用作外键 . 更新单个主键可能会导致更改产生连锁反应 .
不要将"your problem primary key"用作逻辑模型主键 . 例如护照号码,社会安全号码或员工 Contract 号码,因为这些号码可以根据实际情况进行更改 .
在代理vs自然键上,我参考上面的规则 . 如果自然键很小并且永远不会改变,则它可以用作主键 . 如果自然键很大或可能会改变,我使用代理键 . 如果没有主键,我仍然会创建一个代理键,因为经验显示您将始终向您的架构添加表,并希望您放置一个主键 .
我也总是使用数字ID列 . 在oracle中我使用数字(18,0)没有真正的原因高于数字(12,0)(或者无论是int而不是long),也许我只是不想担心获得几十亿行数据库!
我还包括一个用于基本跟踪的创建和修改列(类型时间戳),它似乎很有用 .
我不介意在其他列组合上设置唯一约束,但我真的很喜欢我的id,创建,修改基线要求 .
这是我自己的经验法则,经过25年的开发经验,我已经确定了这一点 .
所有表都应该有一个自动递增的列主键 .
将其包含在任何可更新的视图中
主键在您的应用程序上下文中没有任何意义 . 这意味着它不应该是SKU,帐号或员工ID或对您的应用程序有意义的任何其他信息 . 它只是与实体关联的唯一密钥 .
数据库使用主键进行优化,除了标识特定实体或与特定实体相关之外,您的应用程序不应使用主键 .
始终使用单值主键使得执行UPSERT非常简单 .
使用其他索引来支持在您的应用程序中有意义的多列键 .
我出于一个简单的原因避免使用自然键 - 人为错误 . 尽管通常可以使用自然唯一标识符(SSN,VIN,帐号等),但它们需要人员正确输入它们 . 如果您使用SSN作为主键,有人会在数据输入过程中转换几个数字,并且不会立即发现错误,那么您将面临更改主键的问题 .
我的主键全部由数据库程序处理背景和用户永远不会意识到它们 .
对我而言,自然键和人工键是您在数据库中需要多少业务逻辑的问题 . Social Security number(SSN)就是一个很好的例子 .
“我的数据库中的每个客户端都必须拥有SSN . ” Bam,完成后,将其作为主键并完成它 . 请记住,当您的业务规则发生变化时,您就会被烧毁 .
由于我改变业务规则的经验,我自己不喜欢自然键 . 但如果你确定它不会改变,它可能会阻止一些关键的连接 .
我总是使用自动编号或身份字段 .
我曾为一个使用SSN作为主键的客户工作,然后由于HIPAA规则被迫改为“MemberID”,并且在更新相关表中的外键时引起了大量问题 . 坚持一致的标准列标准有助于我避免在我的所有项目中出现类似的问题 .
所有表 should 都有一个主键 . 否则,你所拥有的是一个HEAP - 在某些情况下,这可能是你想要的(当数据然后通过服务代理复制到另一个数据库或表时,重载插入负载) .
对于行数较少的查找表,可以使用3 CHAR代码作为主键,因为这比INT占用的空间少,但性能差异可以忽略不计 . 除此之外,我总是使用INT,除非你有一个参考表,它可能有一个由来自关联表的外键组成的复合主键 .
自然诗歌人工关键词是数据库社区中的一种宗教辩论 - 请参阅this article以及与之相关的其他内容 . 我既不赞成 always 拥有人工钥匙,也不赞成 never 拥有它们 . 我会根据具体情况做出决定,例如:
美国各州:我为德克萨斯州等提供了TXT,而不是德州的state_id = 1
员工:我很难找到其他有用的东西 . SSN或同等产品可能有效,但可能会出现一些问题,比如还没有提供他/她的SSN的新加入者 .
员工薪资历史:(employee_id,start_date) . 我会 not 创建一个artifical employee_salary_history_id . 它会起什么作用(除了"foolish consistency")
无论何处使用人工密钥,您都应该始终在自然键上声明唯一约束 . 例如,如果必须,请使用state_id,但是最好在state_code上声明一个唯一约束,否则你肯定最终得到:
表应始终具有主键 . 当它不是它应该是一个AutoIncrement字段 .
有时人们会省略主键,因为它们会传输大量数据,并且可能会减慢(依赖于数据库)进程 . 但是,它应该在它之后添加 .
关于链接表的一些评论,这是正确的,它是一个例外但是字段应该是FK以保持完整性,并且在某些情况下,如果链接中的重复未被授权,那些字段也可以是主键...但是保留在简单形式,因为异常是编程中常见的东西,应该存在主键以保持数据的完整性 .
主键有什么特别之处?
模式中表的目的是什么? table 钥匙的目的是什么?主键有什么特别之处?关于主键的讨论似乎忽略了主键是表的一部分,并且该表是模式的一部分 . 表和表关系的最佳选择应该是驱动所使用的密钥 .
表(和表关系)包含有关您要记录的信息的事实 . 这些事实应该是自足的,有意义的,易于理解的和不矛盾的 . 从设计角度来看,在模式中添加或删除的其他表不应影响相关表 . 必须存储仅与信息本身相关的数据的目的 . 了解表中存储的内容不应要求进行科学研究项目 . 为同一目的存储的事实不应存储多次 . 密钥是记录的信息的一部分或全部,它是唯一的,主键是专门指定的密钥,它是表的主要访问点(即应选择它用于数据一致性和使用,而不仅仅是插入性能) .
据说主键应该尽可能小 . 我会说钥匙应该只有必要的大小 . 应避免随意向表中添加无意义的字段 . 从随机添加的无意义字段中创建密钥甚至更糟,特别是当它破坏从另一个表到非主键的连接依赖性时 . 如果表中没有好的候选键,这是合理的,但如果用于所有表,这种情况肯定是架构设计不佳的标志 .
还有人说,主键永远不应该改变,因为更新主键始终是不可能的 . 但更新与删除后插入相同 . 通过这种逻辑,您不应该从具有一个键的表中删除记录,然后使用第二个键添加另一个记录 . 添加代理主键不会删除表中另一个键存在的事实 . 如果其他表通过代理键依赖于该含义,则更新表的非主键可能会破坏数据的含义(例如,具有代理键的状态表,其状态描述已从“已处理”更改为“已取消” '肯定会破坏数据) . 永远不可能的是破坏数据意义 .
话虽如此,我很感激今天企业中存在的许多设计不佳的数据库(无意义的代理键控数据损坏的1NF庞然大物),因为这意味着对于理解正确数据库设计的人们来说,工作量无穷无尽 . . 但在悲伤的一面,它确实有时让我感觉像西西弗斯,但我打赌他有一个401k(崩溃之前) . 远离博客和网站,以获取重要的数据库设计问题 . 如果您正在设计数据库,请查看CJ Date . 你也可以参考Celko for SQL Server,但前提是你先嗤之以鼻 . 在Oracle方面,参考Tom Kyte .
只是对经常被忽视的事情做出额外的评论 . 有时不使用代理键在子表中有好处 . 假设我们的设计允许您在一个数据库中运行多个公司(可能是托管解决方案,或者其他任何东西) .
假设我们有这些表和列:
如果最后一位没有意义,
Invoice.CompanyId
是两个外键的一部分,一个到CostCentre表,一个到CostElement表 . 主键是(InvoiceId,CompanyId) .在这个模型中,不可能搞砸和引用来自一家公司的CostElement和来自另一家公司的CostCentre . 如果在CostElement和CostCentre表上使用了代理键,那么它就是 .
搞砸的机会越少越好 .
我们做了很多连接,复合主键刚刚成为一种性能问题 . 即使您正在引入第二个候选键,简单的int或long也会解决许多问题,但是加入一个字段而不是三个字段会更容易,也更容易理解 .
我会对我对自然键的偏好保持警惕 - 尽可能使用它们,因为它们会让您的数据库管理生活变得更加轻松 . 我在公司 Build 了一个标准,所有表都有以下列:
行ID(GUID)
Creator(string;默认为当前用户的名字(T-SQL中的
SUSER_SNAME()
))创建(日期时间)
时间戳
行ID在每个表上都有一个唯一的键,并且在任何情况下都是每行自动生成的(并且权限可以阻止任何人编辑它),并且可以合理地保证在所有表和数据库中都是唯一的 . 如果任何ORM系统需要单个ID密钥,则可以使用该密钥 .
同时,如果可能的话,实际PK是一个自然键 . 我的内部规则如下:
人 - 使用代理键,例如INT . 如果是内部的,则Active Directory用户GUID是可接受的选择
查找表(例如StatusCodes) - 使用简短的CHAR代码;它比INT更容易记住,并且在许多情况下,纸质表格和用户也将使用它以简洁(例如,"Expired"的状态= "E","Approved"的"A","No Asbestos Detected In Sample"的"NADIS")
链接表 - FK的组合(例如
EventId, AttendeeId
)因此,理想情况下,您最终会得到一个自然的,人类可读且令人难忘的PK,以及一个ORM友好的one-ID-per-table GUID .
警告:我维护的数据库往往是数十万的记录,而不是数百万或数十亿,所以如果你有大型系统的经验,这反对我的建议,请随意忽略我!
一个自然的关键,如果可用,通常是最好的 . 因此,如果datetime / char唯一标识该行,并且这两个部分对该行有意义,那就太好了 .
如果只是日期时间是有意义的,并且刚刚添加了char以使其唯一,那么您可能只需要使用标识字段 .
GUIDs可以用作主键,但是您需要创建正确类型的GUID才能使其表现良好 .
您需要生成COMB GUID . 关于它和性能统计的一篇好文章是The Cost of GUIDs as Primary Keys .
在SQL中构建COMB GUID的一些代码也在Uniqueidentifier vs identity(archive)中 .
您应该使用包含多个字段的“复合”或“复合”主键 .
这是一个完全可以接受的解决方案,请here获取更多信息:)
从各个领域制作主键没有问题,这是一个自然键 .
您可以使用Identity列(与候选字段上的唯一索引相关联)来生成Surrogate Key .
这是一个古老的讨论 . 在大多数情况下,我更喜欢代理键 .
但没有任何借口可以缺少一把钥匙 .
RE: EDIT
是的,有很多争议:D
除了事实上它们是自然选择之外,我没有看到任何关于自然键的明显优势 . 你总是会想到Name,SocialNumber - 或类似的东西 - 而不是idPerson .
代理键是自然键具有的一些问题的答案(例如,传播变化) .
当你习惯代理时,它似乎更干净,更易于管理 .
但最终,你会发现这只是一种品味 - 或心态 - . 人们用自然键“思考得更好”,而其他人则不然 .
我寻找自然的主键并尽可能地使用它们 .
如果找不到自然键,我更喜欢GUID到INT,因为SQL Server使用树,并且总是在树中向键末尾添加键是不好的 .
在多对多联接的表上,我使用外键的复合主键 .
因为我很幸运能够使用SQL Server,所以我可以使用分析器和查询分析器研究执行计划和统计数据,并且可以非常轻松地了解我的密钥的执行情况 .
除了所有这些好的答案,我只想分享一篇我刚读过的好文章,The great primary-key debate .
只是引用几点:
在为每个表选择主键时,开发人员必须应用一些规则:
主键必须唯一标识每条记录 .
记录的主键值不能为空 .
创建记录时必须存在主键值 .
主键必须保持稳定 - 您无法更改主键字段 .
主键必须紧凑且包含尽可能少的属性 .
无法更改主键值 .
自然键(倾向于)打破规则 . 代理键符合规则 . (你最好仔细阅读那篇文章,值得你花时间!)