无论是什么关系型数据库,尤其在OLTP系统中,索引是提升数据访问速度的常用方式之一,但是不同类型的数据库,对索引碎片的处理可能会略有不同。

我们知道,索引碎片多了,意味着可能会扫描更多的数据块、消耗更多的资源,因此,要在某些场景下,对碎片做些处理。SQL Server中,索引碎片有2种形式:外部碎片和内部碎片,不管哪种碎片基本上都会影响索引内页的使用。

1. 外部碎片

当索引页不在逻辑顺序上时就会产生外部碎片。索引创建时,索引键按照逻辑顺序放在一组索引页上。当新数据插入索引时,新的键可能放在存在的键之间。为了让新的键按照正确的顺序插入,可能会创建新的索引页来存储需要移动的那些存在的键。这些新的索引页通常物理上不会和那些被移动的键原来所在的页相邻。创建新页的过程会引起索引页偏离逻辑顺序。

在有特定搜索或者返回无序结果集的查询的情况下,偏离顺序的索引页不会引起问题。对于返回有序结果集的查询,搜索那些无序的索引页需要进行额外处理。查询可能需要进行额外的页切换,虽然一个额外的页切换在一个长时间运行里是无关紧要的,但是如果有个好几百页偏离顺序的非常大的表的情形,这就很糟糕了。

2. 内部碎片

当索引页没有用到最大量时就产生了内部碎片。虽然在一个有频繁数据插入的应用程序里这也许有帮助,但是设置一个fill factor(填充因子)会在索引页上留下空间,服务器内部碎片会导致索引尺寸增加,从而在返回需要的数据时要执行额外的读操作,这些额外的读操作会降低查询的性能。

怎么确定索引是否有碎片?

可以用DBCC SHOWCONTIG命令,格式如下,

DBCC SHOWCONTIG   
[ (   
    { table_name | table_id | view_name | view_id }   
    [ , index_name | index_id ]   
) ]   
    [ WITH   
        {   
         [ , [ ALL_INDEXES ] ]   
         [ , [ TABLERESULTS ] ]   
         [ , [ FAST ] ]  
         [ , [ ALL_LEVELS ] ]   
         [ NO_INFOMSGS ]  
         }  
    ]

P.S. 

https://docs.microsoft.com/en-us/sql/t-sql/database-console-commands/dbcc-showcontig-transact-sql?view=sql-server-ver15

其中,

FAST选项指定执行索引的快速扫描,输出结果是最小的,该选项不读索引的叶或数据页且只返回扫描页数、扫描扩展盘区数、扫描密度[最佳值:实际值]、逻辑扫描碎片。

TABLERESULTS选项将用行集的形式显示信息,将返回扩展盘区开关数、扫描密度[最佳值:实际值]、逻辑扫描碎片、扩展盘区扫描碎片、每页上的平均可用字节数、平均页密度(完整)。如果既指定FAST选项又指定TABLERESULTS选项,那么将返回对象名、对象ID、索引名、索引ID,页数、扩展盘区开关数、扫描密度[最佳值:实际值]和逻辑扫描碎片。

ALL_INDEXES选项将显示指定表和试图的所有索引的结果,即使指定了一个索引。

ALL_LEVELS选项指定是否为所处理的每个索引的每个级别产生输出(默认只输出索引的页级或表数据级的结果),并且只能与TABLERESULTS选项一起使用。

测试如下,

USE 数据库名称
GO
DBCC SHOWCONTIG(TEST)


DBCC SHOWCONTIG 正在扫描 'TEST' 表...
表: 'TEST' (341589001);索引 ID: 1,数据库 ID: 5
已执行 TABLE 级别的扫描。
- 扫描页数................................: 381114
- 扫描区数..............................: 40102
- 区切换次数..............................: 140012
- 每个区的平均页数........................: 7.9
- 扫描密度 [最佳计数:实际计数].......: 34.01% [47640:140011]
- 逻辑扫描碎片 ..................: 27.72%
- 区扫描碎片 ..................: 63.43%
- 每页的平均可用字节数.....................: 1202.1
- 平均页密度(满).....................: 85.25%
DBCC 执行完毕。如果 DBCC 输出了错误信息,请与系统管理员联系。

