谁能确认如果我运行“ALTER INDEX ALL ON REORGANIZE
WITH ( LOB_CAMPACTION = ON )” 声明,它将压缩
varbinary(max) LOB 列,即使它们不存在于任何
非聚集索引和仅在底层堆中?
是的。您可以通过经验轻松地确认这一点,我们将在稍后完成。
这背后的基本原理是回收由
将符合条件的行的 LOB 列设置为 null 的应用程序作业。
LOB 压缩不会真正回收所有释放的空间。即使重建整个表也不会回收 LOB 空间——重组是您能做的最好的事情,但这并不会回收所有东西。如果它让您感觉更好:这不仅限于堆表,它实际上是一个特性,而不是一个错误。
让我证明一下。让我们用 LOB 数据创建一个堆表:
CREATE TABLE heap_of_trouble(ID INT IDENTITY, lobby VARBINARY(MAX));
-- SQL Server will store values <8K in the row by default; force the use of LOB pages
EXEC sp_tableoption 'heap_of_trouble', 'large value types out of row', 1;
SET NOCOUNT ON;
GO
BEGIN TRANSACTION;
GO
INSERT heap_of_trouble(lobby) VALUES (CONVERT(VARBINARY(MAX), REPLICATE(' ', 4000)));
GO 10000
COMMIT;
SELECT p.[rows], p.index_id, au.[type_desc], au.data_pages, au.total_pages, au.used_pages
FROM sys.partitions p
JOIN sys.allocation_units au ON au.container_id = p.hobt_id
JOIN sys.objects o ON o.[object_id] = p.[object_id]
WHERE o.[name] = 'heap_of_trouble'
+-------+----------+-------------+------------+-------------+------------+
| rows | index_id | type_desc | data_pages | total_pages | used_pages |
+-------+----------+-------------+------------+-------------+------------+
| 10000 | 0 | IN_ROW_DATA | 43 | 49 | 44 |
| 10000 | 0 | LOB_DATA | 0 | 5121 | 5118 |
+-------+----------+-------------+------------+-------------+------------+
让我们清除一些列:
UPDATE heap_of_trouble SET lobby = NULL WHERE ID % 2 = 0;
让我们再次获取页数:
+-------+----------+-------------+------------+-------------+------------+
| rows | index_id | type_desc | data_pages | total_pages | used_pages |
+-------+----------+-------------+------------+-------------+------------+
| 10000 | 0 | IN_ROW_DATA | 43 | 49 | 44 |
| 10000 | 0 | LOB_DATA | 0 | 5121 | 5117 |
+-------+----------+-------------+------------+-------------+------------+
没有变化,除了最后一页。这是预期的。所以现在让我们重新组织和压缩:
ALTER INDEX ALL ON heap_of_trouble REORGANIZE WITH (LOB_COMPACTION = ON);
+-------+----------+-------------+------------+-------------+------------+
| rows | index_id | type_desc | data_pages | total_pages | used_pages |
+-------+----------+-------------+------------+-------------+------------+
| 10000 | 0 | IN_ROW_DATA | 43 | 49 | 44 |
| 10000 | 0 | LOB_DATA | 0 | 3897 | 3897 |
+-------+----------+-------------+------------+-------------+------------+
您会注意到页数不是我们开始时的一半:LOB 数据已重新组织,但并未完全重建。
如果您改用ALTER TABLE .. REBUILD,您会发现 LOB 数据根本没有压缩:
+-------+----------+-------------+------------+-------------+------------+
| rows | index_id | type_desc | data_pages | total_pages | used_pages |
+-------+----------+-------------+------------+-------------+------------+
| 10000 | 0 | IN_ROW_DATA | 29 | 33 | 30 |
| 10000 | 0 | LOB_DATA | 0 | 5121 | 5117 |
+-------+----------+-------------+------------+-------------+------------+
请注意IN_ROW_DATA 是如何重建的,但 LOB 数据完全保持不变。您也可以尝试使用聚集索引(只需将 ID 设为 PRIMARY KEY 即可隐式创建一个)。但是,对于非聚集索引,情况并非如此。重新开始,但这次添加另一个索引:
CREATE INDEX IX_heap_of_trouble_ID ON heap_of_trouble (ID) INCLUDE (lobby)
当然,在索引中包含 LOB 数据不是正常设置;这只是为了说明。看看我们在ALTER TABLE REBUILD 之后得到了什么:
+-------+----------+-------------+------------+-------------+------------+
| rows | index_id | type_desc | data_pages | total_pages | used_pages |
+-------+----------+-------------+------------+-------------+------------+
| 10000 | 0 | IN_ROW_DATA | 29 | 33 | 30 |
| 10000 | 0 | LOB_DATA | 0 | 5121 | 5117 |
| 10000 | 2 | IN_ROW_DATA | 35 | 49 | 37 |
| 10000 | 2 | LOB_DATA | 0 | 2561 | 2560 |
+-------+----------+-------------+------------+-------------+------------+
惊喜(也许),非聚集索引的LOB数据是重建的,而不仅仅是重组。 ALTER INDEX ALL .. REBUILD 将具有相同的效果,但不会影响堆。用一张小表来总结一下:
+----------------------+---------------+-------------------+----------------------+
| | TABLE REBUILD | INDEX ALL REBUILD | INDEX ALL REORGANIZE |
+----------------------+---------------+-------------------+----------------------+
| Heap in-row | Rebuild | - | - |
| Heap LOB | - | - | Reorganize |
| Clustered in-row | Rebuild | Rebuild | Reorganize |
| Clustered LOB | - | - | Reorganize |
| Non-clustered in-row | Rebuild | Rebuild | Reorganize |
| Non-clustered LOB | Rebuild | Rebuild | Reorganize |
+----------------------+---------------+-------------------+----------------------+
我正在考虑禁用
堆,重建堆,然后重新启用非集群
索引。
您不需要单独重新启用非聚集索引; ALTER TABLE .. REBUILD 也会重建所有索引,并且禁用的索引将作为重建的一部分重新启用。
此操作是否还会重新声明/压缩 lob 中任何未使用的空间
列以及删除转发的记录并删除但不
完全解除分配的行?
根据我们之前的结果,不,不完全是。如果您对仅将 LOB 数据与重建表的其余部分进行压缩感到满意,那么过程将是:
- 执行
ALTER INDEX ALL .. DISABLE 禁用所有非聚集索引;
- 执行
ALTER INDEX ALL .. REORGANIZE WITH (LOB_COMPACTION = ON) 以压缩底层堆的 LOB 页面(这将不理会禁用的索引);
- 执行
ALTER TABLE .. REBUILD重建堆的行内数据,以及索引的所有数据,并重新启用它们。
如果您真的想将堆缩小到最小大小,则必须创建一个新表并在其中插入数据,但这涉及更多的脚本编写和对sp_rename 的明智使用。它也非常昂贵,因为它需要复制所有 LOB 数据(REORGANIZE 避免的事情)。如果您在不注意文件组和使用的日志空间的情况下执行此操作,则最终消耗的空间可能会超过您寻求回收的空间,并且不太可能有助于提高性能。