前些天我遇到一個問題,一個200Gb的庫,其中一個表大約2000W行數據,我刪除了其中約600W行數據。我想把刪除后未使用的空間騰出來。
按照以往的經驗,重建這個表上的聚集索引就可以了。可是這次表上只有一個非聚集主鍵索引。如何釋放這個堆表未使用的空間
首先來模擬出我遇到的情況:
USE master
GO
CREATE DATABASE TEST;
GO
USE TEST
GO
--每一行占一個PAGE.
CREATE TABLE tb_Test
(
id INT NOT NULL ,
val CHAR(8000)
);
ALTER TABLE dbo.tb_Test
ADD CONSTRAINT PK_tbTest_id PRIMARY KEY NONCLUSTERED (id);
GO
INSERT INTO dbo.tb_Test
( id, val )
VALUES ( 1, REPLICATE('A',10)),( 2, REPLICATE('B',10)),
( 3, REPLICATE('C',10)),( 4, REPLICATE('D',10)),
( 5, REPLICATE('E',10)),( 6, REPLICATE('F',10)),
( 7, REPLICATE('E',10)),( 8, REPLICATE('F',10)),
( 9, REPLICATE('E',10)),( 10, REPLICATE('F',10));
GO
DBCC SHOWCONTIG('tb_Test');
GO
(10 行受影響)
DBCC SHOWCONTIG 正在掃描 'tb_Test' 表...
表: 'tb_Test' (85575343);索引 ID: 0,數據庫 ID: 12
已執行 TABLE 級別的掃描。
- 掃描頁數................................: 10
- 掃描區數..............................: 5
- 區切換次數..............................: 4
- 每個區的平均頁數........................: 2.0
- 掃描密度 [最佳計數:實際計數].......: 40.00% [2:5]
- 區掃描碎片 ..................: 80.00%
- 每頁的平均可用字節數.....................: 83.0
- 平均頁密度(滿).....................: 98.97%
每行占用一個頁所以共有10頁,接下來我刪除其它5行。
DELETE FROM dbo.tb_Test
WHERE id<6;
GO
DBCC SHOWCONTIG('tb_Test');
GO
(5 行受影響)
DBCC SHOWCONTIG 正在掃描 'tb_Test' 表...
表: 'tb_Test' (85575343);索引 ID: 0,數據庫 ID: 12
已執行 TABLE 級別的掃描。
- 掃描頁數................................: 10
- 掃描區數..............................: 5
- 區切換次數..............................: 4
- 每個區的平均頁數........................: 2.0
- 掃描密度 [最佳計數:實際計數].......: 40.00% [2:5]
- 區掃描碎片 ..................: 80.00%
- 每頁的平均可用字節數.....................: 4088.5
- 平均頁密度(滿).....................: 49.49%
仍然占用10頁,怎么釋放出這“空余”的5頁?
嘗試1:把非聚集主鍵索引改成聚集,再把它改回來。因為聚集索引的葉級頁是數據本身,所以創建或者重建都會重新組織數據頁。
ALTER TABLE [dbo].[tb_Test] DROP CONSTRAINT [PK_tbTest_id];
ALTER TABLE dbo.tb_Test
ADD CONSTRAINT PK_tbTest_id PRIMARY KEY CLUSTERED (id);
ALTER TABLE [dbo].[tb_Test] DROP CONSTRAINT [PK_tbTest_id];
ALTER TABLE dbo.tb_Test
ADD CONSTRAINT PK_tbTest_id PRIMARY KEY NONCLUSTERED (id);
GO
DBCC SHOWCONTIG('tb_Test');
GO
DBCC SHOWCONTIG 正在掃描 'tb_Test' 表...
表: 'tb_Test' (85575343);索引 ID: 0,數據庫 ID: 12
已執行 TABLE 級別的掃描。
- 掃描頁數................................: 5
- 掃描區數..............................: 3
- 區切換次數..............................: 2
- 每個區的平均頁數........................: 1.7
- 掃描密度 [最佳計數:實際計數].......: 33.33% [1:3]
- 區掃描碎片 ..................: 66.67%
- 每頁的平均可用字節數.....................: 83.0
- 平均頁密度(滿).....................: 98.97%
這種做法有效。但是釋放出來的空間不會返還給OS,只是成為數據庫的Unused space.
嘗試2:收縮數據文件。無論是SHRINKDATABASE或是SHRINKFILE原理都是一樣的。執行下面查詢時,要回滾“嘗試1”的操作。
DBCC SHRINKFILE(TEST);
DBCC SHOWCONTIG('tb_Test');
GO
DBCC SHOWCONTIG 正在掃描 'tb_Test' 表...
表: 'tb_Test' (149575571);索引 ID: 0,數據庫 ID: 12
已執行 TABLE 級別的掃描。
- 掃描頁數................................: 5
- 掃描區數..............................: 3
- 區切換次數..............................: 2
- 每個區的平均頁數........................: 1.7
- 掃描密度 [最佳計數:實際計數].......: 33.33% [1:3]
- 區掃描碎片 ..................: 66.67%
- 每頁的平均可用字節數.....................: 83.0
- 平均頁密度(滿).....................: 98.97%
這種做法也有效,也可以根據Shrink的設定把空間返還給OS。但是在生產環境中,特別是較大的庫執行,影響特別大,容易產生大量碎片,一般不會用到。做為緊急應對的一種方法吧。
總結:
1. 郁悶。我的生產環境中,在做了“嘗試1”並未起到效果,最后閑時維護使用“嘗試2”才釋放出來的。這點才是我做實驗和寫此文的初衷。
2. 不管什么表,最好還是建立一個聚集索。利於管理使用空間,不然像5行數據占據10行的空間,這種事情會經常發生。