参数说明如下,

扫描页数(Page Scanned):如果你知道行的近似尺寸和表或索引里的行数,那么你可以估计出索引里的页数。看看扫描页数,如果明显比你估计的页数要高,说明存在内部碎片。

扫描扩展盘区数(Extents Scanned):用扫描页数除以8,四舍五入到下一个最高值。该值应该和DBCC SHOWCONTIG返回的扫描扩展盘区数一致。如果DBCC SHOWCONTIG返回的数高,说明存在外部碎片。碎片的严重程度依赖于刚才显示的值比估计值高多少。

扩展盘区开关数(Extent Switches):该数应该等于扫描扩展盘区数减1。高了则说明有外部碎片。

每个扩展盘区上的平均页数(Avg. Pages per Extent):该数是扫描页数除以扫描扩展盘区数,一般是8。小于8说明有外部碎片。

扫描密度[最佳值:实际值](Scan Density [Best Count:Actual Count]):DBCC SHOWCONTIG返回最有用的一个百分比。这是扩展盘区的最佳值和实际值的比率。该百分比应该尽可能靠近100%。低了则说明有外部碎片。有朋友推荐,该值对应的操作,10%以内不维护,10%-30%重组,30%以上重建。

逻辑扫描碎片(Logical Scan Fragmentation):无序页的百分比。该百分比应该在0%到10%之间,高了则说明有外部碎片。

扩展盘区扫描碎片(Extent Scan Fragmentation):无序扩展盘区在扫描索引叶级页中所占的百分比。该百分比应该是0%,高了则说明有外部碎片。

每页上的平均可用字节数(Avg. Bytes Free per Page):所扫描的页上的平均可用字节数。越高说明有内部碎片,不过在你用这个数字决定是否有内部碎片之前,应该考虑fill factor(填充因子)。

平均页密度(完整)(Avg. Page Density (full)):每页上的平均可用字节数的百分比的相反数。低的百分比说明有内部碎片。

DBCC SHOWCONTIG实际上仅对那些大表有用。小表显示的结果根本不符合正常标准,因为他们也许没有由多于8个的页面组成。在查看小表上执行DBCC SHOWCONTIG的结果时应该忽略一些结果,在处理小表时只需关心扩展盘区开关数、逻辑扫描碎片、每页上的平均可用字节数、平均页密度(完整)。

当然还可以使用客户端工具SQL Server Management Studio,可视化地看碎片,

解决碎片问题,可以有几种操作,

1. 删除索引,并重建

需要手工执行DROP INDEX和CREATE INDEX,缺点显而易见,就是这两个操作存在时间差,即刚删除,未重建完成前,其实是没索引的,查询性能受影响,而且当都请求索引的时候会引起阻塞,直到重建索引为止。另外,用DROP INDEX和CREATE INDEX重建聚集索引时会引起非聚集索引重建两次。删除聚集索引时非聚集索引的行指针会指向数据堆,聚集索引重建时非聚集索引的行指针又会指回聚集索引的行位置。

这种操作的好处是通过重新排序索引页,使索引页紧凑并删除不需要的索引页来完全重建索引,因此可能在内部和外部碎片都很高的情况下才使用,以使那些索引回到它们应该在的位置。

参考,10%以内不维护,10%-30%重组,30%以上重建。

如果是重组,可以用这种操作,

ALTER INDEX ALL ON table_name REORGANIZE;

如果是重建索引,还可以用这种,

ALTER INDEX ALL ON TableName REBUILD WITH (FILLFACTOR=90, ONLINE=ON);
ALTER INDEX index_name ON table_name REBUILD GO;

