始终将单个整数列作为主键的不利之处是什么?


18

在我正在处理的一个Web应用程序中,所有的数据库操作都使用在Entity Framework ORM上定义的一些通用存储库来抽象。

但是,为了对通用存储库进行简单设计,所有涉及的表都必须定义一个唯一的整数(Int32在C#中,int在SQL中)。到目前为止,这始终是表格的PK以及IDENTITY

外键被大量使用,它们引用这些整数列。它们是一致性和ORM生成导航属性所必需的。

应用程序层通常执行以下操作:

  • 从表(*)加载初始数据 -SELECT * FROM table
  • 更新 -UPDATE table SET Col1 = Val1 WHERE Id = IdVal
  • 删除 -DELETE FROM table WHERE Id = IdVal
  • 插入 -INSERT INTO table (cols) VALUES (...)

不太频繁的操作:

  • 批量插入 - BULK INSERT ... into table所有数据加载后跟(*)(以检索生成的标识符)
  • 批量删除 -这是正常的删除操作,但是从ORM的角度来看,这是“笨拙的”:DELETE FROM table where OtherThanIdCol = SomeValue
  • 批量更新 -这是正常的更新操作,但从ORM的角度来看是“庞大”的:UPDATE table SET SomeCol = SomeVal WHERE OtherThanIdCol = OtherValue

*所有小表都缓存在应用程序级别,几乎所有小表都SELECTs不会到达数据库。典型的模式是初始载荷和大量INSERTs,UPDATEs和DELETEs。

根据当前应用程序的使用情况,在任何表中达到100M条记录的可能性很小。

问: 从DBA的角度来看,由于存在此表设计限制,我是否会遇到重大问题?

[编辑]

在阅读了答案(感谢您的宝贵反馈)和参考文章之后,我觉得我必须添加更多详细信息:

  1. 当前应用程序的详细信息 -我没有提及当前的Web应用程序,因为我想了解该模型是否也可以用于其他应用程序。但是,我的特殊情况是从DWH中提取大量元数据的应用程序。源数据非常混乱(以一种奇怪的方式进行了归一化,存在一些不一致,在许多情况下没有自然的标识符等),并且我的应用程序正在生成清晰的分离实体。另外,还会显示许多生成的标识符(IDENTITY),以便用户将其用作业务密钥。除了进行大量代码重构外,这还不包括GUID的使用

  2. “它们不应该是唯一标识行的唯一方法”(Aaron Bertrand♦)-这是一个非常好的建议。我所有的表还定义了UNIQUE CONSTRAINT,以确保不允许业务重复。

  3. 前端应用程序驱动的设计与数据库驱动的设计 -设计选择是由这些因素引起的

    1. 实体框架限制 -允许多列PK,但其值无法更新

    2. 自定义限制 -具有单个整数键可以大大简化数据结构和非SQL代码。例如:所有值列表都有一个整数键和一个显示的值。更重要的是,它保证任何标记为缓存的表都可以放入Unique int key -> value映射中。

  4. 复杂的选择查询 -这几乎永远不会发生,因为所有小的(<20-30K记录)表数据都在应用程序级别缓存。这使编写应用程序代码时的工作变得更加艰难(更难于编写LINQ),但是数据库命中了更好的情况:

    1. 列表视图 -不会SELECT在加载时生成查询(缓存所有内容)或类似以下查询:

      SELECT allcolumns FROM BigTable WHERE filter1 IN (val1, val2) AND filter2 IN (val11, val12)

      所有其他必需的值都是通过缓存查找(O(1))获取的,因此不会生成复杂的查询。

    2. 编辑视图 -将生成如下SELECT语句:

      SELECT allcolumns FROM BigTable WHERE PKId = value1

(所有过滤器和值均为ints)


您可能会发现这些帖子具有相关性,因为讨论了有关使用具有系统生成的替代值的列的一些逻辑,物理和实践方面。
MDCCL '17

Answers:


19

除了额外的磁盘空间(以及依次的内存使用和I / O)之外,即使向不需要一个表的表添加IDENTITY列也没有任何危害(例如,一个不需要IDENTITY列的表的示例)是一个简单的联结表,例如将用户映射到其权限)。

我反对将它们盲目地添加到2010年博客文章的每个表中:

但是代理键确实具有有效的用例-请小心不要假定它们保证唯一性(这有时就是为什么要添加它们-它们不应该是唯一标识行的唯一方法)。如果您需要使用ORM框架,并且您的ORM框架需要单列整数键,即使您的实键不是整数,不是单列或两者都不是,请确保定义唯一的约束/索引也是您的真实钥匙。


