始终拥有一个自动增量整数主键是一种好习惯吗?


191

在我的数据库中,我倾向于养成使用自动递增的整数主键的习惯,该主键带有id我制作的每个表的名称,以便对任何特定的行进行唯一的查找。

这是一个坏主意吗?这样做有什么弊端吗?有时我会拥有多个指标喜欢id, profile_id, subscriptions这里id是唯一的标识符,profile_id链接到国外id一的Profile表等

还是在某些情况下您不想添加这样的字段?


61
看看一个德国坦克问题为例,其中一个普通的自动递增标识符是一个问题。当然,这仅在您公开使用ID时才重要。
Bergi

24
@ArukaJ关键是它泄漏了有关系统的某些信息。例如,假设数据库包含用户编写的帖子,每个帖子都有一个顺序ID。假设您发布了四个帖子,每个帖子都有一个ID:凌晨4点(20),凌晨5点(25),晚上8点(100)和晚上9点(200)。通过查看ID,您可以看到在凌晨4点到凌晨5点之间仅添加了5条帖子,而在晚上8点至晚上9点之间添加了100条帖子。如果您想抽出时间进行拒绝服务攻击,那可能是有价值的信息。
约书亚·泰勒

29
对于每个抱怨“德国坦克问题”的人。...如果阻止某人访问数据的唯一原因是您的URL中的一个键,那么...您面临的问题要比GUID和Auto INT更大。
马修·怀特

11
@MatthewWhited不仅仅是交换URL中的参数。假设您使用一个站点,并且一次创建资产100,一次创建t资产120 t + 60。如果您可以清楚地看到这两个ID(100和120),那么您现在知道存在的资产总数,以及它们的大致创建速度。这是信息泄漏。这不是纯粹的假设。
克里斯·海斯

15
永远 …… 是一种好习惯吗?”
brian_o

Answers:


137

保证唯一的行标识符绝对不是一个坏主意。我想我不应该说永远不要-但是让我们在绝大多数情况下都是一个好主意。

理论上的潜在缺点包括要维护的额外索引和使用的额外存储空间。对于我而言,不使用它从来都不是足够的理由。


11
我就是做这个的。大多数人都使用“ id”或“ tablename_id”(例如user_id)。该参数通常不是是否需要该列,而是使用哪种命名方式。
GrandmasterB

102
我个人认为表名称应包含其余名称。TableName.id而不是TableName.TableName_id,因为那id还要指的是什么?如果我在表中还有另一个id字段,那么如果它指向其他表,我将在表名前添加前缀
-AJJ

10
@ArukaJ,您提到您正在使用SQLite。这实际上是一个特例,因为它总是使这种专栏“在幕后”。因此,您甚至没有使用任何多余的空间,因为无论您是否想要,都可以得到一个空间。另外,SQLite的rowid始终是64位整数。如果我对它的理解是正确的,则如果定义自动递增的行,它将是内部行ID的别名。因此,您可能总是会这样做!参见sqlite.org/autoinc.html
GrandmasterB,

9
我可以想到的一个例外是,如果您有一个以其他方式生成的唯一标识符,在这种情况下,应将其作为主键,而自动递增的ID是多余的。
HamHamJ

4
@GrandmasterB:当前版本的SQLite允许创建WITHOUT ROWID表(带有显式PRIMARY KEY)作为优化。但是否则,INTEGER PRIMARY KEY列是rowid的别名。
dan04 '16

91

我不同意以前的所有答案。在所有表中添加自动递增字段是一个坏主意的原因有很多。

如果您的表中没有明显的键,那么自动递增字段似乎是个好主意。毕竟,您不想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。但是现在35彼此成为朋友了两次!那是在浪费空间,如果您考虑一下,自动增量列也是如此。您需要查看的所有内容ab是的朋友是选择带有这两个值的行。它们一起是唯一的行标识符。(您可能想要做写一些逻辑,以确保3,55,3被重复数据删除。)

在某些情况下,顺序ID可能很有用,例如在构建url缩短器时,但是大多数情况下(甚至使用url缩短器),您真正想使用的是随机生成的唯一ID。

TL; DR:如果您还没有识别每一行的独特方法,请使用UUID代替自动增量。


26
UUID的问题在于,它们占用大多数表的太多空间。为每个表使用正确的唯一标识符。
斯蒂芬