如果用的SQL Server Management Studio,图形化就能操作。

2. 使用DROP_EXISTING子句重建索引

为了避免在重建聚集索引时表上的非聚集索引重建两次,可以使用带DROP_EXISTING子句的CREATE INDEX语句。这个子句会保留聚集索引键值,以避免非聚集索引重建两次。和删除并重建索引一样,该方法也可能会引起阻塞和索引消失的问题。这个应该是针对每个索引的操作,因此,需要强迫你找到表上的每一个索引。

3. DBCC DBREINDEX

DBCC DBREINDEX类似于第二种方法,但他物理地重建索引,允许SQL Server给索引分配新页来减少内部和外部碎片。DBCC DBREINDEX也能动态的重建带约束的索引,不像第二种方法。

DBCC DBREINDEX的缺陷是会遇到或引起阻塞问题,DBCC DBREINDEX是作为一个事务来运行的,所以如果在完成之前中断了,那么会丢失所有已经执行过的碎片。

例如,

dbcc dbreindex('database.dbo.test','',90)

倒腾碎片后,可能就是这样,确实从数字上,更清晰了,但是无论是重建、重组,都会涉及到索引数据块的调整,因此他的执行时间应该是和数据量和碎片的多少直接相关的,如果对大表,这些操作,还是尽量选择在系统低峰期,或者有停机时间,

DBCC SHOWCONTIG 正在扫描 'TEST' 表...
表: 'TEST' (341589001);索引 ID: 1,数据库 ID: 5
已执行 TABLE 级别的扫描。
- 扫描页数................................: 331728
- 扫描区数..............................: 41803
- 区切换次数..............................: 41802
- 每个区的平均页数........................: 7.9
- 扫描密度 [最佳计数:实际计数].......: 99.19% [41466:41803]
- 逻辑扫描碎片 ..................: 0.40%
- 区扫描碎片 ..................: 63.01%
- 每页的平均可用字节数.....................: 169.2
- 平均页密度(满).....................: 97.91%

SQL Server的操作,还是不太熟悉,网上有些朋友,将以上的检测、重建过程,封装了下,可以参考,

1. 查看碎片情况

--改成当前库
use DB_Name
--创建变量 指定要查看的表
declare @table_id int
set @table_id=object_id('TableName')
--执行
dbcc showcontig(@table_id)

2. 使用SQL Server代理任务定期重建索引

但是不建议定期进行重建,比较耗时,

DECLARE @TABLENAME NVARCHAR(MAX)
IF(CURSOR_STATUS('global','TABLE_CURSOR')<>'-3')
    BEGIN
        CLOSE TABLE_CURSOR
        DEALLOCATE TABLE_CURSOR
    END
DECLARE TABLE_CURSOR CURSOR FOR SELECT NAME FROM SYS.objects WHERE TYPE = 'U' AND name LIKE 'T_HIS%' ORDER BY modify_date DESC
OPEN TABLE_CURSOR
FETCH NEXT FROM TABLE_CURSOR INTO @TABLENAME
WHILE (@@FETCH_STATUS = 0) 
    BEGIN 
        dbcc dbreindex (@TABLENAME,'',80)
        FETCH NEXT FROM TABLE_CURSOR INTO @TABLENAME
    END
CLOSE TABLE_CURSOR
DEALLOCATE TABLE_CURSOR

参考资料,

https://blog.csdn.net/y_h_t/article/details/6120945

https://blog.csdn.net/u011334954/article/details/98728830

https://www.cnblogs.com/cl1024cl/archive/2008/06/25/6204920.html

近期更新的文章:

MySQL客户端预读数据的区别

小白学习MySQL-MySQL分组需求探秘

国产数据库排行榜解读

了解一下ISO 8601是什么

小白学习MySQL - 查询会锁表?

文章分类和索引:

公众号700篇文章分类和索引

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