首先要申明,一般情形下没有必要对用户数据库的数据文件进行收缩,因为虽然可能看到有很多空间被占用,但是实际未释放,但是当数据库中有新的对象或者新的数据进来时,这些空间是会被重新使用到的。
但是在某些特定情况下,比如磁盘空间快满了,但是硬件小组暂时并未有足够的资源可以调配,如果发现了该磁盘的某个数据库中有大量的未使用空间,可能心想“可以搞定”。但是当实际收缩的时候,一直无法收缩,又凉凉了。
那么这个时候,我们就要看下,为什么实际存在未使用空间,但是实际却无法收缩。
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... |
此时,我们删除掉每个分区中的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
此时就可看到未使用空间和理论值一样了,如果此时需要做收缩文件的操作,那么就可以达到效果了。