为什么有时SQL Server无法收缩数据库文件

    首先要申明,一般情形下没有必要对用户数据库的数据文件进行收缩,因为虽然可能看到有很多空间被占用,但是实际未释放,但是当数据库中有新的对象或者新的数据进来时,这些空间是会被重新使用到的。

    但是在某些特定情况下,比如磁盘空间快满了,但是硬件小组暂时并未有足够的资源可以调配,如果发现了该磁盘的某个数据库中有大量的未使用空间,可能心想“可以搞定”。但是当实际收缩的时候,一直无法收缩,又凉凉了。

    那么这个时候,我们就要看下,为什么实际存在未使用空间,但是实际却无法收缩。

    DBCC SHRINKFILE的工作原理:

DBCC SHRINKFILE执行时,是做的区(Extent)级别的动作。他会将使用过的区前移,没有使用的区从文件中移除掉。但是,并不会将一个区中的空页移除,然后合并区,也不会把页面中的空间移除,合并页面。所以如果一个数据库中有很多的区,但是这些区中只有一两个页才有数据,那么DBCC SHRINKFILE就不会起作用。

    这也就解释了上面所说的那些情况,为什么明明看到数据文件有空间,但是不能压缩或者清空。通常的原因是数据文件里虽然有很多空的页面,但是页面是分布在各个区中,所以就没办法压缩了。

    举个例子:

use wisontest
go
create table show_extent
(a int,
 b nvarchar(3900))
 go
 declare @i int
 set @i=1
 while @i<=1000
 begin
	insert into show_extent values (1,replicate(N'a',3900))
	insert into show_extent values (2,replicate(N'b',3900))
	insert into show_extent values (3,replicate(N'c',3900))
	insert into show_extent values (4,replicate(N'd',3900))
	insert into show_extent values (5,replicate(N'e',3900))
	insert into show_extent values (6,replicate(N'f',3900))
	insert into show_extent values (7,replicate(N'g',3900))
	insert into show_extent values (8,replicate(N'h',3900))
	set @i=@i+1
end

dbcc showcontig('show_extent')

    这个时候得到如下的结果。可以看到申请了8000个数据页,也就是1001个区。

DBCC SHOWCONTIG scanning 'show_extent' table...
Table: 'show_extent' (885578193); index ID: 0, database ID: 12
TABLE level scan performed.
- Pages Scanned................................: 8000
- Extents Scanned..............................: 1001
- Extent Switches..............................: 1000
- Avg. Pages per Extent........................: 8.0
- Scan Density [Best Count:Actual Count].......: 99.90% [1000:1001]
- Extent Scan Fragmentation ...................: 0.10%
- Avg. Bytes Free per Page.....................: 279.0
- Avg. Page Density (full).....................: 96.55%
DBCC execution completed. If DBCC printed error messages, contact your system administrator.

    此时,我们删除掉每个分区中的7个页面,只保留a=5的记录

delete show_extent where a<>5

exec sp_spaceused show_extent

exec sp_spaceused

  返回的结果如下,可以看到还是有一半的空间被占用到了(虽然实际上这个时候应该只使用1/8的空间才对)。

    

    如果此时,我们去执行DBCC SHRINKFILE,那么我们会发现没有什么效果。面对这种情况的时候,我们可以通过对该表新建一个聚集索引来使得可以收缩文件(如果表有聚集索引,那么就是重建聚集索引)。 

create clustered index icx_show_extent on show_extent(a)
exec sp_spaceused show_extent
exec sp_spaceused

  

    此时就可看到未使用空间和理论值一样了,如果此时需要做收缩文件的操作,那么就可以达到效果了。 

原文地址:https://www.cnblogs.com/Wison-Ho/p/14109612.html