49
关于唯一性的整个段落都没有意义-可以强制执行唯一性,无论是否具有主键。此外,UUID从理论上讲更好,但是在调试/执行DBA任务或进行其他任何不“抵御攻击”的事情时,都很难使用。

11
UUID更好的另一种情况是:实现幂等PUT操作,以便您可以安全地重试请求,而不会引入重复的行。
yurez '16

21
在“ URL猜测”点上,具有唯一的ID(顺序的或其他)并不意味着将该ID暴露给应用程序的用户。
戴夫·谢罗曼

7
纯粹从数据库的角度来看,这个答案是完全错误的。使用UUID而不是自动递增整数会使索引增长得太快,并对性能和内存消耗产生不利影响。如果您是从Web服务或Web应用程序的角度进行交谈,则无论如何数据库和前端之间都应该有一层。别的都是不好的设计。使用数据作为主键甚至更糟。主键应仅在数据层上使用,而不能在其他地方使用。
醉酒代码猴子

60

自动增量键主要具有优势。

但是一些可能的缺点可能是:

  • 如果您有业务密钥,则也必须在该列上添加唯一索引以实施业务规则。
  • 在两个数据库之间传输数据时,尤其是当数据位于多个表(即主数据/明细数据)中时,由于序列未在数据库之间同步,因此它不是直截了当的,因此您必须首先使用业务密钥作为匹配项,以了解源数据库中的哪个ID与目标数据库中的哪个ID对应。但是,从/向隔离表传输数据时,这应该不是问题。
  • 许多企业具有临时的,图形的,点击式和拖放式报告工具。由于自动递增ID是没有意义的,因此这类用户将很难理解“应用程序”之外的数据。
  • 如果您不小心修改了业务密钥,很可能您将永远无法恢复该行,因为您再也没有任何东西可供人类识别。那一次在BitCoin平台上造成了故障
  • 当PK应该简单地由两个外部ID组成时,一些设计人员将ID添加到两个表之间的联接表中。显然,如果联接表在三个或更多表之间,则使用自动增量ID是有意义的,但是当将其应用于FK组合以实施业务规则时,则必须添加唯一键。

这是Wikipedia文章中有关代理密钥的缺点的部分


13
指责替代密钥上的mt.gox漏洞似乎很可疑。问题在于它们将所有字段都包括在其复合键中,甚至包括可变/恶意字段。
CodesInChaos

6
使用自动增量键的“社交”缺点是,有时“业务”会假定绝不存在任何差距和要求,以便知道在插入失败(事务回滚)时发生的丢失行发生了什么情况。
瑞克·赖克

4
另一个缺点是,如果系统变得如此庞大以至于必须分片数据库,则无法再使用自动增量来生成全局唯一密钥。当您达到这一点时,您可能有很多依赖于该假设的代码。还有其他产生唯一标识符的方法,如果对数据库进行分片,则该标识符将继续起作用。
kasperd '16

1
@Voo无法保证您选择的数据库支持该功能。尝试实现比数据库本身更高的层意味着您将失去SQL会给您的某些保证。最后,如果您使用的是分布式系统,则ID的任何集中分配都会增加延迟。
kasperd '16

1
@Voo当然,无论系统的规模如何,都不应对自动递增ID的性质做出太多假设。如果您只有一个数据库,则会按顺序分配它们,但不能保证按顺序提交它们。序列中可能会有缺口,因为并非所有事务都已提交。
卡巴斯德(Kasperd)

19

恰恰相反,不,您不必始终拥有数字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-只能通过“告诉我这辆车上有哪些附件”汽车或从附件“告诉我这辆车上有该附件”来访问它。


4
>所有Toyata汽车的VIN都以“ TO”开头,这是不正确的。如果是日本制造,则以“ JT”开头。美国制造的丰田车有完全不同的VIN en.wikibooks.org/wiki/...
蒙蒂哈尔德

17
Don't create a second, meaningless primary key; it's wasteful and may cause errors.但是,如果您为记录建立唯一性的方式是6列的组合,那么始终将所有6列连接起来就很容易出错。数据自然具有PK,但是最好使用一id列,并对这6列使用唯一约束。
布拉德,