感谢您的快速答复。是的,该应用程序使用ORM(EF)。它不需要单个整数列键,但是我引入了此限制,以使某些通用操作更加容易(在设计方面)。同样,所有应用程序缓存都将所有内容存储在地图(字典)中,以便通过键快速检索,并且键必须唯一。由于我选择的是int而不是Guid,因此我不得不对插入的任何表使用IDENTITY。对于固定值表,不需要IDENTITY。
Alexei

我认为存在一些需要避免对自然键进行唯一性检查的情况。作为使用GIS数据的人,立即想到的是自然键要么是几何本身,要么是几何加上一些外键。通过精确的几何图形查找事物总是不切实际的,因此对其进行唯一性约束不太可能有太大帮助,并且可能会带来性能缺陷。如果自然键的一部分是长文本列,则可能同样如此。但是我同意:只要可行,是的,应该对自然键应用唯一的约束。
jpmc26

13

根据我的经验,为每个表使用单独的ID的主要原因是:

几乎在每种情况下,我的客户都在构想阶段宣誓就职,即某些外部“自然”领域XYZBLARGH_ID将永远保持唯一性,并且对于给定的实体永远不会改变,并且永远不会重复使用,最终出现了这样的情况:主键属性已损坏。只是无法解决问题。

然后,从DBA的角度来看,使数据库变慢或膨胀的原因肯定不是每行4个字节(或其他任何值),而是诸如错误或丢失索引,忘记表/索引重组,错误的RAM /表空间调整参数之类的事情。 ,忽略使用绑定变量等。这些可以使DB减慢10、100、10000倍...而不是额外的ID列。

因此,即使每行增加32位存在技术上的,可测量的缺点,也不是您是否可以优化ID的问题,而是在某个时候ID是否必不可少的问题,而这将是一个更大的问题。可能比没有。而且,我不会指望软件开发立场带来的所有“软”收益(例如您的ORM示例,或者当所有ID设计为具有相同数据类型时,这样会使软件开发人员更容易的事实) 。

注意:请注意,n:m关联表不需要单独的ID,因为对于此类表,关联实体的ID应该构成主键。一个反例将是一个奇怪的n:m关联,无论出于什么奇怪的原因,它都允许在同一两个实体之间进行多个关联-然后,这些实体将需要使用自己的ID列来创建PK。有无法处理多列的PK的,所以这将是宽松与开发商的理由,如果他们有这样的图书馆工作ORM库。


2
“奇怪的n:m关联允许在相同两个实体之间进行多个关联”在现实生活中非常常见。例如,某人拥有一辆汽车,那么当所有权开始和结束时,要求更改为“重新记录”(一个人可以出售汽车,以后再买回它,并使您的软件崩溃。...)
Ian Ringrose

是的,@ IanRingrose。
AnoE

6

如果您总是在每个表中添加无意义的额外列,并且仅将这些列作为外键引用,那么几乎不可避免地会使数据库变得更加复杂且难以使用。实际上,您将从外键属性中删除用户感兴趣的数据,并强制用户/应用程序执行额外的联接以检索相同的信息。查询变得更加复杂,优化器的工作变得更加困难,性能可能会受到影响。

与“原本”数据相比,表中的“实际”数据将更加稀疏。因此,数据库将更难以理解和验证。您可能还会发现很难或不可能实施某些有用的约束(约束将涉及不再在同一表中的多个属性)。

我建议您更仔细地选择密钥,并仅在有充分理由的情况下才将它们设置为整数。数据库设计基于良好的分析,数据完整性,实用性和可验证的结果,而不是依赖教条规则。


1
然而,许多系统的确在每个表上都有合成整数主键(例如,几乎所有曾经编写过的Ruby on Rails应用程序),而不会遇到此类问题。他们也从来没有遇到必须将对主键所做的更改(从未发生过)推送到所有外键表的问题。
David Aldridge

2
这个问题要求可能的弊端,因此我回答了。我并不否认,如果明智地使用代理密钥,那么这是有意义的。但是我看到带有3、4、5(或更多)无意义外键的表,因此需要3、4、5或更多的连接才能从中获得有用的结果。更为实用的设计可能根本不需要任何连接。
nvogel '02

1
我不相信这种查询的执行是人们采用这种设计的主要问题,而是人们经常反对的是编写查询。
David Aldridge

5

根据我在各种数据库中的经验,整数主键总是比根本没有定义键的应用程序更好或者那些键以不合逻辑的笨拙方式连接半打varchar列... (叹气)

