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


336

我有一个几乎在所有表中都使用GUID作为主键的应用程序,并且我已经读到在将GUID用作主键时存在有关性能的问题。老实说,我还没有遇到任何问题,但是我将要启动一个新的应用程序,并且我仍然想将GUID用作主键,但是我正在考虑使用复合主键(GUID以及其他领域) )

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

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

为避免基于此方法的性能受到影响,创建基于GUID的主键的最佳实践是什么?


20
这个问题是不应该的。如果您的PK是群集的,那么几乎每个插入都有可能导致页面拆分。在现代版本的SQL Server中,已使用NEWSEQUENTIALID()对其进行了“修复”,但是却失去了能够事先进行计算的好处。我强烈建议您阅读其他地方的GUID,因为这是一个太宽泛的问题,可能会引发一场持续数小时的宗教斗争……
Aaron Bertrand

4
我还要补充一句,“ 服务器”一词不明确,因为我想在 服务器 分配Guid (不要让SQL创建GUID)
Erik Philips

这个问题有相似之处,这个“SQL服务器的GUID,排序算法,为什么” stackoverflow.com/questions/7810602/...
克林顿沃德

Answers:


494

GUID似乎是您的主键的自然选择-如果确实需要,您可能会争辩说将其用于表的PRIMARY KEY。我强烈建议您不要使用GUID列作为群集键,默认情况下,SQL Server 会这样做,除非您明确要求不要这样做

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

  1. 主键是一个逻辑结构-候选键唯一和可靠地识别你的表中每一行的一个。可以是任何东西,实际上是- INT,a GUID,字符串-选择最适合您的方案的东西。

  2. 聚集键(列或定义表上的“聚集索引”列) -这是一个物理存储相关的事情,在这里,一个小的,稳定的,不断增长的数据类型是您最好的挑选- INTBIGINT为您的默认选项。

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

正如索引王后金伯利·特里普Kimberly Tripp)以及其他人多次指出-GUID那样,-聚类键不是最佳的,因为它的随机性,它将导致大量的页面和索引碎片,并且通常会导致性能下降。

是的,我知道newsequentialid()-SQL Server 2005及更高版本中-但这不是真正且完全顺序的,因此也遭受与GUID- 相同的问题-只是不太明显。

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

快速计算-使用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列作为主键(而不是集群键),并将另一列MYINTINT 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)我作为集群主键-比其他任何方式都效率更高!


2
这是一个很好的答案,我要提到的一件事是,在插入之前能够生成密钥通常很有用。使用“ newsequentialid()”可以帮助群集,但是这需要额外的SQL往返。因此,“代理键”方法的另一个好处是,您可以在客户端生成新的id,而不必担心索引碎片。
Andrew Theken

2
我读这本书的方式是,既有非聚集的uniqueidentifier列又有int身份列,FK也应该是uniqueidentifier吗?如果这样做,您什么时候可以实际直接使用身份列,或者您不会?
pinkfloydx33

2
毫无疑问,GUID现在应该用于连接还是int id?我的直觉告诉我应该使用GUID,但我看不到使用int id的技术问题...
Nicolas Belley 2015年

3
@marc_s,但是在复制方案中,如果int列是标识,那么我们不应该使用GUID,因为int列可以在设备之间重复吗?
Nicolas Belley

6
@Kipei:主要问题是如果您拥有如此自然的价值-是的,您可以将其用作主键。但是:像这样DATETIME的值对于聚类键没有用,因为它们的精度仅为3.33ms,因此可以存在重复项。因此,在这种情况下,您*仍然需要一个INT IDENTITY替代项-因此,我通常默认情况下使用该替代项,因为frmo我已有20多年的经验,因此几乎不存在真正可用的自然键 ....
marc_s

51

自2005年以来,我一直将GUID用作PK。在这个分布式数据库世界中,这绝对是合并分布式数据的最佳方法。您可以解雇合并表,而不必担心合并表之间的整数匹配。可以轻松复制GUID联接。

这是我使用GUID的设置:

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

  2. PK Guid是非聚集索引。除非它是NewSequentialID,否则切勿对GUID建立索引。但是即使那样,服务器重新启动也将导致订单严重中断。

  3. 将ClusterID Int添加到每个表。这是您的聚集索引...,可以对您的表进行排序。

  4. 加入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)?
菲尔(Phil)

2
你打赌 索引发生了两件事:1.在ClusterID上集群-在磁盘上排序表(碎片为0%)。2.基于电子邮件ID的非群集-索引电子邮件ID字段以加快GUID ID查找。GUID字段查找的行为类似于字符串,因此如果没有索引,则EmailID查找将很慢。
罗伯特·J·古德

@ RobertJ.Good我之前已经看过讨论此方法的方法,即添加代理int密钥进行集群。但是我找不到任何地方可以显示通过使用堆使用代理键聚集索引而获得的性能提升。您是否有指向基准数据的链接?
Dale K

1
@DaleBurrell,您好,聚集索引是为了防止表碎片。当表自然在磁盘上按顺序增长且碎片少时,性能就会提高。
罗伯特·J·古德

@ RobertJ.Good这是一个Web应用程序吗?您在url / hrefs中使用什么?guid或int?
dariol

10

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

我所有的课程(表)以及一个INT PK和FK。我还有一个类型为Guid的附加列(由c#构造函数生成),上面带有非聚集索引。

EF中表的所有联接都是通过int键进行管理的,而外部(控制器)的所有访问均由Guid完成。

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


您是否需要执行任何操作来将整数pK配置为群集(如数据注释),还是只是自动配置?
艾伦·王

您对Guid one使用的物业名称是什么?
Trong Phan


3

该链接比我说的更好,对我的决策有帮助。我通常选择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和其他字段,情况会一样吗?
VAAA 2012年

1
PK(索引)将由两列组成,但是除非您出于某些业务特定的原因执行此操作,否则似乎没有必要。
马特

1
顺便说一句,这个问题是目前最具争议性和争议性的问题之一,因此很难获得答案,因为您将感到百分百满意。两种方法都需要权衡取舍,所以祝您好运:)
Matt


0

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


您可以提供任何逻辑或证据来支持此主张吗?我正在努力查看顺序ID如何危害安全性。
jonaglon

当然,如果您知道ID号是整数,则可以猜测数据库中的顺序记录。因此,如果查询单个项目,则可以说下一个项目是pk +1。如果您具有随机GUIDS,它将不会遵循模式。除了您先前查询过的记录(而且知道PK)以外,几乎不可能查询其他记录。
DaBlue

1
如果黑客可以查询您的数据库,那么您已经受到威胁,那么我将看不到顺序ID会如何使情况变得更糟。
jonaglon

1
如果用户可以将1012换成另一个数字并查看他们不应该看到的数据,那么就存在一个非常严重的安全问题,该问题不是由主键选择引起的,而是由主键选择引起的。我同意你的意思,谢谢你的解释。
jonaglon

2
您可以使用GUID在网页上找到记录,而不是表的PK。在网站中使用查询参数不应定义如何构造数据库架构。PK与UI或后端系统中的输入和参数无关。
Panos Roditakis
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.