14
我承认其中一些建议对我来说有点过头。是的,务实是可以的,但是我无法数出某人多长时间发誓他的长子一生中,某些属性超出了领域,在接下来的几天中将保持独特。好吧,通常情况良好,直到上线后的第二个星期才出现,直到第一个副本出现。;)使用“描述”作为PK的想法还很遥远。
AnoE

2
@蒙蒂,我不好,你是对的。易失的内存,距离我设计车队管理系统已有20年了。不,VIN不是主键:)我使用了AutoInc Asset_ID IIRC,这导致了我忘记的事情。表是多对多关系的链接器,在这些表中您将汽车链接到附件(例如,天窗)。许多汽车都有许多附件,因此您需要一个“ Car_Accessory”表,其中包含Car_ID和Accessory_ID,但绝对不需要Car_Accesory_ID作为AutoInc PK。
mcottle

7
真正令人惊讶的是,几乎没有真正的“自然键”。SSN的?不,他们可以改变。很少见,但有可能发生。用户名?不。最终,有人将有有效的商业理由进行更改。VIN通常是教科书中的一个例子,但是没有很多其他例子。给定街道名称更改,即使家庭住址也可以更改。
Erik Funkenbusch '16

12

许多表已经具有自然的唯一ID。不要在这些表上添加另一个唯一的id列(自动递增或其他方式)。请使用自然的唯一ID。如果添加另一个唯一ID,则您的数据实际上就具有冗余(重复或依赖性)。这违反了规范化的原则。一个唯一的ID依赖于另一个唯一ID。这意味着,他们必须完全同步,在保持始终每一个系统管理这些行。这只是数据完整性中的另一个脆弱性,您实际上并不需要长期管理和验证。

如今,大多数表实际上并不需要额外的唯一id列会带来很小的性能提升(有时甚至会降低性能)。作为IT的一般规则,请避免像瘟疫一样造成冗余建议您在任何地方抵抗它。这是恶心。并注意报价。一切都应该尽可能简单,但不要简单。即使天生的ID看起来不太整洁,也没有两个ID足以满足要求。


3
如果绝对保证永不更改,则不应该只使用“自然” ID作为主键吗?例如,您不应该将驾驶执照号码用作主键,因为如果某人获得了新的驾驶执照,则不仅需要更新该表,还需要更新任何带有引用该表的外键的表!
ekolis

1
驾驶执照编号不符合自然唯一标识的原因有很多。首先,其中一些来自其他数据,例如出生日期和姓名。不能保证它们在各州之间是唯一的。以您的例子为例,当一个人被重新颁发具有相同编号但可能延长的有效期的许可证时,会发生什么呢?他们拥有具有相同编号的不同许可证。自然ID仍必须满足主键的基本属性。驾照号码(至少在美国)在这方面存在一些缺陷。
布拉德·托马斯

1
好的,我想我当时误解了自然ID的定义。我认为这仅仅是业务规则定义的ID,实际上是否可以保证它是不可变的。
ekolis '16

10

在较大的系统上,ID是一致性增强器,请在几乎任何地方使用它。在这种情况下,不建议使用单个主键,因为它们在底线很昂贵(请阅读原因)。

每个规则都有一个例外,因此在用于导出/导入的登台表以及类似的单向表或临时表上,您可能不需要整数自动增量ID。您还希望在分布式系统上使用GUID而不是ID。

这里有许多答案建议应采用现有的唯一密钥。好吧,即使它有150个字符?我不这么认为。

现在我的要点是:

看起来自动递增整数ID的反对者正在谈论最多20个表的小型数据库。他们在那里可以为每个桌子提供单独的方法。

但是,一旦您拥有具有400多个表的ERP,那么在任何地方(上述情况除外)都具有整数自动递增ID 就是很有意义的。即使存在其他唯一字段并确保其唯一性,您也不必依赖它们。

  • 您将受益于通用的省时,省力,易于记忆的约定。
  • 在大多数情况下,您JOIN无需检查键是什么就可以进行表操作。
  • 您可以使用通用代码例程来处理整数自动增量列。
  • 您可以使用以前没有预见到的新表或用户插件来扩展系统,只需参考现有表的ID。它们从一开始就已经存在,无需额外添加它们。

在较大的系统上,有必要忽略这些主键的次要好处,并在大多数情况下始终使用整数自动增量ID。使用现有的唯一字段作为主键可能会为每条记录节省一些字节,但是在当今的数据库引擎中,额外的存储或索引时间不会造成任何问题实际上,您浪费了更多的金钱和资源,浪费了开发人员/维护人员的时间。当今的软件应针对程序员的时间和精力进行优化-具有一致ID的哪种方法可以更好地实现。