我已经看到了从整数PK转换为GUID的应用程序。他们这样做的原因是,在某些情况下需要合并来自多个源数据库的数据。开发人员将所有切换到GUID,以便即使在不属于合并的表上也可以进行合并而不必担心数据冲突(以防这些表成为将来合并的一部分)。

我想说的是整数PK不会咬你,除非您打算合并来自不同来源的数据,或者您的数据可能会超出整数大小的限制-直到您用完插入空间时,这都是很有趣的游戏。 。

我会说,虽然,它可以是有意义的设置您的聚集索引上比你的PK等一列,若该表将被更频繁地这样询问。但这是一个例外情况,尤其是如果大量更新和选择基于PK值时。


2
听起来似乎很可怕,要将所有键更改为导航键。我目前正在使用一个对所有代理键都使用guid的数据库。
安迪

2
不能。使用GUID不好玩。我不喜欢它们,但在某些用例中我尊重它们的价值。
CaM

2

放在一边:

  • 宗教战争(Google代理与自然键)
  • 关于在表上定义哪些聚集索引的单独问题
  • 缓存所有数据的可行性

只要您在适当的地方使用批量删除/更新,并且具有支持此类操作的索引,我认为您不会因为使用的PK标准而遇到麻烦。
如果您以后让EF使用连接等生成查询,它们的效率可能不如基于自然键的存储库那么高效,但是我对这方面的知识还不够,无法确定哪种方法。


4
我想不出一种情况,自然键上的联接比整数上的联接更有效-没有多少自然键可以小于4个字节,如果这样,那么唯一性就不够行以使材料有所不同。
亚伦·伯特兰

对于合格的,可优化的SQL,我同意,但是我指的是SQL生成器的可能限制。我在这方面的唯一经验是被要求去创建广泛的观点,以便可以用EF来填充EF。尽管.net开发人员可能对EF不够了解,或者还有其他原因。
TH

@AaronBertrand我要说的是,提高效率的唯一方法是根本不需要连接。我认为唯一使用自然键的地方是标准代码列表,例如ISO4127货币代码(人类可识别),并且我可能会使用GBP,EUR等作为货币代码上主键或备用键的外键表。
David Aldridge

@David当然,我在谈论需要加入的情况。在很多情况下,我都不希望在所有相关表中都增加自然键,因为自然键可以更改,这是一件很痛苦的事情。
亚伦·伯特兰

嗯,我看到我的答案可能是被误解为在代理上推广自然外键。明确地说,我实际上只提到它们是因为a)我将Alexei的问题理解为“我们不使用自然键是否存在问题?”,b)Alexei的总结问题始于“从DBA的角度来看”,我觉得我应该承认存在不止一种观点,并且c)因为我认为要使用的ORM功能在很大程度上决定了选择(如果确实可以有所作为)。我本人坚决处于代理外键训练营。
TH

2

您有几个因素可以帮助指导您,

  1. 定义和规格。

    如果某项任务或物理定律将某些事物定义为唯一的,那么您将浪费时间使用代理密钥。

  2. 独特性

    为了个人理智,联接和更高级别的数据库功能,您将需要:(a)唯一列,(b)唯一列系列

    所有充分规范化的架构(1NF)提供以下之一。如果没有,则应始终创建一个。如果您有一个准备在周日参加志愿者活动的人员花名册,其中包括姓氏和名字,您将想知道何时有两个Joe Bobs。

  3. 实施和优化。

    一个int往往是一个小的数据形式,可以快速进行比较和相等。将其与排序规则取决于语言环境(位置和语言)的Unicode字符串进行比较。在ASCII / UTF8字符串中存储4242是4个字节。将其存储为整数,适合2个字节。

因此,在不利方面,您需要考虑一些因素。

  1. 混乱和模棱两可。

    1. @Aaron Bertrand博客条目很好地总结了这一点。在规范和任务中拥有一个OrderID,然后通过数据库实现强加一个“ OrderID ” ,这并不是自我记录。有时您必须澄清这一点或创建一个约定,但这可能会增加混乱。
  2. 空间。

    整数仍然为行添加空间。而且,如果您不使用它们,则没有任何目的。

  3. 聚类。

    您只能以一种方式订购数据。如果您强加了不需要的替代密钥,您是采用这种方式还是采用自然密钥的方式进行集群?


优点和缺点。
阿列克谢

@Alexei谢谢,如果符合您的要求,请考虑将其标记为选中。或者,要求澄清。
埃文·卡罗尔
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.