任何人都可以确认,如果我运行“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
aPRIMARY 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
避免的)。如果您在不注意文件组和使用的日志空间的情况下执行此操作,则最终消耗的空间可能比您寻求回收的空间多,并且不太可能有助于提高性能。