SQL Server - Guid VS. Long

5

到目前为止,我一直使用C#的“Guid = Guid.NewGuid();”方法生成一个唯一ID,并将其存储为某些SQL Server数据库表中的ID字段,通过Linq to SQL进行操作。

有人告诉我,出于索引的原因,使用GUID是不好的,而应该使用自增长的Long。那么,使用Long会加速我的数据库事务吗?如果是这样,我该如何生成类型为Long的唯一ID呢?

谢谢!


我被告知,出于索引原因,使用GUID不是一个好主意。如果在标识字段上有聚集索引,则GUID将影响插入操作的性能。SQL Server根据聚集索引对表进行排序。由于新的GUID是非顺序的,插入新行将不得不插入到表的中间而不是末尾,从而导致大量IO。只要聚集索引列类型避免了这个问题就可以了。 - adrianbanks
1
只是补充一下 - 如果GUID是主键,那么默认情况下GUID将被聚集 - 这可以是非聚集的,但需要手动干预。 - Kev Riley
请参阅以下链接以了解与此主题相关的详细信息:http://sqlblogcasts.com/blogs/martinbell/archive/2009/05/25/GUID-Fragmentation-in-SQL-Server.aspx和http://www.dotnetrocks.com/default.aspx?showNum=455(其中包含大量关于此主题的内容) 。 - Robert Venables
7个回答

11

两种方法都有优缺点,主要取决于使用方式。

如果您需要能够在多个数据库中使用的标识符,则需要GUID。Long(手动为每个数据库分配不同的种子/增量)有一些技巧,但这并不适用于大规模应用。

就索引而言,如果索引是聚集的(默认情况下主键是聚集的,但可以修改表),则Long将提供更好的插入性能,因为表在每次插入后不需要重新组织。

然而,在同时插入方面,Long(identity)列比GUID要慢 - 身份列生成需要一系列独占锁来确保只有一行获得下一个顺序号。在许多用户不断插入许多行的环境中,这可能会影响性能。在这种情况下,GUID生成速度更快。

存储方面,GUID占用的空间是Long的两倍(8字节对16字节)。然而,如果8字节会在行的总体大小上产生明显差异,并且从磁盘中提取的叶子数目因此减少,则取决于您的行的总体大小。


3
如果您使用GUID作为聚集键,那么该值(比BIGINT大两倍)也会添加到每个非聚集索引的每个条目中,从而使空间浪费乘以不仅是磁盘上的空间,还包括SQL Server RAM。 - marc_s

4

"索引女王" - Kim Tripp - 在她的索引博客文章中基本上已经说得很清楚了:

基本上,她的最佳实践是:最佳聚集键应该是:

  • 唯一的
  • 小的
  • 稳定的(永远不会改变)
  • 递增的

GUID违反了"小"和"递增"原则,因此不是最佳选择。

此外:所有的聚簇键将被添加到每个非聚簇索引中的每个条目中(作为查找数据库记录的方式),因此您希望尽可能地使它们更小(INT = 4字节 vs. GUID = 16字节)。如果您有数亿行和多个非聚簇索引,选择 INT 或 BIGINT 而不是 GUID 可以产生重大差异 - 即使仅在空间上。

Marc


2
如果你担心规模问题,不要选择GUID。在较小的数据库中,从速度或规模的角度来看可能没有太大的区别,但是如果你进入了有超过一亿行数据和多个索引的表格,GUID将成为性能噩梦...请阅读Kim Tripp的文章,这是一个重大决定,也是我们花费了三个月技术债务的原因。 - Tom DeMille
嗨@marc_s。这是一个旧问题了。但在您过去的项目中,您决定选择什么?您使用GUID还是随机生成的bigint键? - deveton
1
@deveton:我尽可能避免使用GUID - 它们在性能方面对许多事情都有负面影响。我仍然非常喜欢使用INT IDENTITYBIGINT IDENTITY作为我的替代主键。 - marc_s
关于随机大整数?如何确保它永远不会重复...GUID在这个世界上永远不会重复。但是随机的int/bigint可能会重复吗? - deveton
1
@deveton:不是随机的 - IDENTITY 提供递增的值,这些值通过设计保证是唯一的 - 只要你让它们保持不变,不要去修改标识列... - marc_s

