使用 GUID 作为主键的最佳做法是啥,特别是在性能方面? [关闭]

Posted

技术标签:

【中文标题】使用 GUID 作为主键的最佳做法是啥,特别是在性能方面? [关闭]【英文标题】:What are the best practices for using a GUID as a primary key, specifically regarding performance? [closed]使用 GUID 作为主键的最佳做法是什么,特别是在性能方面? [关闭] 【发布时间】:2012-08-09 21:40:32 【问题描述】:

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

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

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

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

【问题讨论】:

这个问题不是假设的。如果您的 PK 是集群的,那么几乎每个插入都有可能导致页面拆分。在现代版本的 SQL Server 中,这已使用 NEWSEQUENTIALID()“修复”,但这失去了能够预先计算它的好处。我强烈建议您阅读其他地方的 GUID,因为这是一个过于宽泛的问题,并且可能会引发一场持续数小时的宗教斗争...... 我还要补充一点,server 这个词在 I want to assign the Guid on the server 中不明确方面(不想让 SQL 创建 GUID). 这个问题和这个“sql-server-guid-sort-algorithm-why”***.com/questions/7810602/…有相似之处 【参考方案1】:

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

你真的需要把两个问题分开:

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

    clustering key(定义表上“聚集索引”的一个或多个列) - 这是一个与存储相关的物理事物,这里是一个小型、稳定、不断增长的数据类型是您的最佳选择 - INTBIGINT 作为您的默认选项。

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

正如Kimberly Tripp - 索引女王 - 和其他人已经多次声明的那样 - GUID 作为集群键不是最佳的,因为由于它的随机性,它会导致大量页面和索引碎片并且通常表现不佳。

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

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

快速计算 - 使用 INTGUID 作为主键和聚类键:

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

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

更多值得深思的东西 - Kimberly Tripp 的优秀作品 - 阅读,再阅读,消化!这是 SQL Server 索引的福音,真的。

GUIDs as PRIMARY KEY and/or clustered key The clustered index debate continues Ever-increasing clustering key - the Clustered Index Debate..........again! Disk space is cheap - that's not the point!

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) 作为我的集群主键 - 比其他任何东西都更有效!

【讨论】:

我读这篇文章的方式是,既有非聚集的 uniqueidentifier 列又有 int 标识列,FK 也应该是 uniqueidentifier?如果你这样做,你什么时候会直接使用标识列,还是不直接使用? 小问题,现在应该在连接上使用 GUID 还是 int id?我的直觉告诉我应该使用 GUID,但我没有看到使用 int id 的技术问题... @marc_s 但在复制场景中,如果 int 列是标识,我们不应该使用 GUID,因为 int 列可以跨设备重复吗? 这是一个旧线程,但我可以补充一点:不要只使用无用的任意 INT 作为集群键。使用一些有用的东西,比如实际搜索的增量日期,它与您存储的数据有一定的关系。您只会得到一个集群键,如果您选择正确的一个,您将获得良好的性能 @Kipei:主要问题是 I-F 你有这样一个自然值 - 那么是的,你可以将它用作主键。 BUT:例如 DATETIME 之类的值对于集群键NOT有用,因为它们只有 3.33 毫秒的准确度,因此会重复可以存在。所以在这种情况下,你 *still 需要一个 INT IDENTITY 代替 - 因此,我通常默认使用它,因为我有 20 多年的经验,一个真正可用的 自然键 几乎从来没有真正存在过......【参考方案2】:

好吧,如果您的数据永远不会达到数百万行,那就太好了。如果你问我,我从不使用 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 过滤。

【讨论】:

我基本同意,但“视情况而定”。问题是,即使是用于聚集索引的 IDENTITY 和日期列有时也会出现更严重的碎片问题,因为执行 INSERT 的可怕习惯是,然后对刚刚插入的行进行“ExpAnsive”更新。大规模的碎片是保证和瞬时的。即使人们避免使用随机 GUID,也必须正确设计。奇怪的是,关于插入/更新碎片问题的随机 GUID 聚集索引一次长达数月,而不是瞬时的。【参考方案3】:

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

【讨论】:

虽然您的答案在技术上是正确的,但它不是问题的答案。【参考方案4】:

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

【讨论】:

你为什么要这样做?【参考方案5】:

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

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

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

此解决方案允许不在 URL 上显示 int 键,但保持模型整洁和快速。

【讨论】:

您需要做些什么来将整数 pK 配置为集群,如数据注释,还是只是自动配置? Guid one 使用的属性名称是什么? 如果在Controller中收到Guid,不知道关联的int怎么访问呢?您是否在 Guid 列中进行顺序搜索?【参考方案6】:

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

这是我使用 GUID 的设置:

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

    PK Guid 是非聚集索引。除非它是 NewSequentialID,否则永远不要对 GUID 进行群集索引。但即便如此,服务器重启也会导致排序中断。

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

    加入 ClusterID (int) 效率更高,但我使用 20-30 百万个记录表,因此加入 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)

【讨论】:

您能解释一下 PK_Email 约束吗?为什么你有 ... NonClustered(EmailID ASC) 而不是 ...Nonclustered(ClusterID ASC) ? 你打赌。索引发生的两个主要事情: 1. Clustered on ClusterID - 在磁盘上对表进行排序(0% 碎片)。 2. NonClustered on EmailID - 索引 EmailID 字段以加快 GUID ID 查找。 GUID 字段查找的行为类似于字符串,因此如果没有索引,EmailID 查找会很慢。 @RobertJ.Good 我已经看到之前讨论过的这种方法,即添加一个代理 int 键来集群。但是我找不到任何地方显示使用代理键聚集索引而不是使用堆的性能增益。您有任何指向基准数据的链接吗? 嗨@DaleBurrell,聚集索引是为了防止表碎片。当表在磁盘上自然按顺序增长时,性能会得到提升,碎片较少。 @RobertJ.Good 当您提到“在这个分布式数据库世界中,它绝对是合并分布式数据的最佳方式”。你的意思是你最终将记录合并到一个主数据库?想知道 clusterID 会发生什么,合并“源”后如何处理重复项?【参考方案7】:

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

【讨论】:

您能否提供任何逻辑或证据来支持这一说法?我正在努力了解顺序 ID 可能会如何危及安全性。 当然,如果您知道 ID 号是整数,您可以按顺序猜测数据库中的记录。所以如果你查询单个项目,你可以说下一个项目是pk + 1。如果你有随机的GUIDS,它就不会遵循一个模式。除了您之前查询的记录(并且知道 PK)之外,几乎不可能查询其他记录。 如果黑客可以查询你的数据库,你已经被入侵了,我看不出顺序 ID 是如何让情况变得更糟的。 如果用户可以将 1012 切换为另一个号码并查看他们不应该看到的数据,那么存在一个非常严重的安全问题,该问题不是由主键选择引起的,而是由它。我同意你的观点,谢谢你把它拼出来。 您可以使用 GUID 来定位网页上的记录,而不是表的 PK。在网站中使用查询参数不应定义您如何构建数据库架构。 PK与UI或后端系统中的输入和参数无关。【参考方案8】:

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

    https://www.sqlskills.com/blogs/kimberly/disk-space-is-cheap/ https://www.sqlskills.com/blogs/kimberly/guids-as-primary-keys-andor-the-clustering-key/

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

完全同意!!但这意味着,如果我有一个 GUID 作为 PK 或一个具有 GUID 和其他字段的复合 PK 将是相同的吗? PK(索引)将由两列组成,但除非您有特定的业务原因这样做,否则似乎没有必要。 顺便说一句,这个问题是目前最两极分化和争论最多的问题之一,因此很难得到答案,你会感到 100% 满意。这两种方法都需要权衡取舍,祝你好运:)

以上是关于使用 GUID 作为主键的最佳做法是啥,特别是在性能方面? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

使用GUID作为数据表主键的好处(转)

在现有 Django 应用程序中更改主键的最佳方法是啥?

使用 C 类型 uuid_t 作为 std::map 中的键的最佳方法是啥?

Oracle的SYS_GUID() 函数

聊聊用 UUID/GUID 作为主键那些坑

int 和guid做主键的时候性能的区别