Sequential Guid和碎片化问题

6
我正在尝试理解顺序GUID如何比常规GUID表现更好。
这是因为对于常规GUID,索引使用GUID的最后一个字节进行排序吗?由于它是随机的,所以会导致很多碎片和页面分裂,因为它经常会将数据移动到另一页来插入新数据?
由于顺序GUID是连续的,它将导致更少的页面分裂和碎片化?
我的理解正确吗?
如果有人能在这个问题上提供更多的信息,我将非常感激。
谢谢!
编辑:
顺序GUID = NEWSEQUENTIALID(),
常规GUID = NEWID()

你可能希望定义一下你所说的“sequential guid”,尤其是“regular guid”的含义。 - Greg Hewgill
2
@Greg:我相信他指的是由NEWSEQUENTIALID()生成的“顺序GUID”,而不是由NEWID()生成的“常规GUID”。 - Joe Stefanelli
3个回答

9

你在问题中已经说得很清楚了。

使用顺序GUID /主键,新行将添加到表的末尾,这对SQL服务器非常方便。相比之下,随机主键意味着新记录可以插入表中的任何位置-如果所有读取都在最后一页,则表的最后一页有很大可能性在缓存中,但是表中间的随机页在缓存中的可能性相当低,这意味着需要额外的IO。

此外,在插入行到表的中间时,有可能没有足够的空间来插入额外的行。如果是这种情况,则SQL服务器需要执行额外的昂贵IO操作以为记录创建空间-避免这种情况的唯一方法是在数据中散布间隙以允许插入额外的记录(称为填充因子),这本身会导致性能问题,因为数据分布在更多的页面上,因此需要更多的IO才能访问整个表。


谢谢!我只是想确认我理解得对。 - pdiddy

3
我在这个话题上倾听了Kimberly L. Tripp的智慧:
但是,一个不连续的GUID - 比如客户端生成的(使用.NET)或者使用newid()函数(在SQL Server中),都可能会导致基础表中的碎片化问题,而且它的大小也是一个问题。它的宽度是不必要的(比基于int的标识符宽4倍),基于int的标识符可以提供20亿(实际上是40亿)个唯一行。如果你需要超过20亿行,你可以选择bigint(8字节整型),并获得263-1行。
阅读更多:http://www.sqlskills.com/BLOGS/KIMBERLY/post/GUIDs-as-PRIMARY-KEYs-andor-the-clustering-key.aspx#ixzz0wDK6cece

使用序列Guid,由于它是顺序的,理论上所有新的索引页面都将是顺序的,从而提高性能。我理解得正确吗?对于常规的guid,记录会随机分布,因此使用范围进行常规选择语句可能会很慢? - pdiddy
@pdiddy - 既然“随机”GUID不是连续的,为什么要选择一系列它们呢? - JNK
2
你对顺序与随机性的观点是正确的,然而真正令人痛苦的是插入操作而不是查询操作。 - Joe Stefanelli
我明白了,插入操作由于页面分裂和碎片化确实很麻烦。但是带有连接的选择查询也会很麻烦,因为它必须进行大量的查找操作,特别是在与主键为常规 GUID 的表进行连接时。 - pdiddy

1

为了全面展示整个情况,可以使用名为ostress的实用工具。例如,您可以创建两个表:一个使用普通GUID作为主键,另一个使用顺序GUID。

-- normal one
CREATE TABLE dbo.YourTable(
   [id] [uniqueidentifier] NOT NULL,
   CONSTRAINT [PK_YourTable] PRIMARY KEY NONCLUSTERED (id)
);
-- sequential one
CREATE TABLE dbo.YourTableSeq(
   [id] [uniqueidentifier] NOT NULL CONSTRAINT [df_yourtable_id]  DEFAULT (newsequentialid()),
   CONSTRAINT [PK_YourTableSeq] PRIMARY KEY NONCLUSTERED (id)
);

然后,使用给定的工具,您可以运行一些插入操作,并选择有关索引碎片化的统计信息:

ostress -Slocalhost -E -dYourDB -Q"INSERT INTO dbo.YourTable VALUES (NEWID()); SELECT count(*) AS Cnt FROM dbo.YourTable; SELECT AVG_FRAGMENTATION_IN_PERCENT AS AvgPageFragmentation, PAGE_COUNT AS PageCounts FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, N'LIMITED') DPS INNER JOIN sysindexes SI ON DPS.OBJECT_ID = SI.ID AND DPS.INDEX_ID = SI.INDID WHERE SI.NAME = 'PK_YourTable';" -oE:\incoming\TMP\ -n1 -r10000

ostress -Slocalhost -E -dYourDB -Q"INSERT INTO dbo.YourTableSeq DEFAULT VALUES; SELECT count(*) AS Cnt FROM dbo.YourTableSeq; SELECT AVG_FRAGMENTATION_IN_PERCENT AS AvgPageFragmentation, PAGE_COUNT AS PageCounts FROM sys.dm_db_index_physical_stats (DB_ID(), NULL, NULL , NULL, N'LIMITED') DPS INNER JOIN sysindexes SI ON DPS.OBJECT_ID = SI.ID AND DPS.INDEX_ID = SI.INDID WHERE SI.NAME = 'PK_YourTableSeq';" -oE:\incoming\TMP\ -n1 -r10000

然后在文件 E:\incoming\TMP\query.out 中,您将找到您的统计数据。 我的结果是:

"Normal" GUID:
Records    AvgPageFragmentation     PageCounts           
---------------------------------------------- 
1000       87.5                     8                    
2000       93.75                    16                   
3000       96.15384615384616        26                   
4000       96.875                   32                   
5000       96.969696969696969       33                   
10000      98.571428571428584       70                   


Sequential GUID:
Records    AvgPageFragmentation     PageCounts           
---------------------------------------------- 
1000       83.333333333333343       6                    
2000       63.636363636363633       11                   
3000       41.17647058823529        17                   
4000       31.818181818181817       22                   
5000       25.0                     28                   
10000      12.727272727272727       55       

正如您所看到的,使用顺序生成的GUID进行插入操作时,索引碎片化较少,因为插入操作很少导致新页面分配。

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