从个人经验来看,我完全同意您的回答的后半部分。与快速而紧凑的索引相比,您需要的全局唯一键要少得多,而且使用频率要少得多。如果确实需要一个,请创建一个具有自动生成的ID和UUID列的GlobalEntities表。然后,将ExGlobalEntityId外键添加到“客户”表中。或使用某些值的哈希值。
醉酒代码猴子

8

多余的设计不是一个好习惯。即-不需要一个总是自动递增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来执行此操作,或者可以将此唯一标识基于其他(不可变的)数据?


7

还是在某些情况下您不想添加这样的字段?

当然。

首先,有些数据库没有自动增量(例如,Oracle,当然不是周围最小的竞争者之一)。这应该是不是每个人都喜欢或需要它们的第一个迹象。

更重要的是,考虑一下ID实际什么-它是数据的主键。如果您的表具有不同的主键,那么您就不需要ID,也应该没有ID。例如,一个表(EMPLOYEE_ID, TEAM_ID)(每个雇员可以同时在多个团队中)具有一个明确定义的主键,该主键由这两个ID组成。添加自动增量ID列(这也是该表的主键)根本没有任何意义。现在,您在拖着2个主键,“主键”中的第一个单词应该给您提示,您实际上应该只有一个。


9
(不是Oracle用户可以原谅这个问题,但是)Oracle是否不像其他人使用“自动增量/身份”那样使用Sequence?是说Oracle没有Autoincrement数据类型,实际上只是一个语义参数?
布拉德,

好吧,那只是一个小问题;主要部分是运行ID不适用于每个表,因此习惯于仅在每个表上使用自动ID可能不是最明智的选择。
AnoE

没有两个主键,只有一个主键和所有其余的被称为候选键,如果它们可以作为主键太..
拉胡尔TYAGI

7

在为“长期”数据(我希望插入一次并无限期保持记录的记录)定义新表时,我通常使用“身份”列(自动递增整数),即使它们最终通过设置位字段而被“逻辑删除” )。

当您不想使用它们时,我会想到几种情况,其中大多数归结为一种情况,即数据库的一个实例上的一个表不能成为新ID值的权威来源:

  • 当增量ID对于潜在的攻击者来说是太多信息时。将标识列用于“面向公众”的数据服务,使您容易受到“德国坦克问题”的攻击;如果存在记录ID 10234,则可以推断出存在记录10233、10232等,至少回到记录10001,然后可以很容易地检查记录1001、101和1以确定标识列从何处开始。主要由随机数据组成的V4 GUID通过设计打破了这种递增行为,因此,仅由于存在一个GUID,就不一定存在通过递增或递减GUID字节而创建的GUID,这使得攻击者更难使用专有的服务。用于单记录检索作为转储工具。还有其他安全措施可以更好地限制访问,但这很有帮助。
  • 在M:M交叉引用表中。这有点像个给我,但我以前看过。如果数据库中的两个表之间存在多对多关系,则首选解决方案是一个交叉引用表,其中包含引用每个表PK的外键列。该表的PK实际上应该始终是两个外键的复合键,以获取内置的索引行为并确保引用的唯一性。
  • 当您计划在此表上进行大量插入和删除时。标识列的最大缺点可能是在插入来自另一个表或查询的行时必须经历的额外操作,以维持原始表的键值。您必须打开“身份插入”(无论如何在DBMS中完成),然后手动确保要插入的密钥是唯一的,然后在完成导入后,必须在表的元数据达到当前的最大值。如果此操作在此表上发生很多,请考虑使用其他PK方案。
  • 对于分布式表。标识列非常适合单实例数据库,故障转移对以及其他情况,在任何情况下,一个数据库实例都是整个数据模式的唯一权限。但是,只有这么大的空间,您仍然可以拥有一台足够快的计算机。复制或事务日志传送可以为您提供其他只读副本,但是该解决方案的规模也受到限制。迟早,您将需要两个或多个服务器实例来处理数据插入,然后彼此同步。当出现这种情况时,您将需要一个GUID字段而不是一个增量字段,因为大多数DBMS已预先配置为使用它们生成的一部分GUID作为实例特定的标识符,然后随机生成其余的标识符或逐步地。在任一情况下,
  • 当您必须在数据库中的多个表之间实施唯一性时。例如,在会计系统中,通常以一系列代表一个日历月/年。然后可以创建视图以将它们连接在一起以进行报告。从逻辑上讲,这都是一个非常大的表,但是将其切碎会使数据库的维护工作更加轻松。但是,它提出了一个问题,即如何管理多个表中的插入(允许您在下个月开始记录事务,同时仍然关闭上一个事务)而不会导致重复的键。同样,GUID而不是标识整数列是首选解决方案,因为DBMS旨在以一种真正独特的方式生成它们,