3

一个长整型(在 SQL Server 中为 big int)占用 8 个字节,而 GUID 占用 16 个字节,因此当进行查找时,使用长整型可以减少 SQL Server 需要比较的字节数一半。

在数据库中创建字段时,请使用 IDENTITY(1,1) 生成长整型。

因此,可以使用 create table 或 alter table 命令来实现:

Field_NAME BIGINT NOT NULL PRIMARY KEY IDENTITY(1,1)

查看评论以发布Linq到SQL


1
当您使用L2S将新记录提交到数据库时,请不要填写PK。它将由L2S在提交过程中填充,因此您可以在提交后从记录对象中检索它,如果需要的话。 - Lazarus
有没有一种使用SQL Server设计视图来完成这个的方法?我的SQL脚本编写能力不太好...... - Goober
有这个功能,但我不知道怎么做。抱歉 :( - kemiller2002


1

你可以整天讨论GUID或identity。我更喜欢数据库使用identity生成唯一值。如果你从多个数据库合并数据,添加另一列(用于标识源数据库,可能是tinyint或smallint),并形成一个组合主键。

如果你选择使用identity,请确保根据预期生成的键数选择正确的数据类型:

bigint - 8 Bytes - max positive value: 9,223,372,036,854,775,807  
int    - 4 Bytes - max positive value:             2,147,483,647

请注意,“期望键的数量”与行数不同。如果您主要添加和保留行,则可能会发现一个INT足以拥有超过20亿个唯一键。我敢打赌,您的表不会变得那么大。但是,如果您有一个高容量的表,在其中不断添加和删除行,您的行计数可能很低,但是您将快速使用完键。您应该进行一些计算,以查看使用INT的20亿个键需要多长时间。如果它不会很快用完,请使用INT,否则将键大小加倍并使用BIGINT。

1

当需要考虑到多个数据库的导入/导出时,请使用GUID。与指定IDENTITY属性的列相比,使用GUID在处理具有多个子关系的数据集时通常更容易。这是因为您可以在代码中以断开状态随机生成GUID,然后一次性提交所有更改。当GUID正确生成时,它们很难被偶然复制。对于标识列,您通常必须先插入父行并查询其新标识,然后再添加子数据。然后,您必须使用新的父标识更新所有子记录,然后将它们提交到数据库。孙子及以下的情况也是如此。这会积累很多看似不必要和乏味的工作。您可以通过不指定IDENTITY来生成类似于GUID的随机整数,但随着时间的推移,碰撞的几率会大大增加。(Guid.NewGuid()类似于随机Int128-尚不存在)。

我使用Byte(TinyInt),Int16(SmallInt),Int32 / UInt16(Int),Int64 / UInt32(BigInt)用于小型查找列表或不在多个数据库之间复制的数据(权限,应用程序配置,颜色名称等)。

我认为,无论您使用GUID还是长整型,索引查询所需的时间都是相同的。通常,表中有其他字段被索引,这些字段比128位更大(例如用户表中的用户名)。 GUID和整数之间的区别在于内存中索引的大小以及填充和重建索引的时间。大多数数据库事务通常是读取操作,写入操作很少。首先专注于优化从数据库中读取数据,因为它们通常由未经过适当优化的联接表、不正确的分页或缺少索引组成。

与任何事物一样,最好的方法是证明自己的观点。创建一个具有两个表的测试数据库。一个使用整数/长整型作为主键,另一个使用GUID。每个表都填充N百万行。监视每个表在CRUD操作(创建、读取、更新、删除)期间的性能。您可能会发现它确实会影响性能,但是影响微不足道。

服务器通常在没有调试环境和其他占用CPU、内存和硬盘I/O(特别是RAID)的应用程序的情况下运行。开发环境只能给您提供性能的大致概念。



网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接