在我正在处理的一个 Web 应用程序中,所有数据库操作都使用一些在实体框架 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
都不会到达数据库。一个典型的模式是初始加载和大量的INSERT
s、UPDATE
s 和DELETE
s。
根据当前的应用程序使用情况,在任何表中达到 100M 记录的可能性非常小。
问题: 从 DBA 的角度来看,有这个表设计限制我会遇到重大问题吗?
[编辑]
在阅读了答案(感谢您的反馈)和参考文章后,我觉得我必须添加更多细节:
当前应用程序细节- 我没有提及当前的 Web 应用程序,因为我想了解该模型是否也可以用于其他应用程序。但是,我的特殊情况是从 DWH 中提取大量元数据的应用程序。源数据非常混乱(以一种奇怪的方式非规范化,有一些不一致,在许多情况下没有自然标识符等),我的应用程序正在生成清晰分离的实体。此外,还会显示许多生成的标识符 (
IDENTITY
),以便用户可以将它们用作业务密钥。除了大量的代码重构之外,这还排除了 GUID 的使用。“它们不应该是唯一标识一行的唯一方法”(Aaron Bertrand♦)——这是一个非常好的建议。我所有的表还定义了一个 UNIQUE CONSTRAINT 以确保不允许业务重复。
前端应用驱动设计与数据库驱动设计- 设计选择是由这些因素引起的
实体框架限制- 允许多列 PK,但它们的值不能更新
自定义限制- 具有单个整数键大大简化了数据结构和非 SQL 代码。例如:所有值列表都有一个整数键和一个显示值。更重要的是,它保证任何标记为缓存的表都能够放入
Unique int key -> value
映射中。
复杂的选择查询——这几乎不会发生,因为所有小(< 20-30K 记录)表数据都在应用程序级别缓存。这使得编写应用程序代码时的生活有点困难(更难编写 LINQ),但数据库受到的影响要好得多:
列表视图- 不会
SELECT
在加载时生成查询(所有内容都被缓存)或如下所示的查询:SELECT allcolumns FROM BigTable WHERE filter1 IN (val1, val2) AND filter2 IN (val11, val12)
所有其他必需的值都是通过缓存查找 (O(1)) 获取的,因此不会生成复杂的查询。
编辑视图- 将生成
SELECT
如下语句:SELECT allcolumns FROM BigTable WHERE PKId = value1
(所有过滤器和值都是int
s)
除了额外的磁盘空间(以及内存使用和 I/O)之外,即使对不需要 IDENTITY 列的表(不需要 IDENTITY 列的表的示例)添加 IDENTITY 列也没有任何害处是一个简单的联结表,例如将用户映射到他/她的权限)。
我反对在 2010 年的博客文章中盲目地将它们添加到每个表中:
但是代理键确实有有效的用例——请注意不要假设它们保证唯一性(这有时是它们被添加的原因——它们不应该是唯一标识行的唯一方法)。如果您需要使用 ORM 框架,并且您的 ORM 框架需要单列整数键,即使您的实际键不是整数,也不是单列,或者两者都不是,请确保定义唯一约束/索引也可以用于您的真正钥匙。
根据我的经验,为每个表使用单独的 ID 的主要原因和压倒性的原因如下:
几乎在每一种情况下,我的客户在构思阶段都发誓,一些外部的“自然”场
XYZBLARGH_ID
将永远保持独特,永远不会改变给定的实体,永远不会被重复使用,最终出现了这样的案例:主键属性被破坏。它只是不这样。然后,从 DBA 的角度来看,导致 DB 变慢或臃肿的原因肯定不是每行 4 个字节(或其他),而是诸如错误或丢失索引、忘记表/索引重组、错误 RAM/表空间调整参数之类的事情,忽略使用绑定变量等等。这些可以使数据库减慢 10、100、10000 倍......而不是额外的 ID 列。
因此,即使每行增加 32 位存在技术上的、可衡量的缺点,也不是您是否可以优化 ID 的问题,而是 ID 在某些时候是否必不可少,这将是更多可能不是。而且我不会计算软件开发立场带来的所有“软”好处(例如您的 ORM 示例,或者当所有设计的 ID 具有相同的数据类型时,它使软件开发人员更容易等等) .
注意:请注意,关联表不需要单独的 ID,
n:m
因为对于此类表,关联实体的 ID 应形成主键。一个反例是一个奇怪的n:m
关联,它允许出于任何奇怪的原因在相同的两个实体之间建立多个关联——那些需要他们自己的 ID 列来创建一个 PK。不过,有些ORM 库无法处理多列 PK,因此如果他们必须使用这样的库,这将是对开发人员宽容的理由。如果您总是在每个表中添加一个无意义的额外列,并且仅将这些列作为外键引用,那么您几乎不可避免地会使数据库变得更加复杂和难以使用。实际上,您将从外键属性中删除用户感兴趣的数据,并强制用户/应用程序进行额外的连接以检索相同的信息。查询变得更加复杂,优化器的工作变得更加困难,性能可能会受到影响。
您的表将比其他情况下更稀疏地填充“真实”数据。因此,数据库将更难以理解和验证。您可能还会发现很难或不可能强制执行某些有用的约束(其中约束将涉及不再在同一个表中的多个属性)。
我建议您更仔细地选择您的密钥,并仅在您有充分理由时才将它们设为整数。将您的数据库设计基于良好的分析、数据完整性、实用性和可验证的结果,而不是依赖教条规则。
根据我对各种数据库的经验,整数主键总是比根本没有定义键的应用程序好。或者具有以不合逻辑的尴尬方式连接六个 varchar 列的键...... (叹气)
我见过从整数 PK 切换到 GUID 的应用程序。他们这样做的原因是因为在某些情况下需要合并来自多个源数据库的数据。开发人员将所有键都切换为 GUID,以便可以在不担心数据冲突的情况下进行合并,即使在不属于合并的表上也是如此(以防这些表成为未来合并的一部分)。
我想说一个整数 PK 不会咬你,除非你打算合并来自不同来源的数据,或者你的数据可能超出你的整数大小限制——这一切都很有趣和游戏,直到你用完插入空间.
不过,我会说,如果以这种方式更频繁地查询表,那么在 PK 以外的列上设置聚集索引是有意义的。但这是一个异常情况,特别是如果大量更新和选择基于 PK 值。
抛开:
如果您在适当的情况下使用批量删除/更新,并且有支持此类操作的索引,我认为您不会因为您使用的 PK 标准而遇到麻烦。
如果您稍后让 EF 生成带有连接等的查询,它们可能不会像使用基于自然键的存储库那样高效,但我对该领域的了解还不够,无法确定任何一种方式。
你有几个因素可以帮助指导你,
定义和规格。
如果某些东西被任务或物理定律定义为独特的,那么你就是在用代理键浪费你的时间。
独特性。
对于个人理智、连接和更高级别的数据库功能,您将需要 (a) 唯一列,(b) 唯一列系列
所有充分规范化的模式 (1NF) 都提供以下之一。如果他们不这样做,您应该始终创建一个。如果您有一份周日志愿服务的人员名单,其中包括姓氏和名字,您会想知道什么时候有两个 Joe Bobs。
实施和优化。
int 往往是一种小型数据形式,可以快速进行比较和相等。将其与排序规则取决于区域设置(位置和语言)的 Unicode 字符串进行比较。在 ASCII/UTF8 字符串中存储 4242 是 4 个字节。将它存储为一个整数,它适合 2 个字节。
因此,当谈到不利因素时,您有几个因素。
混乱和模棱两可。
空间。
整数仍然会为行增加空间。而且,如果您不使用它们,则没有任何目的。
聚类。
您只能以一种方式订购数据。如果你强加了一个不需要的代理键,你是用那种方式聚类还是用自然键的方式聚类?