正如我所希望提到的,有一些变通办法可以在这些情况下使用标识列,但是在大多数情况下,从标识整数列升级到GUID更简单,并且可以更彻底地解决问题。


1
在某些情况下,ID, ID_M, ID_N由于将属性附加到M:N关系的实例,您仍然需要在M:N表中使用ID(使用column )。
miroxlav '16

不保证V4 GUIDS使用具有加密功能的PNRG,因此您真的不应该在第一个示例imo中使用它(尽管如果您的数据库引擎做出更强的承诺,您可能会很好,但是那是不可移植的)。否则,一个合理的职位。
Voo

1
@miroxlav-我要断言,如果一个表具有足够的关于关系的元数据,那么两个FK之外的单独PK是个好主意,那么它实际上就不再是交叉引用表了;它是自己的实体,碰巧引用了另外两个实体。
KeithS

@Voo-没错,V4 GUID不能保证是加密随机的,只是唯一的(就像所有GUID一样)。但是,美国喷气式战斗机的机尾号也不是从密码学上随机的种子数据/算法生成的。您真正要寻找的是一个人烟稀少的域名;V4 GUID具有112字节的随机数据,能够唯一地标识5e33记录。
KeithS

从数字上看,这个星球上的每个男人,女人和孩子(全部70亿)在我们的数据库中都可以拥有741 万亿个单独分类和ID的数据点,而我们仍然仅使用十亿美元的 GUID值。大数据作为一个全球性行业,还远远没有达到这种知识规模。即使给GUID生成提供了一种模式,也存在其他熵源,例如数据进入系统并分配给GUID的顺序。
KeithS

7

一个自动递增的(标识)主键是一个好主意,除了要注意,它在数据库上下文和该数据库的直接客户端之外毫无意义。例如,如果您将某些数据传输并存储到另一个数据库中,然后继续将不同的数据写入两个数据库表,则ID会有所不同-即,一个数据库中ID为42的数据不一定与数据匹配另一个ID为42。

鉴于此,如果仍然需要能够在数据库外部唯一地标识行(并且经常是这样),那么您必须为此使用其他密钥。精心选择的业务密钥可以做到,但是您通常最终会处于需要保证唯一性的大量列的位置。另一种技术是将ID列作为自动递增的聚集主键,而将另一个uniqueidentifier(guid)列作为非聚集的唯一键,以唯一地标识行在世界上任何地方的目的。在这种情况下,您仍然拥有一个自动递增的密钥的原因是因为对自动递增的密钥进行聚类和索引比对GUI进行同样的操作更为有效。

您可能不希望自动递增键的一种情况是多对多表,其中主键是其他两个表的Id列的组合(您仍然可以在此处使用自动递增键,但是我看不出重点)。

另一个问题是自动递增密钥的数据类型。使用Int32可为您提供较大但相对有限的值范围。就我个人而言,我经常将bigint列用作ID,以便实际上无需担心用完值。


6

正如其他人提出的增加主键的理由一样,我将为GUID作一个:

  • 保证是唯一的
  • 您可以减少到数据库的一次访问,以获取应用程序中的数据。(例如,对于类型表,您可以将GUID存储在应用程序中,并使用该GUID检索记录。如果使用身份,则需要按名称查询数据库,我已经看到很多应用程序可以通过该操作来获取PK然后稍后再次查询以获取完整的详细信息)。
  • 这对于隐藏数据很有用。www.domain.com/Article/2让我知道您只有两篇文章,而www.domain.com/article/b08a91c5-67fc-449f-8a50-ffdf2403444a告诉我什么都没有。
  • 您可以轻松合并来自不同数据库的记录。
  • MSFT使用GUIDS进行标识。

