使用GUID作为主键的最佳实践是什么,特别是在性能方面?

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

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

我将使用实体框架4.3,我想在应用程序代码中分配Guid,然后将其插入数据库。(例如,我不想让SQL生成Guid)。

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

202250 次浏览

这个链接比我说得更好,并帮助我做决定。我通常选择int作为主键,除非我有特定的需要,我也让SQL server自动生成/维护这个字段,除非我有一些特定的原因不这样做。实际上,性能问题需要根据特定的应用程序来确定。这里有许多因素,包括但不限于预期的db大小、适当的索引、有效的查询等等。尽管人们可能不同意,但我认为在许多情况下,你不会注意到这两种选择之间的区别,你应该选择更适合你的应用,以及让你更容易、更快速、更有效地开发应用的选项(如果你从未完成应用,那么其他选项又有什么区别呢:)。

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

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

guid似乎是主键的自然选择——如果确实必须,可能会主张将它用于表的primary key。我强烈建议不做使用GUID列作为聚类的关键, SQL Server默认这样做,除非你特别告诉它不要这样做。

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

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

  2. 聚类的关键(定义“聚集索引”的一列或多列;-这是一个物理存储相关的东西,在这里,一个小的,稳定的,不断增加的数据类型是你的最佳选择- INTBIGINT作为你的默认选项。

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

正如金伯利特里普(索引女王)和其他人多次指出的那样,GUID作为聚类键并不是最优的,因为它的随机性,它将导致大量的页面和索引碎片,并且通常会导致糟糕的性能。

是的,我知道-在SQL Server 2005及更高版本中有newsequentialid() -但即使是它也不是真正的完全顺序的,因此也遭受与GUID相同的问题-只是没有那么突出。

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

快速计算-使用INT vs. GUID作为主键和集群键:

  • 1'000'000行基本表(3.8 MB vs. 15.26 MB)
  • 6个非聚集索引(22.89 MB vs. 91.55 MB)

总计:25 MB vs. 106 MB -这只是在一个表上!

金伯利·特里普的精彩作品——再读一遍,消化它!它是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)作为我的集群主键——比其他任何东西都更有效!

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

我从2005年开始使用guid作为pk。在这个分布式数据库世界中,它绝对是合并分布式数据的最佳方法。您可以触发并忘记合并表,而不必担心在连接的表之间进行整型匹配。可以毫无顾虑地复制guid连接。

这是我使用guid的设置:

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

  2. PK Guid是非聚类索引。永远不要集群索引一个GUID,除非它是NewSequentialID。但即便如此,服务器重新启动也会导致排序出现重大中断。

  3. 为每个表添加ClusterID Int。这是你的聚集索引…这是为您点菜。

  4. 在clusterid (int)上连接更有效,但我使用的是2000 - 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)

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

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

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

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

大多数情况下,它不应该用作表的主键,因为它真的会影响数据库的性能。 关于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/

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

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

好吧,如果您的数据从来没有达到数百万行,那就没问题了。如果你问我,我从来不使用GUID作为任何类型的数据库标识列,包括PK,即使你强迫我用霰弹枪在头上设计。

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

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

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

这些数字(5000万和5亿)当然是为轻选择使用。如果您需要以复杂的方式选择信息和/或有大量的插入/更新/删除,对于一个非常苛刻的系统,这些甚至可能是1-2百万和5千万。如果您还添加了完整恢复模型、高可用性和无维护窗口等现代系统常见的因素,情况就会变得非常糟糕。

注意,在这一点上,20亿是int的限制,看起来很糟糕,但int是4倍小,是一个顺序类型的数据,小的大小和顺序类型是数据库可伸缩性的首要因素。你可以使用big int,它只小了两倍,但仍然是顺序的,顺序是非常重要的,甚至比大小更重要,当涉及到数百万或数十亿行的时候。

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

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

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