你觉得你的主键怎么样?

时间:2008-12-31 21:14:12

标签: algorithm database-design relational-database primary-key ddl

在我的团队的一次相当生气勃勃的讨论中,我被认为是大多数人喜欢的主键。我们有以下小组 -

  1. Int / BigInt哪个自动增量是足够好的主键。
  2. 至少应有3列构成主键。
  3. Id,GUID和人类可读的行标识符都应区别对待。
  4. PK的最佳方法是什么?如果你可以证明你的意见,这将是很棒的。上面有没有更好的方法?

    编辑:任何人都有一个简单的样本/算法来为可以扩展的行生成人类可读的标识符吗?

26 个答案:

答案 0 :(得分:69)

如果您要在偶尔连接的应用程序的数据库之间进行任何同步,那么您应该使用GUID作为主键。这是一种调试的痛苦,所以除了这种情况,我倾向于坚持自动增量的整数。

自动增量整数应该是您的默认值,使用它们应该是合理的。

答案 1 :(得分:55)

我没有看到一个答案指出(我认为)真正的基本点 - 即主键是保证你不会在同一个真实世界的表中获得两个条目实体(在数据库中建模)。这种观察有助于确定主键的优点和选择。

例如,在(US)州名称和代码表中,名称或代码可以是主键 - 它们构成两个不同的候选键,其中一个(通常是较短的 - 代码)是被选为主键。在函数依赖的理论(和连接依赖--1NF到5NF - 它是关键而不是主键的候选键。

对于反例,人名通常是主键的错误选择。有许多人以“约翰史密斯”或其他类似的名字命名;甚至考虑到中间名(记住:不是每个人都有一个 - 例如,我没有),有很多重复的余地。因此,人们不会使用名称作为主键。他们发明了人工密钥,例如社会安全号码(SSN)或员工编号,并用它们来指定个人。

理想的主键是短小,独特,令人难忘和自然。在这些特征中,唯一性是强制性的;考虑到现实世界数据的限制,其他人必须屈服。

因此,在确定给定表的主键时,您必须查看该表所代表的内容。表中的哪些列集或列集唯一标识表中的每一行?这些是候选键。现在,如果每个候选键由4列或5列组成,那么您可能会认为这些列太笨拙而无法制作好的主键(主要是基于短路)。在这种情况下,您可能会引入代理键 - 人工生成的数字。通常(但不总是)一个简单的32位整数就足以代替密钥。然后,将此代理键指定为主键。

但是,您必须仍然确保其他候选密钥(代理密钥也是候选密钥,以及所选主密钥)都保持为唯一标识符 - 通常通过放置对这些列集的唯一约束。

有时,人们发现难以确定哪一行是唯一的,但应该有一些事情要做,因为简单地重复一条信息并不会使它更加真实。如果你不小心并且确实得到两个(或更多)声称存储相同信息的行,然后你需要更新信息,则存在危险(特别是如果你使用游标),你将只更新一行而不是每一行,所以行不同步,没有人知道哪一行包含正确的信息。

在某些方面,这是一个非常强硬的观点。

我在使用GUID时没有特别的问题,但是它们往往是 big (如16-64字节),并且它们经常被使用。通常,一个非常好的4字节值就足够了。使用GUID,其中4字节值足以浪费磁盘空间,并且甚至减慢了对数据的索引访问速度,因为每个索引页的值更少,因此索引将更深,并且必须读取更多页面才能访问信息。

答案 2 :(得分:24)

这只是一个宗教问题,因为人们寻求普遍的正确答案。你的团队和这个SO线程显示出如此多的分歧的事实应该是一个线索,在不同的情况下有充分的理由使用你描述的所有解决方案。

  • 当表中没有其他属性或属性集适合唯一标识行时,代理键很有用。
  • 在可能的情况下,自然键是首选,以使表格更易于阅读。自然键还允许从属表中的外键包含实际值而不是代理ID。例如。当您需要存储state(CA,TX,NY)时,您也可以使用char(2)自然键而不是int。
  • 在适当的位置使用复合主键。当存在非常好的复合键时,不要不必要地添加“id”代理键(在多对多表中尤其如此)。每张表中三列密钥的授权绝对是无稽之谈。
  • 当您需要在多个站点上保留唯一性时,GUID是一种解决方案。如果您需要主键中的值是唯一的,但不是有序的或连续的,它们也很方便。
  • INT与BIGINT:表对主键需要 64位范围并不常见,但随着64位硬件的可用性增加,它不应成为负担,并且更确保您不会溢出。 INT当然是较小的,所以如果空间非常宝贵,它可以带来轻微的优势。

答案 3 :(得分:20)

我喜欢The Database Programmer blog作为此类信息的来源。

主键有3列?我会说列应该有业务规则要求的适当的唯一约束,但我仍然有一个单独的代理键。复合键意味着业务逻辑进入密钥。如果逻辑发生变化,那么整个架构都会被搞砸。

答案 4 :(得分:15)

我喜欢我的独特。

答案 5 :(得分:9)

稍微偏离主题,但我觉得有必要加入......

如果您的主键是GUID, 使其成为聚集索引。由于GUID是非顺序的,因此几乎每次插入时数据都将重新排列在磁盘上。 (哎呀。)如果使用GUID作为主键,它们应该是非聚簇索引。

答案 6 :(得分:9)

我总是使用代理键。代理键(通常是标识列,自动增量或GUID)是数据本身不存在密钥的代理键。另一方面,自然键是一个唯一标识行的键。就像我在生活中所说的那样,几乎没有任何真正的自然键。甚至像美国的SSN这样的东西也不是天生的关键。复合主键是一种等待发生的灾难。您无法编辑任何数据(这是任何自然键的复合或无复合的主要缺点),但更糟糕的是使用复合键,现在您必须将该关键数据保存到每个相关表中。真是个巨大的浪费。

现在,为了选择代理键,我坚持使用标识列(我主要在MS SQL Server中工作)。 GUID太大,Microsoft建议反对将它们用作PK。如果你有多个服务器,你需要做的只是增加10或20或你认为你需要同步/扩展到的最大服务器数量,并且只需要为每个后续服务器上的每个表添加种子,你永远不会有数据冲突。

当然,由于增量,我将标识列设为BigInt(也称为long [64位])。

进行一些数学运算,即使你使增量为100,你的表中仍然可以有92,233,720,368,547,758(> 92千万亿)行。

答案 7 :(得分:9)

我认为在短语“Primary”Key中使用“Primary”这个词是真正意义上的,具有误导性。

首先,使用“key”是表中必须唯一的属性或属性集的定义,

然后,任何密钥都有几个通常相互矛盾的目的。

  1. 将连接条件用作子表中与该父表有关系的一个或多个记录。 (在这些子表中明确或隐式定义外键)
  2. (相关)确保子记录必须在父选项卡中具有父记录; e(子表FK必须作为父表中的键存在)
  3. 增加需要快速查找表中特定记录/行的查询的性能。

  4. 通过防止表示相同逻辑实体的重复行插入表来确保数据一致性。 (这通常称为“自然”键,应该包含相对不变的表(实体)属性。)

  5. 显然,任何非有意义的非自然键(如GUID或自动生成的整数)完全无法满足#4。

    但是,通常,对于许多(大多数)表,一个可以提供#4的完全自然的键通常由多个属性组成,并且过宽,或者太宽以至于将其用于#1,#2或#3目的将导致不可接受的性能后果。

    答案很简单。使用两者。对其他子表中的所有Joins和FK使用简单的自动生成整数键,但要确保每个需要数据一致性的表(很少有表没有)具有备用的自然唯一键,以防止插入不一致的数据行。 ..另外,如果你总是同时使用两者,那么所有反对使用自然键的反对意见(如果它改变了怎么办?我必须改变它被引用为FK的每个地方)都没有实际意义,因为你没有使用它。 ..你只是在一个PK中使用它,以避免不一致的重复数据......

    对于GUID,要非常小心地使用它们,因为在索引中使用guid可以软管索引碎片。用于创建它们的最常用算法将guid的“随机”部分放在最重要的位位置......这增加了对添加新行时常规索引碎片整理/重新索引的要求。

答案 8 :(得分:8)

你不应该做的一件事是使用智能钥匙。这是关键,有关记录的信息在密钥本身中编码,最终会咬你。

我在一个地方工作,其中主键是帐户ID,它是字母和数字的组合。我不记得任何细节,但是,例如,那些特定类型的帐户将在600范围内,而另一种类型,从400开始。这很好,直到该客户决定要求两者工作类型。或者改变了他们所做的工作类型。

另一个地方,使用树中的位置作为记录的主键。所以会有如下记录。

Cat1.subcatA.record1
Cat1.subcatA.record2
Cat1.subcatB.record1
Cat2.subcatA.record1

当然,客户想要的第一件事就是在树中移动物品。整套软件在此之前就已经死了。

拜托,拜托,如果您正在编写我需要维护的代码,请不要使用智能密钥!

答案 9 :(得分:4)

我喜欢将自动增量作为主键。我内心深处知道这是一个警察,但它确实使数据在添加时很容易排序(ORDER BY ID DESC,f'r实例)。

3列听起来非常严厉,人性化解析。

这就是权衡 - 你需要多少关系能力,而不是让这个表格在这里对人类进行询问是可以理解的(与存储过程或程序化界面相比)。

自动增量适用于我们人类。 : - (

答案 10 :(得分:4)

一般来说,这取决于。

就个人而言,我喜欢自动增量投注。

但是,我可以告诉你的一件事是永远不要相信来自其他来源的数据作为你的钥匙。我发誓,每次我做完,它都会回来咬我。好吧,再也不会!

答案 11 :(得分:3)

  

至少应该有3列构成主键。

我不明白这一点。

你在谈论一个“自然键”,例如“姓名和出生日期”?一个自然键可能是理想的,如果它存在,但大多数自然键的候选者要么不是唯一的(几个具有相同名称的人),要么不恒定(有人可以更改其名称)。

  

Int / BigInt哪个自动增量是足够好的主键。

我更喜欢Guid。自动增量的一个潜在问题是值(例如“订单ID”)由数据库实例(例如“销售数据库”)分配......这将不会完全起作用(相反,您开始需要复合键),如果您需要合并由多个数据库实例创建的数据(例如,从多个销售办事处各自使用自己的数据库)。

答案 12 :(得分:3)

RE GUID的

注意这是否真的非常非常真正大数据库,大量负载和快速访问。

在我上一份工作中,我们拥有1亿到5亿条记录的数据库,我们的数据库人员强烈反对GUID,以及适当大小的十进制数。他们认为(在Oracle下)字符串内部存储的大小差异Guid - vs- a十进制值会在查找中产生非常显着的差异。 (更大的键=更深的树遍历)

GUID的随机性也会显着降低索引页面的填充因子 - 这会大大增加撕裂和磁盘I / O.

答案 13 :(得分:2)

自动增加列。我能够使我的代码与SQL Server或Oracle无缝协作,一个使用身份,另一个使用序列通过我的DAL,我不能更快乐。我同意,如果您正在复制或发送数据以便稍后在处理时接收它,则GUID有时是必要的。

答案 14 :(得分:2)

我总是使用代理键 - 一个名为'id'的自动增量整数。即使另一种选择显而易见,我也可以看到很多理由:

  • 一致性
  • 数据独立(唯一,不会因格式更改而被破坏)
  • 人类可读

......没有明智的理由不:

  • 加入时的歧义? - 别名表是一种更好的做法,恕我直言。
  • 最佳表格? - 每个条目删除一个字节是过早优化,恕我直言
  • 每桌决定? - 不再一致
  • 缩放问题? - 呃?为什么?
  • 分层数据结构? - 这是非正规化,是宗教的另一个主题。我只想说我在理论上的一些情况下是粉丝,但从未在实践中:)

我没有想到或遇到的明显理由仍然受到欢迎......

答案 15 :(得分:1)

几乎总是整数。

除了更小/更快的处理之外,它们还有其他充分的理由。你更愿意写下哪一个 - “404040”或“3463b5a2-a02b-4fd4-aa0f-1d3c0450026c”?

答案 16 :(得分:1)

我接近主键的方式(我觉得最好)是避免使用“默认”方法。这意味着不是只是单击一个自动递增的整数并调用它一天,我会查看问题并说“是否有一列或一组列始终是unqiue且不会更改?”如果答案是肯定的,那么我采用这种方法。

答案 17 :(得分:1)

过去一个基本的定义答案,构成好的主键的内容主要归功于宗教和破坏房间的论点。如果您拥有的东西,并且将始终唯一地映射到单个行,那么它将作为主键正常工作。过去,还有其他一些考虑因素:

  • 主键定义是不是过于复杂?是否为了遵循“最佳实践”而避免引入不必要的复杂性?
  • 是否有更好的主键需要更少的数据库处理开销(即INTEGER与VARCHAR等)?
  • 我绝对肯定我的主键的唯一性和定义不变量不会改变吗?

最后一个可能是吸引大多数人使用诸如GUID或自增量整数列之类的东西,因为依赖于诸如地址,电话号码,名字/姓氏之类的东西,只是不要削减它。关于我能想到的人的唯一不变量是SSN,但是我甚至不能完全肯定那些永远独特的人。

希望这有助于增加一些清晰度......

答案 18 :(得分:1)

我只使用自动增量int或GUID。 99%的时间我使用自动增量int。这正是我第一次学习数据库时所教会使用的,并且从未遇到过不使用它们的原因(虽然我知道为什么GUID会更好)。

我喜欢自动增量整数,因为它有助于提高可读性。例如,我可以说“看看记录129383”,并且很容易让某人进去找到它。使用GUID几乎不可能做到。

答案 19 :(得分:1)

只是略微相关,但是我最近开始做的一件事是当我有小分类表(基本上代表代码中的ENUM)时,我会将主键设为char(3)或char(4) )。然后我创建代表查找值的主键。

例如,我有一个内部销售代理的报价系统。我们有“成本类别”,每个报价行项目都分配了一个...所以我有一个名为'tCostCategories'的类型查找表,其中主键是'MTL','SVC','TRV','TAX', 'ODC'。查找表中的其他列存储了更多详细信息,例如代码的正常英语含义,“材料”,“服务”,“旅行”,“税收”,“其他直接成本”等。

这非常好,因为它不使用任何空间而不是int,当你查看源数据时,你不必链接查找表来知道值是什么。例如,引用行可能如下所示:

1 PartNumber $ 40 MTL
2 OtherPartNumber $ 29.99 SVC
3 PartNumber2 $ 150 TRV

使用int来表示类别然后在所有行上链接1,2,3要容易得多 - 你的数据就在你面前,而且性能似乎根本没有受到影响(不是我真的经过了测试。)

就真正的问题而言......我喜欢RowGUID uniqueidentifiers。我不是百分之百,但不是所有的行都有内部的RowGuid吗?如果是这样,那么使用RowGuid实际上会占用比int更少的空间(或其他任何东西。)我所知道的是,如果它足够好M $在GreatPlains中使用那么它对我来说已经足够了。 (我应该躲?)

答案 20 :(得分:1)

哦,我使用GUID的另一个原因 - 我使用分层数据结构。也就是说,我有一个表'Company'和一个表'Vendor',其中主键匹配。但我也有一个'制造商'表,也'继承'公司。供应商和制造商共有的字段不会出现在这些表中 - 它们出现在公司中。在这个设置中,使用int比Guids更痛苦。至少,您不能使用身份主键。

答案 21 :(得分:1)

Guids.period。

如果您需要扩展或需要通过其他方式分配主键,他们将成为您的朋友。您可以为其他所有内容添加索引。


更新以澄清我的陈述。

我曾经在很多不同类型的网站上工作过。从小型单一服务器交易到支持多个数据库和Web服务器的大型服务器。肯定有一些应用程序可以自动增加整数作为主键。但是,那些不适合我的工作模式。

使用GUID时,您可以在任何地方生成ID。它可以由远程服务器,您的Web应用程序,在数据库本身内生成,甚至可以在多主机情况下的多个数据库中生成。

另一方面,只能在主数据库中安全地生成自动递增的INT。同样,如果你的应用程序与那个支持数据库服务器密切相关,那么可能可以,并且扩展不是你所关心的。

当然,使用GUID意味着您必须每晚重建索引流程。但是,如果您使用的是除自动增量INT之外的任何其他内容,则无论如何都应该这样做。哎呀,即使将INT作为主要内容,您可能还需要重新生成其他索引来处理碎片。因此,使用GUID并不会完全添加其他问题,因为无论如何都需要执行这些任务。

如果您看一下较大的应用程序,您会发现一些重要的事情:它们都使用Base64编码的GUID作为密钥。原因很简单,GUID的使用使您可以轻松地扩展 out ,而在尝试扩展INT时可能会有很多跳跃。

我们最新的应用程序经历了一段时间的重插入,持续了大约一个月。之后,90%以上的查询都是报告选择。为了增加容量,我可以在这个大插入期间启动额外的数据库服务器;然后很容易将它们合并到一个DB中进行报告。试图用INT进行这项工作将是一场绝对的噩梦。

坦率地说,无论何时集群数据库或设置复制,数据库服务器都会要求您在桌面上拥有GUID。所以,如果您认为您的系统可能需要增长,那么选择一个好的系统。

答案 22 :(得分:1)

我倾向于使用选项#1或#3,具体取决于大小,连接人数以及是否是多数据库服务器情况。

选项#2对我没有多大意义。如果三者中的任何一个不足以识别唯一记录,则可能(不经过额外的阴谋)两个记录在所有三列中显示具有相同值的两个记录。如果要对三者的任意组合强制执行唯一性,则只需为它们添加索引。

答案 23 :(得分:1)

每当我信任他们时,我都喜欢自然键。我愿意支付一个小的性价格,以便使用对主题专家有意义的密钥。

对于描述实体的表,应该有一个简单的自然键,以与主题人员相同的方式识别各个实例。如果主题没有其中一个实体的可信标识符,那么我将使用代理键。

对于描述关系的表,我使用复合键,其中每个组件引用参与关系的实体,因此引用实体表中的行。同样,使用复合键的性能损失通常很小。

正如其他人所指出的那样,“主键”这个词有点误导。在关系数据模型中,使用的术语是“候选键”。单个表可能有几个候选键。从逻辑上讲,每一个都和另一个一样好。选择其中一个作为“主要”并通过该键进行所有引用只是设计师可以做出的选择。

答案 24 :(得分:1)

这是一个经典的“它取决于”。每个项目都没有正确答案。我喜欢不同的情况。这取决于我是否使用ORM以及它支持的内容。它取决于整体架构(分布式或非分布式)等。只需选择一个你认为可行的方法,然后继续争论标签和空格。

答案 25 :(得分:0)

无论你是否意识到,这都是一个复杂的主题。可能属于StackOverflow FAQ的部分。

我不应该在这里问什么样的问题?

避免提出主观,议论或需要进行深入讨论的问题。这是一个可以回答问题的地方!

多年来一直争论不休,并将继续争论多年。我所看到的唯一一致的暗示是,答案在某种程度上是可以预测的,这取决于你是否要求OO人(GUID是唯一的方法!),数据建模者(自然键是唯一的方法!),或面向绩效的DBA(INT是唯一的方法!)。