编辑:重复点


5
-1。GUID / UUID不能保证唯一,也不是100%唯一。GUID仍然是有限长度的,因此尽管不太可能,但是在某些时候您可能会有获得重复的风险。关于减少数据库访问次数的观点也是无效的-为什么不能像使用GUID键那样将主ID存储在应用程序中?
Niklas H

2
杰夫·阿特伍德(Jeff Atwood)说,这比我以前做的好得多。 blog.codinghorror.com/primary-keys-ids-versus-guids
三种价值逻辑

至于为什么不能在应用程序中存储主ID?因为数据库创建了它。如果在空数据库上运行种子,则可以假定ID为1。如果在其中包含数据的数据库上运行相同脚本,该怎么办?该ID将不为1
三值逻辑

您没有说有关在应用程序中创建ID的任何内容-您只写了“存储”。但是,如果有必要在数据库外部创建ID,那么可以,GUID可能是答案。
Niklas H

2
我会补充说,它们的规模更好。像Cassandra这样的大数据NoSQL数据库甚至都不支持自动增量键。
Karl Bielefeldt

2

作为良好设计的原则,每个表都应具有一种可靠的方式来唯一标识一行。尽管这就是主键的用途,但并不总是要求存在主键。向每个表添加主键不是一个坏习惯,因为它提供了唯一的行标识,但是可能没有必要。

为了保持两个或多个表的行之间的可靠关系,您需要通过外键来实现,因此至少在某些表中需要主键。向每个表添加主键可以更轻松地扩展数据库设计,以便在需要时向现有数据添加新表或关系。提前计划永远是一件好事。

作为一项基本原则(也许是硬性规定),主键的值在其整个生命周期中都不应改变。明智的做法是假设连续的任何业务数据在其生命周期内都可能发生变化,因此任何业务数据都不适合用作主键。这就是为什么抽象的东西(例如自动递增的整数)通常是个好主意的原因。但是,自动递增的整数确实有其局限性。

如果您的数据在数据库中只会存在生命,那么自动递增的整数就可以了。但是,正如在其他答案中提到的那样,如果您希望共享,同步数据或以其他方式使数据在数据库之外存在生命,则自动递增的整数会导致较差的主键。更好的选择是GUID(又名uuid“通用唯一ID”)。


2

该问题和许多答案都遗漏了一个重要的问题,即每个表的所有自然键都仅驻留在数据库的逻辑架构中,而每个表的所有代理键都仅驻留在数据库的物理架构中。其他答案仅讨论整数与GUID代理键的相对好处,而没有讨论为什么正确使用代理键的原因以及何时使用。

顺便说一句:让我们避免使用定义不正确和不精确的术语主键。它是关系前数据模型的产物,首先被(不明智地)选择加入关系模型,然后由各种RDBMS供应商选择加入物理域。它的使用仅用于混淆语义。

关系模型中注意到,为了使数据库逻辑模式采用第一范式,每个表都必须具有用户可见的字段集(称为自然键),字段唯一地标识表的每一行。在大多数情况下,这样的自然键很容易识别,但是有时必须构造一个自然键,无论是平局还是其他。但是,这样构造的密钥始终对用户仍然可见,因此始终驻留在数据库的逻辑模式中。

相比之下,表上的任何替代键都完全位于数据库的物理模式中(因此,出于安全性原因和维护数据库完整性的考虑,必须始终对数据库用户完全不可见)。引入代理密钥的唯一原因是为了解决数据库的物理维护和使用中的性能问题。无论是联接,复制,数据的多个硬件源还是其他。

由于引入代理密钥的唯一原因是性能,因此让我们假设我们希望它具有高性能。如果出现了性能问题,那么我们必然希望使代理密钥尽可能地窄(不会妨碍硬件,因此通常会省略短整数和字节)。连接性能取决于最小的索引高度,因此4字节整数是一个自然的解决方案。如果您的性能问题是插入率,那么4字节整数也是一个自然的解决方案(取决于RDBMS的内部)。如果表的性能问题是复制或多个数据源,而不是某些其他替代键技术,则GUID或两部分键(主机ID +整数)可能更适合。我个人并不是GUID的最爱,但它们很方便。

