在我的数据库中,我倾向于养成使用自动递增的整数主键的习惯,该主键带有id
我制作的每个表的名称,以便对任何特定的行进行唯一的查找。
这是一个坏主意吗?这样做有什么弊端吗?有时我会拥有多个指标喜欢id, profile_id, subscriptions
这里id
是唯一的标识符,profile_id
链接到国外id
一的Profile
表等
还是在某些情况下您不想添加这样的字段?
在我的数据库中,我倾向于养成使用自动递增的整数主键的习惯,该主键带有id
我制作的每个表的名称,以便对任何特定的行进行唯一的查找。
这是一个坏主意吗?这样做有什么弊端吗?有时我会拥有多个指标喜欢id, profile_id, subscriptions
这里id
是唯一的标识符,profile_id
链接到国外id
一的Profile
表等
还是在某些情况下您不想添加这样的字段?
Answers:
保证唯一的行标识符绝对不是一个坏主意。我想我不应该说永远不要-但是让我们在绝大多数情况下都是一个好主意。
理论上的潜在缺点包括要维护的额外索引和使用的额外存储空间。对于我而言,不使用它从来都不是足够的理由。
TableName.id
而不是TableName.TableName_id
,因为那id
还要指的是什么?如果我在表中还有另一个id字段,那么如果它指向其他表,我将在表名前添加前缀
WITHOUT ROWID
表(带有显式PRIMARY KEY
)作为优化。但是否则,INTEGER PRIMARY KEY
列是rowid的别名。
我不同意以前的所有答案。在所有表中添加自动递增字段是一个坏主意的原因有很多。
如果您的表中没有明显的键,那么自动递增字段似乎是个好主意。毕竟,您不想select * from blog where body = '[10000 character string]'
。你宁愿select * from blog where id = 42
。我认为在大多数情况下,您真正想要的是唯一的标识符。不是顺序的唯一标识符。您可能想改为使用通用唯一标识符。
大多数数据库中都有函数来生成随机唯一标识符(uuid
在mysql中,newid
在mssql中为postgres。)。这些功能使您可以随时将数据生成到不同计算机上的多个数据库中,并且彼此之间没有网络连接,并且合并数据时冲突为零。这使您可以更轻松地设置多个服务器,甚至可以使用微服务来设置数据中心。
这也可以避免攻击者猜测他们不应该访问的页面的url。如果有的话,https://example.com/user/1263
也可能有一个 https://example.com/user/1262
。这可以允许用户配置文件页面中的安全利用自动化。
在很多情况下,uuid列无用甚至有害。假设您有一个社交网络。有一张users
桌子和一张friends
桌子。好友表包含两个用户ID列和一个自动递增字段。您想3
和成为朋友5
,所以您将其插入3,5
数据库。数据库会添加一个自动增量ID并存储1,3,5
。用户3
以某种方式再次单击“添加朋友”按钮。您3,5
再次插入数据库,数据库将添加一个自动增量ID并插入2,3,5
。但是现在3
和5
彼此成为朋友了两次!那是在浪费空间,如果您考虑一下,自动增量列也是如此。您需要查看的所有内容a
和b
是的朋友是选择带有这两个值的行。它们一起是唯一的行标识符。(您可能想要做写一些逻辑,以确保3,5
和5,3
被重复数据删除。)
在某些情况下,顺序ID可能很有用,例如在构建url缩短器时,但是大多数情况下(甚至使用url缩短器),您真正想使用的是随机生成的唯一ID。
TL; DR:如果您还没有识别每一行的独特方法,请使用UUID代替自动增量。
自动增量键主要具有优势。
但是一些可能的缺点可能是:
这是Wikipedia文章中有关代理密钥的缺点的部分。
恰恰相反,不,您不必始终拥有数字AutoInc PK。
如果您仔细分析数据,则通常会识别数据中的自然键。当数据对业务具有内在含义时,通常就是这种情况。有时,PK是来自古代系统的人工制品,业务用户将其用作第二语言来描述其系统的属性。例如,我已经看到车辆VIN号用作车队管理系统中“车辆”表的主键。
无论它起源于什么,如果您已经有一个唯一的标识符,请使用它。不要创建第二个毫无意义的主键;这很浪费,可能会导致错误。
有时,您可以使用AutoInc PK生成客户有意义的值,例如策略编号。将起始值设置为合理的值,并应用有关前导零等的业务规则。这可能是“两全其美”的方法。
当您有少量相对静态的值时,请使用对系统用户有意义的值。当您可以使用L,C,H(其中L,H和C代表保险“政策类型”上下文中的人寿,汽车和房屋)时,为什么要使用1,2,3,或者回到VIN示例,如何使用“ TO”对于丰田?所有Toyata汽车的VIN都以“ TO”开头。这是用户要记住的一件事情,它使他们引入编程和用户错误的可能性降低,甚至可以用作管理报告中完整描述的替代品,从而使报告更简单写,也许更快地生成。
对此的进一步发展可能是“过头的桥梁”,我一般不建议这样做,但为了完整起见我将其包括在内,您可能会发现它很好用。也就是说,使用描述作为主键。对于快速变化的数据,这是令人讨厌的。对于“ 所有时间”报告的非常静态的数据,也许不是。只需提及它,就可以坐在那里。
我确实使用AutoInc PK,我只是动脑筋,首先寻找更好的替代方案。数据库设计的艺术正在使有意义的事情可以快速查询。加入过多会阻碍这一点。
编辑不需要自动生成的PK的另一个关键情况是表的情况,该表表示其他两个表的交集。与汽车类似,汽车有0..n个附件,每个附件都可以在许多汽车上找到。为此,您可以创建一个Car_Accessory表,其中包含Car和Accessory的PK以及有关链接日期等的其他相关信息。
您通常不需要的是此表上的AutoInc PK-只能通过“告诉我这辆车上有哪些附件”汽车或从附件“告诉我这辆车上有该附件”来访问它。
Don't create a second, meaningless primary key; it's wasteful and may cause errors.
但是,如果您为记录建立唯一性的方式是6列的组合,那么始终将所有6列连接起来就很容易出错。数据自然具有PK,但是最好使用一id
列,并对这6列使用唯一约束。
许多表已经具有自然的唯一ID。不要在这些表上添加另一个唯一的id列(自动递增或其他方式)。请使用自然的唯一ID。如果添加另一个唯一ID,则您的数据实际上就具有冗余(重复或依赖性)。这违反了规范化的原则。一个唯一的ID依赖于另一个唯一ID。这意味着,他们必须完全同步,在保持始终在每一个系统管理这些行。这只是数据完整性中的另一个脆弱性,您实际上并不需要长期管理和验证。
如今,大多数表实际上并不需要额外的唯一id列会带来很小的性能提升(有时甚至会降低性能)。作为IT的一般规则,请避免像瘟疫一样造成冗余!建议您在任何地方抵抗它。这是恶心。并注意报价。一切都应该尽可能简单,但不要简单。即使天生的ID看起来不太整洁,也没有两个ID足以满足要求。
每个规则都有一个例外,因此在用于导出/导入的登台表以及类似的单向表或临时表上,您可能不需要整数自动增量ID。您还希望在分布式系统上使用GUID而不是ID。
这里有许多答案建议应采用现有的唯一密钥。好吧,即使它有150个字符?我不这么认为。
现在我的要点是:
看起来自动递增整数ID的反对者正在谈论最多20个表的小型数据库。他们在那里可以为每个桌子提供单独的方法。
但是,一旦您拥有具有400多个表的ERP,那么在任何地方(上述情况除外)都具有整数自动递增ID 就是很有意义的。即使存在其他唯一字段并确保其唯一性,您也不必依赖它们。
JOIN
无需检查键是什么就可以进行表操作。在较大的系统上,有必要忽略这些主键的次要好处,并在大多数情况下始终使用整数自动增量ID。使用现有的唯一字段作为主键可能会为每条记录节省一些字节,但是在当今的数据库引擎中,额外的存储或索引时间不会造成任何问题。实际上,您浪费了更多的金钱和资源,浪费了开发人员/维护人员的时间。当今的软件应针对程序员的时间和精力进行优化-具有一致ID的哪种方法可以更好地实现。
多余的设计不是一个好习惯。即-不需要一个总是自动递增int主键的做法不是一个好习惯。
让我们看一个不需要一个的例子。
您有一个文章表-它有一个int主键id
和一个名为的varchar列title
。
您还拥有一个充满文章类别的表格id
-int主键varchar name
。
“文章”表中的一行id
包含5,其中一个是title
“如何用黄油煮鹅”。您想要将该文章与“类别”表中的以下行链接:“禽”(id:20),“鹅”(id:12),“烹饪”(id:2),“黄油”(id:9) 。
现在,您有2个表格:文章和类别。您如何在两者之间建立关系?
您可能有一个包含3列的表:id(主键),article_id(外键),category_id(外键)。但是现在您有了类似的东西:
| id | a_id | c_id | | 1 | 5 | 20 | | 2 | 5 | 12 | | 3 | 5 | 2 |
更好的解决方案是使主键由2列组成。
| a_id | c_id | | 5 | 20 | | 5 | 12 | | 5 | 2 |
这可以通过执行以下操作来完成:
create table articles_categories (
article_id bigint,
category_id bigint,
primary key (article_id, category_id)
) engine=InnoDB;
不使用自动递增整数的另一个原因是,如果您将UUID用作主键。
根据其定义,UUID是唯一的,它完成与使用唯一整数相同的操作。与整数相比,它们还具有自己的优点(和缺点)。例如,对于UUID,您知道您要引用的唯一字符串指向特定的数据记录。如果您没有1个中央数据库,或者应用程序具有脱机创建数据记录的能力(以后再将它们上传到数据库),这将很有用。
最后,您无需将主键视为事物。您需要将它们视为它们执行的功能。为什么需要主键?为了能够使用将来不会更改的字段从表中唯一标识特定的数据集。您是否需要一个特定的列id
来执行此操作,或者可以将此唯一标识基于其他(不可变的)数据?
还是在某些情况下您不想添加这样的字段?
当然。
首先,有些数据库没有自动增量(例如,Oracle,当然不是周围最小的竞争者之一)。这应该是不是每个人都喜欢或需要它们的第一个迹象。
更重要的是,考虑一下ID实际是什么-它是数据的主键。如果您的表具有不同的主键,那么您就不需要ID,也应该没有ID。例如,一个表(EMPLOYEE_ID, TEAM_ID)
(每个雇员可以同时在多个团队中)具有一个明确定义的主键,该主键由这两个ID组成。添加自动增量ID
列(这也是该表的主键)根本没有任何意义。现在,您在拖着2个主键,“主键”中的第一个单词应该给您提示,您实际上应该只有一个。
在为“长期”数据(我希望插入一次并无限期保持记录的记录)定义新表时,我通常使用“身份”列(自动递增整数),即使它们最终通过设置位字段而被“逻辑删除” )。
当您不想使用它们时,我会想到几种情况,其中大多数归结为一种情况,即数据库的一个实例上的一个表不能成为新ID值的权威来源:
正如我所希望提到的,有一些变通办法可以在这些情况下使用标识列,但是在大多数情况下,从标识整数列升级到GUID更简单,并且可以更彻底地解决问题。
ID, ID_M, ID_N
由于将属性附加到M:N关系的实例,您仍然需要在M:N表中使用ID(使用column )。
一个自动递增的(标识)主键是一个好主意,除了要注意,它在数据库上下文和该数据库的直接客户端之外毫无意义。例如,如果您将某些数据传输并存储到另一个数据库中,然后继续将不同的数据写入两个数据库表,则ID会有所不同-即,一个数据库中ID为42的数据不一定与数据匹配另一个ID为42。
鉴于此,如果仍然需要能够在数据库外部唯一地标识行(并且经常是这样),那么您必须为此使用其他密钥。精心选择的业务密钥可以做到,但是您通常最终会处于需要保证唯一性的大量列的位置。另一种技术是将ID列作为自动递增的聚集主键,而将另一个uniqueidentifier(guid)列作为非聚集的唯一键,以唯一地标识行在世界上任何地方的目的。在这种情况下,您仍然拥有一个自动递增的密钥的原因是因为对自动递增的密钥进行聚类和索引比对GUI进行同样的操作更为有效。
您可能不希望自动递增键的一种情况是多对多表,其中主键是其他两个表的Id列的组合(您仍然可以在此处使用自动递增键,但是我看不出重点)。
另一个问题是自动递增密钥的数据类型。使用Int32可为您提供较大但相对有限的值范围。就我个人而言,我经常将bigint列用作ID,以便实际上无需担心用完值。
正如其他人提出的增加主键的理由一样,我将为GUID作一个:
编辑:重复点
作为良好设计的原则,每个表都应具有一种可靠的方式来唯一标识一行。尽管这就是主键的用途,但并不总是要求存在主键。向每个表添加主键不是一个坏习惯,因为它提供了唯一的行标识,但是可能没有必要。
为了保持两个或多个表的行之间的可靠关系,您需要通过外键来实现,因此至少在某些表中需要主键。向每个表添加主键可以更轻松地扩展数据库设计,以便在需要时向现有数据添加新表或关系。提前计划永远是一件好事。
作为一项基本原则(也许是硬性规定),主键的值在其整个生命周期中都不应改变。明智的做法是假设连续的任何业务数据在其生命周期内都可能发生变化,因此任何业务数据都不适合用作主键。这就是为什么抽象的东西(例如自动递增的整数)通常是个好主意的原因。但是,自动递增的整数确实有其局限性。
如果您的数据在数据库中只会存在生命,那么自动递增的整数就可以了。但是,正如在其他答案中提到的那样,如果您希望共享,同步数据或以其他方式使数据在数据库之外存在生命,则自动递增的整数会导致较差的主键。更好的选择是GUID(又名uuid“通用唯一ID”)。
该问题和许多答案都遗漏了一个重要的问题,即每个表的所有自然键都仅驻留在数据库的逻辑架构中,而每个表的所有代理键都仅驻留在数据库的物理架构中。其他答案仅讨论整数与GUID代理键的相对好处,而没有讨论为什么正确使用代理键的原因以及何时使用。
顺便说一句:让我们避免使用定义不正确和不精确的术语主键。它是关系前数据模型的产物,首先被(不明智地)选择加入关系模型,然后由各种RDBMS供应商选择加入物理域。它的使用仅用于混淆语义。
从关系模型中注意到,为了使数据库逻辑模式采用第一范式,每个表都必须具有用户可见的字段集(称为自然键),该字段唯一地标识表的每一行。在大多数情况下,这样的自然键很容易识别,但是有时必须构造一个自然键,无论是平局还是其他。但是,这样构造的密钥始终对用户仍然可见,因此始终驻留在数据库的逻辑模式中。
相比之下,表上的任何替代键都完全位于数据库的物理模式中(因此,出于安全性原因和维护数据库完整性的考虑,必须始终对数据库用户完全不可见)。引入代理密钥的唯一原因是为了解决数据库的物理维护和使用中的性能问题。无论是联接,复制,数据的多个硬件源还是其他。
由于引入代理密钥的唯一原因是性能,因此让我们假设我们希望它具有高性能。如果出现了性能问题,那么我们必然希望使代理密钥尽可能地窄(不会妨碍硬件,因此通常会省略短整数和字节)。连接性能取决于最小的索引高度,因此4字节整数是一个自然的解决方案。如果您的性能问题是插入率,那么4字节整数也是一个自然的解决方案(取决于RDBMS的内部)。如果表的性能问题是复制或多个数据源,而不是某些其他替代键技术,则GUID或两部分键(主机ID +整数)可能更适合。我个人并不是GUID的最爱,但它们很方便。
综上所述,并非所有表都需要代理键(任何类型);仅在认为对于执行所考虑的表格必要时才使用它们。无论您偏爱哪种常用的代理关键技术,在做出选择之前都要仔细考虑表的实际需求。更改表的替代关键技术选择将使您筋疲力尽。记录表的关键性能指标,以便您的继任者了解所做的选择。
特别案例
如果您的业务需求为了审计(或其他目的)而要求对交易进行顺序编号,则该字段不是替代键;这是自然键(有额外要求)。从文档中,自动递增的整数只会生成代理密钥,因此请找到另一种机制来生成它。显然,某种监视器是必要的,并且如果您要从多个站点采购交易,则一个站点将是特殊的,因为它是该监视器的指定宿主站点。
如果您的表永远不会超过一百行,那么索引高度就无关紧要;每次访问都将通过表扫描进行。但是,长字符串的字符串比较仍将比4字节整数的比较昂贵,并且比GUID的比较昂贵。
由char(4)代码字段键控的代码值表的性能应与具有4字节整数的代码值相同。尽管我没有证明这一点,但我经常使用该假设,并且从未有过理reason的理由。
这不仅不是一个好习惯,而且实际上在Bill Karwin的SQL Antipatterns一书中将其描述为反模式。
并非每个表都需要一个伪键-一个具有任意值的主键,而不是具有模型语义值的主键-并且没有理由总是调用它id
。
这非常通用-否则您将需要验证密钥实际上是唯一的。这将通过查看所有其他键来完成,这将非常耗时。随着您的记录数接近密钥溢出值,拥有增量密钥会变得很昂贵。
我通常使指针更明显,例如ref_{table}
或类似的字段名称。
如果不需要从外部指向记录,则不需要ID。
unsigned int
字段类型,否则限制为该数字的一半。
我不会说应该总是这样做。我这里有一张没有唯一键的表,它不需要一个。这是审核日志。永远不会有更新,查询会将所有更改返回到所记录的内容,但这是可以合理做到的最好方法,这需要人工定义错误的更改。(如果代码允许的话,它本来就不允许这样做的!)
主键的自动递增计数器不是一个好主意。这是因为您需要返回数据库以查找下一个键,然后在插入数据之前将其递增一。
话虽如此,我通常会使用数据库可以提供的任何主键,而不是将其作为应用程序的一部分。
通过让数据库为您本地提供它,它可以确保密钥对于它的需求是唯一的。
当然,并非所有数据库都支持它。在这种情况下,我通常使用一个表来存储密钥存储区,并使用在应用程序中管理的上限和下限范围。这是我发现的性能最高的解决方案,因为您可以得到10000个数字范围,并在应用程序实例上自动递增它们。另一个应用程序实例可以使用另一个数字桶。您确实需要足够大的主键原语,例如64位长。
我不将UUID用作主键,因为构建和存储UUID的成本比将long值加1的成本高得多。UUID仍然处理生日悖论,因为理论上可能会出现重复。