如何降低 SQL Server 中的 HEAP 碎片?

tux*_*nia 11 sql-server heap fragmentation

我最近发现一个堆表有超过 70% 的碎片。所以我决定做一个

ALTER TABLE dbo.myTable REBUILD
Run Code Online (Sandbox Code Playgroud)

有趣的是,之后我有 20% 的碎片。从那以后,那张桌子上再也没有写过。所以我决定再做一次重建。

第2次后桌帽50%碎片化就更厉害了! 我真的不明白这怎么会发生......

Sha*_*nky 20

堆中的碎片是什么意思

avg_fragmentation_in_percent通过查询sys.dm_db_index_physical_statsDMV从列中获得的 Heap 中的碎片值指出

IN_ROW_DATA 分配单元中索引的逻辑碎片或堆的范围碎片

此外,同一个 BOL 说

这是堆的叶页中乱序范围的百分比。乱序范围是指包含堆的当前页的范围在物理上不是包含前一页的范围之后的下一个范围。

因此,您可以看到分配给堆的页面中存在的可用空间不是空闲空间,而是创建碎片的不同页面序列

这可以通过小测试来证明。让我们创建一个堆表并在其中插入一些记录,然后检查碎片。

create table dbo.HeapTest
(
Id INT not NULL Default (1),
Col1   char(5000) Not null Default ('Heaps Are Cool')
)

SET NOCOUNT ON

Insert into dbo.Heaptest default values
go 50

select index_type_desc,avg_fragmentation_in_percent,fragment_count,
avg_page_space_used_in_percent,record_count
from sys.dm_db_index_physical_stats(db_id(),object_id('dbo.HeapTest','U'),0,default,'detailed')
Run Code Online (Sandbox Code Playgroud)

因此,创建了包含 50 条记录的堆表。下面是查询 DMV sys.dm_db_index_physical stats 后碎片的样子

在此处输入图片说明

您可以看到avg_fragmentation_in_percent列值为 33 %。现在让我们看看页面是如何排列的。这可以通过使用未记录的查询来完成%%lockres%%。查询将是

SELECT  %%lockres%%, * FROM dbo.HeapTest;
Run Code Online (Sandbox Code Playgroud)

下面是输出的样子。只附上它的相关部分。由于我们在 dbo.HeapTest 表中插入了 50 行,因此查询产生了 50 行。

在此处输入图片说明

它说的是第一页有 ID197下一页有 ID242后续页面有连续 ID 直到我们到达页面 ID264因为之后我们得到页面 ID 280。因此,页面 ID 号的这种跳跃实际上是导致碎片化的原因。

现在以免重建堆并再次运行该命令以查看碎片以及页面是如何排列的。我们得到碎片化

在此处输入图片说明

你可以看到碎片是现在14%

让我们看看分配的页码

在此处输入图片说明

我们只有一次跳转,所有页面都被依次分配了页面 ID。由于仅仅一跳,碎片就大大减少了。

我再次重建堆,现在当我检查碎片时它完全消失了。而页面 ID 分配就像

在此处输入图片说明

为什么碎片化增加

现在关于可能导致碎片增加的原因,我们可以证实这一事实,即当页面被分配到堆时,它们不会是连续的,正如您在上面看到的,导致碎片值增加的原因是分配给页面的 PAGE ID 的跳转。

在脑后,您还应该记住,HEAP 的碎片这个词没有任何意义,您将如何为一堆无序页面定义碎片。

真的很担心碎片化

如果您真的遇到堆表碎片化和查询速度变慢的情况,那么在表上创建聚集索引比重建它更好。原因是当你重建堆时,所有底层的非聚集索引也被重建,导致重建过程需要更长的时间,利用大量资源和膨胀的事务日志。在生产系统上,人们总是会尽量避免这种情况。保罗在他关于堆的神话部分中介绍了这一点。

PS:请不要在生产系统上使用未记录的命令。这只是为了演示。