综上所述,并非所有表都需要代理键(任何类型);仅在认为对于执行所考虑的表格必要时才使用它们。无论您偏爱哪种常用的代理关键技术,在做出选择之前都要仔细考虑表的实际需求。更改表的替代关键技术选择将使您筋疲力尽。记录表的关键性能指标,以便您的继任者了解所做的选择。

特别案例

  1. 如果您的业务需求为了审计(或其他目的)而要求对交易进行顺序编号,则该字段不是替代键;这是自然键(有额外要求)。从文档中,自动递增的整数只会生成代理密钥,因此请找到另一种机制来生成它。显然,某种监视器是必要的,并且如果您要从多个站点采购交易,则一个站点将是特殊的,因为它是该监视器的指定宿主站点

  2. 如果您的表永远不会超过一百行,那么索引高度就无关紧要;每次访问都将通过表扫描进行。但是,长字符串的字符串比较仍将比4字节整数的比较昂贵,并且比GUID的比较昂贵。

  3. char(4)代码字段键控的代码值表的性能应与具有4字节整数的代码值相同。尽管我没有证明这一点,但我经常使用该假设,并且从未有过理reason的理由。


-1

这不仅不是一个好习惯,而且实际上在Bill Karwin的SQL Antipatterns一书中将其描述为反模式。

并非每个表都需要一个伪键-一个具有任意值的主键,而不是具有模型语义值的主键-并且没有理由总是调用它id


这似乎并没有提供任何实质性的过度点进行,而且在以往9个答案解释
蚊蚋

2
为什么这可能很重要?
t

3
@gnat因为这是一本有关最佳做法的书,直接解决了这个问题。这不是很明显吗?
Pedro Werneck

3
没有丝毫。Google搜索“ book sql最佳实践”显示了大约90万个链接,为什么这个链接特别值得
gna

1
@gnat我不会整天争论。您不喜欢答案,这就是降票的目的。
Pedro Werneck

-2

这非常通用-否则您将需要验证密钥实际上是唯一的。这将通过查看所有其他键来完成,这将非常耗时。随着您的记录数接近密钥溢出值,拥有增量密钥会变得很昂贵。

我通常使指针更明显,例如ref_{table}或类似的字段名称。

如果不需要从外部指向记录,则不需要ID。


密钥转换值?
AJJ

一个无符号整数的最大值为4294967295,然后将其加1会将其翻转为0。请记住,如果添加一条记录然后将其删除,则计数器仍会增加。确保使用unsigned int字段类型,否则限制为该数字的一半。
约翰尼五世


2
如果添加/删除很多行,则自动递增计数器最终将溢出。
约翰尼五世

1
人们如何处理过渡?如果有些ID较低的记录从未被删除,但是您开始接近一些ID在4294967295的上端的结尾怎么办?可以做“重新索引”吗?
AJJ

-2

我不会说应该总是这样做。我这里有一张没有唯一键的表,它不需要一个。这是审核日志。永远不会有更新,查询会将所有更改返回到所记录的内容,但这是可以合理做到的最好方法,这需要人工定义错误的更改。(如果代码允许的话,它本来就不允许这样做的!)


-3

主键的自动递增计数器不是一个好主意。这是因为您需要返回数据库以查找下一个键,然后在插入数据之前将其递增一。

话虽如此,我通常会使用数据库可以提供的任何主键,而不是将其作为应用程序的一部分。

通过让数据库为您本地提供它,它可以确保密钥对于它的需求是唯一的。

当然,并非所有数据库都支持它。在这种情况下,我通常使用一个表来存储密钥存储区,并使用在应用程序中管理的上限和下限范围。这是我发现的性能最高的解决方案,因为您可以得到10000个数字范围,并在应用程序实例上自动递增它们。另一个应用程序实例可以使用另一个数字桶。您确实需要足够大的主键原语,例如64位长。

我不将UUID用作主键,因为构建和存储UUID的成本比将long值加1的成本高得多。UUID仍然处理生日悖论,因为理论上可能会出现重复。


3
否。自动递增键表示该键的递增由数据库自动完成。有时候,(我看你,甲骨文!),你需要一个序列+触发组合,这样做,但你永远需要查找先前插入的价值为重点,加1,然后使用它。
SQB

使用某些持久性框架(如JPA),如果您想将已创建的键的值返回给调用者,则需要加载记录以查看键。
阿基米德·特拉哈诺
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.