DBCC showcontig的參數解析:
DBCC showcontig('bsscost'): --顯示指定表的所有索引的碎片信息。
掃描頁數:如果你知道行的近似尺寸和表或索引里的行數,那么你可以估計出索引里的頁數。看看掃描頁數(如果明顯比估計的頁數要高,說明存在內部碎片)。掃描區數:用掃描頁數除以8,四舍五入到下一個最高值。該值應該和DBCC SHOWCONTIG返回的掃描擴展盤區數一致如果DBCC SHOWCONTIG返回的數高,說明存在外部碎片。碎片的嚴重程度依賴於剛才顯示的值比估計值高多少(掃描區數=掃描頁數/8 如果過高說明有外部碎片)區切換次數:該數應該等於掃描擴展盤區數減1。高了則說明有外部碎片(理論值=掃描區數-1)。每個擴展盤區上的平均頁數:該數是掃描頁數除以掃描擴展盤區數(一般是8。小於8說明有外部碎片)掃描密度[最佳值:實際值]:DBCC SHOWCONTIG返回最有用的一個百分比。這是擴展盤區的最佳值和實際值的比率。該百分比應該盡可能靠近100%。低了則說明有外部碎片(百分比 越高越好)。邏輯掃描碎片:無序頁的百分比。該百分比應該在0%到10%之間,高了則說明有外部碎片(越低越好)。擴展盤區掃描碎片:無序擴展盤區在掃描索引葉級頁中所占的百分比。該百分比應該是0%,高了則說明有外部碎片(越低越好)。每頁上的平均可用字節數:所掃描的頁上的平均可用字節數。越高說明有內部碎片,不過在你用這個數字決定是否有內部碎片之前,應該考慮fill factor(填充因子)(越低越好)。平均頁密度(完整):每頁上的平均可用字節數的百分比的相反數。低的百分比說明有內部碎片(越高越好)。
索引碎片的理解 (Indexes Fragmentation 浪費的空間在數據庫中就是碎片)
這里的索引碎片可以理解為索引數據頁中的一些空隙,這應該如何理解呢?假如某一個頁里面是滿的,比如是8K,如果存在25%的空隙,那么真正有效的數據只有75%,舉個簡單的例子比如某個表格的索引數據有100個頁,
但是碎片率是25%,所以這100個換頁面里面只有75個頁面的數據是有效的。所以在索引的碎片率非常高的情況下,索引的效率就會非常低,因為其IO的使用率也會非常低。
索引碎片既指索引文件頁中的空白空間;又指被Page Split的索引頁;還指索引失序的數據頁。前面兩種我們稱之為索引內部碎片,后面一種我們叫着索引外部碎片。
這些操作帶來的后果是:更新操作可能導致失序(out of order);刪除操作導致空白條目(empty space);插入操作導致分頁(page split)。結果就是最終形成電話簿(類似於索引)的碎片外部碎片和內部碎片。
由於SQL Server讀取數據的最小單位是數據頁,而不是單條記錄,所以,相同的查詢語句需要SQL Server讀取更多的磁盤寬度,加之索引碎片會浪費更多的內存資源來存放讀取到的數據。
因此,碎片化程度越高意味着更高的內存使用浪費和更低的查詢性能。微軟建議索引碎片率在5%到30%之間,做索引重組;碎片率超過30%,做索引重建工作。
內部碎片:就好比2居室就住了一個人,空余一間居室。行分布在更多的頁中,內部碎片會造成數據行分布在更多的頁中,從而加重了掃描的頁樹,也會降低查詢性能.
外部碎片:就好比我有2間居室,但不在一個屋子里。外部碎片多,則需要進行更多的跨區掃描,從而造成更多的IO操作
解決索引碎片的方法其實很簡單,也就是進行一個Rebuild Indexes的操作,做完這個操作之后統計信息會被更新,相應的執行計划中的緩存信息也會被清空,
當相同的語句再過來的時候,SQL Server就會重新進行執行計划的評估和選擇,並獲得更好的執行計划。
在產品環境中重建索引需要十分小心,原因是:
- 重建索引會消耗大量的系統I/O讀寫資源。
- 重建索引會導致查詢進程的死鎖或者鎖等待,尤其是非企業版SQL Server(企業版可以使用ONLINE選項來最大限度規避這個問題)。
- 重建索引會導致數據庫日志文件暴漲,而因此會給Database Mirroring、Log Shipping和Backup帶來壓力。
- 因為Rebuild Indexes是一個IO密集型的操作,所以會非常消耗IO,所以,請選擇業務低谷期進行索引碎片重整的操作。
對於碎片的解決辦法 :
基本上所有解決辦法都是基於對索引的重建和整理,只是方式不同
1.刪除索引並重建
這種方式並不好.在刪除索引期間,索引不可用.會導致阻塞發生。而對於刪除聚集索引,則會導致對應的非聚集索引重建兩次(刪除時重建,建立時再重建).雖然這種方法並不好,但是對於索引的整理最為有效
2.使用DROP_EXISTING語句重建索引
為了避免重建兩次索引,使用DROP_EXISTING語句重建索引,因為這個語句是原子性的,不會導致非聚集索引重建兩次,但同樣的,這種方式也會造成阻塞
3.如前面文章所示,使用ALTER INDEX REBUILD語句重建索引(DBCC DBREINDEX)
使用這個語句同樣也是重建索引,但是通過動態重建索引而不需要卸載並重建索引.是優於前兩種方法的,但依舊會造成阻塞。可以通過ONLINE關鍵字減少鎖,但會造成重建時間加長.
4.使用ALTER INDEX REORGANIZE(DBCC INDEXDEFRAG)
這種方式不會重建索引,也不會生成新的頁,僅僅是整理,當遇到加鎖的頁時跳過,所以不會造成阻塞。但同時,整理效果會差於前三種.
avg_fragmentation_in_percent:索引碎片百分比,如果碎片小於10%~20%,碎片不太可能會成為問題,如果索引碎片在20%~40%,碎片可能成為問題,但是可以通過索引重組(DROP_EXISTING)
來消除索引解決,大規模的碎片(當碎片大於40%),可能要求索引重建(DBCC DBREINDEX)。
填充因子的理解:
用來設置頁的使用情況,值:0-100 以避免頁拆分。使用填充因子會減少更新或者插入時的分頁次數,但由於需要更多的頁,則會對應的損失查找性能 ,
填充因子的概念(預留一定的空間存放插入和更新新增加的數據,以避免頁拆分)
重建索引固然可以解決碎片的問題.但是重建索引的代價不僅僅是麻煩,還會造成阻塞。影響使用.而對於數據比較少的情況下,重建索引代價並不大。而當索引本身超過百兆的時候。重建索引的時間將會很讓人蛋疼.
填充因子的作用正是如此。對於默認值來說,填充因子為0(0和100表示的是一個概念),則表示頁面可以100%使用。所以會遇到前面update或insert時,空間不足導致分頁.通過設置填充因子,可以設置頁面的使用程度:
使用填充因子會減少更新或者插入時的分頁次數,但由於需要更多的頁,則會對應的損失查找性能.
如何設置填充因子的值:
如何設置填充因子的值並沒有一個公式或者理念可以准確的設置。使用填充因子雖然可以減少更新或者插入時的分頁,但同時因為需要更多的頁,所以降低了查詢的性能和占用更多的磁盤空間.如何設置這個值進行trade-off需要根據具體的情況來看.
具體情況要根據對於表的讀寫比例來看,我這里給出我認為比較合適的值:
1.當讀寫比例大於100:1時,不要設置填充因子,100%填充
2.當寫的次數大於讀的次數時,設置50%-70%填充
3.當讀寫比例位於兩者之間時80%-90%填充
上面的數據僅僅是我的看法,具體設置的數據還要根據具體情況進行測試才能找到最優.
具體實例:
--查看表的空間
EXEC sys.sp_spaceused 'dbo.bsscost'
--獲取索引碎片的詳細信息
SELECT
DB_NAME(database_id) AS 數據庫名稱 ,OBJECT_NAME(ix.object_id) AS 表名稱 ,ix.name 索引名稱 ,avg_fragmentation_in_percent 索引的邏輯碎片,*
FROM sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('dbo.bsscost','U'),NULL,NULL,'LIMITED') AS fra CROSS APPLY sys.indexes AS ix WITH (NOLOCK) WHERE ix.object_id = fra.object_id
AND ix.index_id = fra.index_id DBCC showcontig('bsscost') WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS --DBCC SHOWCONTIG的表格版
DBCC showcontig('bsscost') --分析表的索引建立情況(文本版)
--重建表索引的語句
DBCC DBREINDEX('表名') --可設置填充因子
ALTER INDEX ALL
ON dbo.表名 REBUILD WITH (ONLINE = ON, FILLFACTOR = 90)
自動重建索引實例:
USE RJOA GO
/****** Object: StoredProcedure [dbo].[Proc_Defragment_Index] Script Date: 2018/3/12 11:44:52 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER OFF
GO
/************************************************************************************* 作用:自動重建索引(通過掃描密度的值來篩選需要重建的索引) 日期:2018-03-12 作者:mrhuang 說明“如果你想要整理那些掃描密度小於95%的索引的碎片,則執行:EXEC Proc_Defragment_Index 95.00 ************************************************************************************/
ALTER PROCEDURE [dbo].[Proc_Defragment_Index]
@maxfrag DECIMAL --鎖引的碎片掃描密度,百分比值(在此閾值之下的則進行索引重建)
AS
BEGIN
--聲明變量
SET NOCOUNT ON
DECLARE @tablename VARCHAR (128) --表名稱(已發生索引碎片)
DECLARE @execstr VARCHAR (255) --執行重建索引的語句
DECLARE @objectid INT
DECLARE @objectowner VARCHAR(255) DECLARE @indexid INT
DECLARE @frag DECIMAL --掃描密度
DECLARE @indexname CHAR(255) --索引名稱
DECLARE @dbname sysname --數據庫名稱
DECLARE @tableid INT --表id
DECLARE @tableidchar VARCHAR(255) --表名稱(用於遍歷索引碎片)
--檢查是否在用戶數據庫里運行
SELECT @dbname = db_name() IF @dbname IN ('master', 'msdb', 'model', 'tempdb') BEGIN
PRINT 'This procedure should not be run in system databases.'; RETURN; END
/***********************************************************第1階段:檢測碎片*******************************************************************/
-- 創建一個臨時表來存儲碎片信息
CREATE TABLE #fraglist ( ObjectName CHAR (255), ObjectId INT, IndexName CHAR (255), IndexId INT, Lvl INT, CountPages INT, CountRows INT, MinRecSize INT, MaxRecSize INT, AvgRecSize INT, ForRecCount INT, Extents INT, ExtentSwitches INT, AvgFreeBytes INT, AvgPageDensity INT, ScanDensity DECIMAL, BestCount INT, ActualCount INT, LogicalFrag DECIMAL, ExtentFrag DECIMAL ) --聲明游標
DECLARE tables CURSOR FOR
SELECT convert(varchar,so.id) FROM sysobjects so JOIN sysindexes si ON so.id = si.id WHERE so.type ='U' AND si.indid < 2 AND si.rows > 0 AND so.uid<>5 --因為系統表沒有權限訪問,故需要過濾掉
OPEN tables--打開游標
FETCH NEXT FROM tables INTO @tableidchar; -- 對數據庫的所有表循環執行dbcc showcontig命令
WHILE @@FETCH_STATUS = 0
BEGIN
--對表的所有索引進行統計
INSERT INTO #fraglist EXEC ('DBCC SHOWCONTIG (' + @tableidchar + ') WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS') FETCH NEXT FROM tables INTO @tableidchar
END
CLOSE tables; -- 關閉釋放游標
DEALLOCATE tables; SELECT * FROM #fraglist -- 為了檢查,報告統計結果
/*********************************************************************************************************************************************************/
/**************************************************第2階段: (整理碎片) 為每一個要整理碎片的索引聲明游標***********************************************************/
DECLARE indexes CURSOR FOR
SELECT ObjectName, ObjectOwner = user_name(so.uid), ObjectId, IndexName, ScanDensity --使用 OBJECT_ID 獲得表ID,使用 sys.indexes 獲得索引ID
FROM #fraglist f JOIN sysobjects so ON f.ObjectId=so.id WHERE ScanDensity <= @maxfrag
AND INDEXPROPERTY (ObjectId, IndexName, 'IndexDepth') > 0; SELECT 'Started defragmenting indexes at :' + CONVERT(NVARCHAR(max),GETDATE(),120) -- 輸出開始時間
OPEN indexes; --打開游標
FETCH NEXT FROM indexes INTO @tablename, @objectowner, @objectid, @indexname, @frag; --循環所有的索引
WHILE @@FETCH_STATUS = 0 BEGIN
SET QUOTED_IDENTIFIER ON; SELECT @execstr = 'DBCC DBREINDEX (' +''''+ RTRIM(@objectowner) + '.' + RTRIM(@tablename) +'''' +
', ''' + RTRIM(@indexname) + ''') WITH NO_INFOMSGS'
SELECT 'Now executing: '
SELECT(@execstr); EXEC (@execstr); SET QUOTED_IDENTIFIER OFF; FETCH NEXT FROM indexes INTO @tablename, @objectowner, @objectid, @indexname, @frag; END
CLOSE indexes; -- 關閉釋放游標
DEALLOCATE indexes; /*********************************************************************************************************************************************************/
SELECT 'Finished defragmenting indexes at :' + CONVERT(NVARCHAR(max),GETDATE(),120) --報告結束時間
DROP TABLE #fraglist --刪除臨時表
END