391

我有一个应用程序在几乎所有表中都使用 GUID 作为主键,并且我读到使用 GUID 作为主键时存在性能问题。老实说,我没有看到任何问题,但我即将开始一个新的应用程序,我仍然想使用 GUID 作为主键,但我正在考虑使用复合主键(GUID 可能还有另一个字段.)

我使用 GUID 是因为当您拥有不同的环境(例如“生产”、“测试”和“开发”数据库)以及数据库之间的迁移数据时,它们很好且易于管理。

我将使用 Entity Framework 4.3,并且我想在应用程序代码中分配 Guid,然后再将其插入数据库。(即我不想让 SQL 生成 Guid)。

创建基于 GUID 的主键的最佳实践是什么,以避免与此方法相关的假定性能损失?

4

9 回答 9

565

GUID 似乎是您的主键的自然选择 - 如果您真的必须,您可能会争辩将它用于表的主键。我强烈建议不要使用 GUID 列作为clustering key,SQL Server 默认会这样做,除非你明确告诉它不要这样做。

您确实需要将两个问题分开:

  1. 是一个逻辑结构 - 唯一且可靠地标识表中每一行的候选键之一。这可以是任何东西,真的——一个INT、一个GUID、一个字符串——选择对你的场景最有意义的东西。

  2. 聚集键(在表上定义“聚集索引”的一列或多列)——这是一个与物理存储相关的东西,在这里,一个小的、稳定的、不断增长的数据类型是你最好的选择——INT或者BIGINT作为你的默认选项。

默认情况下,SQL Server 表上的主键也用作集群键——但不必这样!在将以前的基于 GUID 的主键/集群键分解为两个单独的键——GUID 上的主(逻辑)键和单独INT IDENTITY(1,1)列上的集群(排序)键时,我个人看到了巨大的性能提升。

正如金伯利·特里普( Kimberly Tripp)——索引女王——和其他人多次声明的那样——GUID因为集群键不是最优的,因为由于它的随机性,它会导致大量的页面和索引碎片,并且通常会导致性能不佳。

是的,我知道 -newsequentialid()在 SQL Server 2005 及更高版本中存在 - 但即使这样也不是真正和完全顺序的,因此也会遇到与 - 一样的问题GUID- 只是稍微不那么突出。

然后还有另一个问题需要考虑:表上的集群键也将添加到表上每个非聚集索引的每个条目中 - 因此您真的希望确保它尽可能小。通常,INT对于绝大多数表来说,具有 2+ 十亿行的 a 应该足够了 - 与GUID作为集群键的 a 相比,您可以在磁盘和服务器内存中节省数百兆字节的存储空间。

快速计算 - 使用INTvs.GUID作为主键和聚类键:

  • 具有 1'000'000 行的基表(3.8 MB 与 15.26 MB)
  • 6 个非聚集索引(22.89 MB 与 91.55 MB)

总计:25 MB 与 106 MB - 这只是在一张桌子上!

更多值得深思的东西——金伯利·特里普(Kimberly Tripp)的优秀作品——读一读,再读一遍,消化一下!这是 SQL Server 索引的福音,真的。

PS:当然,如果您只处理几百或几千行 - 这些参数中的大多数不会对您产生太大影响。但是:如果您进入数万或数十万行,或者您开始​​数百万 -那么这些点变得非常重要并且非常重要,需要理解。

更新:如果您想将PKGUID列作为主键(但不是集群键),并将另一列MYINT( INT IDENTITY) 作为集群键 - 使用以下命令:

CREATE TABLE dbo.MyTable
(PKGUID UNIQUEIDENTIFIER NOT NULL,
 MyINT INT IDENTITY(1,1) NOT NULL,
 .... add more columns as needed ...... )

ALTER TABLE dbo.MyTable
ADD CONSTRAINT PK_MyTable
PRIMARY KEY NONCLUSTERED (PKGUID)

CREATE UNIQUE CLUSTERED INDEX CIX_MyTable ON dbo.MyTable(MyINT)

基本上:你只需要明确地告诉PRIMARY KEY约束它是NONCLUSTERED(否则它被创建为你的聚集索引,默认情况下) - 然后你创建第二个索引,定义为CLUSTERED

这将起作用 - 如果您有一个需要“重新设计”以提高性能的现有系统,这是一个有效的选择。对于一个新系统,如果您从头开始,并且您不在复制场景中,那么我总是选择ID INT IDENTITY(1,1)作为我的集群主键 - 比其他任何东西都更有效!

于 2012-08-13T16:34:59.417 回答
76

自 2005 年以来,我一直使用 GUID 作为 PK。在这个分布式数据库世界中,它绝对是合并分布式数据的最佳方式。您可以触发并忘记合并表,而无需担心跨连接表的整数匹配。可以毫无顾虑地复制 GUID 连接。

这是我使用 GUID 的设置:

  1. PK = GUID。GUID 的索引类似于字符串,因此高行表(超过 5000 万条记录)可能需要表分区或其他性能技术。SQL Server 变得非常高效,因此性能问题越来越不适用。

  2. PK Guid 是非聚集索引。除非它是 NewSequentialID,否则永远不要对 GUID 进行群集索引。但即便如此,服务器重新启动也会导致订单出现重大中断。

  3. 将 ClusterID Int 添加到每个表。这是您的 CLUSTERED Index... 为您的表排序。

  4. 加入 ClusterIDs (int) 效率更高,但我使用 20-3000 万个记录表,因此加入 GUID 不会明显影响性能。如果您想要最大性能,请使用 ClusterID 概念作为您的主键并加入 ClusterID。

这是我的电子邮件表...

CREATE TABLE [Core].[Email] (
    [EmailID]      UNIQUEIDENTIFIER CONSTRAINT [DF_Email_EmailID] DEFAULT (newsequentialid()) NOT NULL,        
    [EmailAddress] NVARCHAR (50)    CONSTRAINT [DF_Email_EmailAddress] DEFAULT ('') NOT NULL,        
    [CreatedDate]  DATETIME         CONSTRAINT [DF_Email_CreatedDate] DEFAULT (getutcdate()) NOT NULL,      
    [ClusterID] INT NOT NULL IDENTITY,
    CONSTRAINT [PK_Email] PRIMARY KEY NonCLUSTERED ([EmailID] ASC)
);
GO

CREATE UNIQUE CLUSTERED INDEX [IX_Email_ClusterID] ON [Core].[Email] ([ClusterID])
GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_Email_EmailAddress] ON [Core].[Email] ([EmailAddress] Asc)
于 2015-03-31T21:27:41.800 回答
13

我目前正在使用 EF Core 开发 Web 应用程序,这是我使用的模式:

我所有的课程(表)都有intPK 和 FK。然后,我有一个额外的类型列Guid(由 C# 构造函数生成),上面有一个非聚集索引。

EF 中所有表的连接都是通过int键来管理的,而来自外部(控制器)的所有访问都是通过Guids 完成的。

该解决方案允许不在intURL 上显示键,但可以保持模型的整洁和快速。

于 2017-05-12T08:14:15.990 回答
4

这个链接比我能说得更好,并帮助我做出决定。我通常选择一个 int 作为主键,除非我有特定的不需要,而且我也让 SQL Server 自动生成/维护这个字段,除非我有特定的理由不这样做。实际上,性能问题需要根据您的特定应用程序来确定。这里有很多因素在起作用,包括但不限于预期的数据库大小、正确的索引、高效的查询等等。尽管人们可能不同意,但我认为在许多情况下,您不会注意到任何一个选项的区别,您应该选择更适合您的应用程序以及可以让您更轻松、更快、更有效地开发(如果您从未完成应用程序)其余的有什么区别:)。

https://web.archive.org/web/20120812080710/http://databases.aspfaq.com/database/what-should-i-choose-for-my-primary-key.html

PS 我不确定您为什么要使用复合 PK 或您认为这会给您带来什么好处。

于 2012-08-13T16:22:51.007 回答
1

如果您用GUID作主键并创建聚集索引,那么我建议使用它的默认NEWSEQUENTIALID()值。

于 2012-08-13T16:47:42.267 回答
1

拥有顺序 ID 使黑客或数据挖掘者更容易破坏您的站点和数据。在为网站选择 PK 时请记住这一点。

于 2019-04-15T17:10:59.807 回答
0

大多数时候它不应该用作表的主键,因为它确实会影响数据库的性能。关于 GUID 对性能的影响和作为主键的有用链接。

  1. https://www.sqlskills.com/blogs/kimberly/disk-space-is-cheap/
  2. https://www.sqlskills.com/blogs/kimberly/guids-as-primary-keys-andor-the-clustering-key/
于 2019-02-18T12:08:13.417 回答
0

不在用户界面中公开 Id 的另一个原因是竞争对手可以看到您的 Id 在一天或其他时间段内增加,从而推断出您正在做的业务量。

于 2021-03-03T00:00:25.583 回答
0

好吧,如果您的数据永远不会达到数百万行,那您就很好。如果你问我,我从不使用 GUID 作为任何类型的数据库标识列,包括 PK,即使你强迫我用霰弹枪设计。

使用 GUID 作为主键是一个明确的缩放停止器,也是一个关键的停止器。我建议您检查数据库身份和序列选项。序列是独立于表的,可以为您的需求提供解决方案(MS SQL 有序列)。

如果您的表开始达到数千万行,例如 5000 万行,您将无法在可接受的时间读取/写入信息,甚至标准的数据库索引维护也将变得不可能。

然后你需要使用分区,并且可以扩展到 50 亿甚至 1-20 亿行。在途中添加分区并不是最简单的事情,所有读/写语句都必须包含分区列(完整的应用程序更改!)。

当然,这些数字(5000 万和 50000 万)是供轻度选择使用的。如果您需要以复杂的方式选择信息和/或有大量的插入/更新/删除,那么对于要求非常高的系统,这些甚至可能是 1-2 百万和 5000 万。如果您还添加现代系统常见的完全恢复模式、高可用性和无维护窗口等因素,事情就会变得非常难看。

请注意,此时 20 亿是 int 限制,看起来很糟糕,但 int 小 4 倍,并且是一种顺序类型的数据,小尺寸和顺序类型是数据库可扩展性的第一大因素。你可以使用 big int ,它只是小两倍,但仍然是连续的,当涉及数百万或数十亿行时,顺序是真正致命的重要——甚至比大小更重要。

如果 GUID 也是集群的,那么情况就更糟了。只是插入一个新行实际上会随机存储在物理位置的任何地方。

即使只是一个列,不是PK或PK部分,只是索引它很麻烦。从碎片化的角度。

拥有一个 guid 列就像任何 varchar 列一样完全可以,只要您不将其用作 PK 部分并且通常用作连接表的键列。您的数据库必须有自己的 PK 元素,使用它们过滤和连接数据 - 之后也可以通过 GUID 过滤。

于 2021-04-02T11:00:33.727 回答