SQL Server自動重建索引



 
目前我們的優化主要是在數據庫層面,而索引又是重要的一環。但實質上所有的索引都將隨着時間產生碎片,從而產生性能的下降,在先前萬科的數據庫中檢查發現,幾乎所有的索引碎片都超過了90%,這會嚴重影響性能。因此,在這裏找來一些索引維護、整理方面的知識和大家一起學習。


索引需要維護的原因
        幾乎所有的UPDATE、INSERT、DELETE活動都將引起索引比最初創建時變得更無組織。頁拆分更多,大量的頁上只有很少的數據,因此滿足每個SELECT需要更多的I/O。數據和索引的碎片越多,應用程序就會更慢,數據花費的空間就更多。所以我們需要定期的維護索引。  


有什麼方法來維護? 
     基本上我們可以使用數據庫維護向導來執行索引重建,創建維護計劃來完成。但是它有一定的的缺陷。首先,用維護向導來配置和完成索引重建是不慎重的。它將重建每一個索引,不管它是否需要重建。如果你有一個有很多大表和大量索引的大數據庫,這會出問題,因爲不加區別的重建整個數據庫的索引會花費很長的時間,會使你的維護窗口不可用。問題在於,要麼全部重建,要麼全部不重建,你根本不能以任何方式分批處理數據庫的表。


    那麼有什麼別的能做嗎?可以寫一個腳本來重建選擇的表的索引。這樣你能對數據庫分批處理以減少在重建索引時你維護窗口執行的時間。你需要將這個時間減小到最少,因爲重建索引會對錶執行排它鎖,在重建索引期間禁止用戶訪問。所以你可以每週的每個工作日的晚上重建五分之一表的索引,所有的索引至少一週做一次。然而,這也是不慎重的――我們將重建所有表的索引而不論數據和索引是否是有碎片。


        因此這裏推薦選擇性的重建索引。你需要檢查表的索引和數據的碎片,保留數據,據此操作,重建索引要用確定的且區別對待的方式。僅僅通過這樣系統的方法,你可以僅重建那些實際需要重建的表的數據和索引。而且也只有這種方式能最小化索引重建的時間。在整個索引重建期間,如果你不想影響你的用戶的話,減少索引重建的時間是至關重要的。 


那麼我們怎樣可以解決呢?
可以使用命令
   DBCC SHOWCONTIG()
DBCC SHOWCONTIG是SQLServer提供來檢查索引碎片情況的工具。在以前的版本里(7.0和更早的版本),這個命令只輸出文本,如果手工處理這個命令很好,然而,要實現自動化目的,它會帶來嚴重的問題。那意味着你要循環執行每一個表並將結果輸出到文本文件,然後爲了讀和解釋原文的輸出結果以便獲得你尋找的信息,需要進行煩人的結構化處理。 
     SQLServer2000對DBCC SHOWCONTIG()命令引進了一個關鍵子句,名爲WITH TABLERESULTS。這意味着你能運行這個命令然後將捕獲的數據直接輸出到表裏,而不是還需要使用XP_CMDSHELL來操作的文本文件裏。
在SQLServer2000裏,這意味着你能結構化的循環處理表,通過在它們上面運行DBCC SHOWCONTIG命令以將捕獲碎片信息插入表中。然後你能循環使用這個結果,根據碎片的情況,選擇性的進行碎片整理。可以用下面的存儲過程實現: 


CREATE PROCEDURE sp_defragment_indexes @maxfrag DECIMAL
AS 
--聲明變量
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
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階段:檢測碎片
--聲明遊標
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


-- 創建一個臨時表來存儲碎片信息
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)


--打開遊標
OPEN tables


-- 對數據庫的所有表循環執行dbcc showcontig命令
FETCH NEXT
FROM tables
INTO @tableidchar


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
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(VARCHAR,GETDATE())
--打開遊標
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(VARCHAR,GETDATE())


-- 刪除臨時表
DROP TABLE #fraglist
GO 


使用


這個存儲過程應該創建在master數據庫裏,以便你能在服務器上的任何用戶數據庫裏使用。
在用戶數據庫裏通過傳遞一個參數(MAXFRAG)來運行。該參數是一個百分比值。意思是任何索引的碎片掃描密度小於這個值。例如,如果你想要整理那些掃描密度小於95%的索引的碎片:
USE pubs
GO
EXEC sp_defragment_indexes 95.00


侷限 
這個過程依賴於的標準是掃描密度,但掃描密度對於那些跨越多個文件的索引來說不是一個有效的標準。如果你的索引確實跨越多個文件,你需要用另一個標準(如Logical Frag)來更改這個存儲過程。然而,這類更改超過本文的範圍;如果你的索引跨越多個文件,你需要做更多的工作。 


怎樣做,做什麼? 
這個存儲過程有兩個獨特的部分。


第1階段
在這部分裏,存儲過程通過在數據庫裏的每個表上運行下面的命令來檢查索引碎片:
DBCC SHOWCONTIG (‘tablename') WITH FAST, TABLERESULTS, ALL_INDEXES, NO_INFOMSGS
命令的結果存儲在預先創建的臨時表#fraglist裏。這裏就會用到DBCC SHOWCONTIG 語句的WITH TABLERESULTS的好處,僅這一點,真正的節省了太多的以前版本得到同樣結果所花費的麻煩和精力。
你應該注意該存儲過程工作的數據庫的表的擁有者是不是dbo,通常是。我發現我最初的版本不起作用,當時一個軟件經銷商給我們提供的新系統的數據庫裏就出現了擁有者不是dbo的表。當我在這個新系統上第一次運行我的碎片整理過程時,這個程序的缺點就暴露無遺了,最後徹底失敗。這個問題實際上出現在碎片整理階段(階段2),因爲表在這裏要引用表名,而在階段1,DBCC SHOWCONTIG命令引用的時表的ID即object_id。


第2階段
這兒使用了另一個遊標來循環處理表#fraglist裏的記錄,這些記錄是那些掃描密度低於傳給存儲過程參數的那個閾值的表:
DBCC DBREINDEX()
執行的結果以輸出文件的形式顯示在表#fraglist的內容之後,以便你能查看錶和索引的碎片,正如屏幕上所顯示的那樣,也可以通過查看DBCC DBREINDEX()執行的結果列表來查看採取的動作。利用這些你也能推導出每個索引重建的時間。 


輸出結果是什麼意思? 
輸出示例:


上面是在Excel裏打開的存儲過程輸出文本文件的一個截屏。爲了簡潔一些列已經刪掉了。你需要用文本文件嚮導來打開它,選擇固定列寬,打開導入從第三行起。


這裏,你能夠檢查你選擇檢查的數據庫裏的表的掃描密度。


在接下來的輸出文件裏(DBCC SHOWCONTIG輸出結果的後面),你會發現正被重建索引的每個表或索引的細節,這部分的開始和結束部分都有重建索引的開始和結束時間。如下面例子顯示的那樣: 




爲什麼不使用DBCC INDEXDEFRAG()去減少阻塞?


答案是如果你想要或者需要的話就使用它。如果你需要7×24小時的在線操作,那麼DBCC DBREINDEX()的排他表鎖不適合你的業務,你可以使用它來代替DBCC DBREINDEX()。然而,你需要適當改變一下語法,因爲它們是不相同的(謝謝,微軟!)。如果你不知道它們的區別,這裏有一個簡單的摘要:當運行DBCC DBREINDEX()的時候,必須對錶有排他鎖,因爲它是一個完全的,徹頭徹尾的索引重建操作。而DBCC INDEXDEFRAG()就不那麼完全了,在線的操作試圖改善你索引的環境而不至於引起阻塞和中斷OLTP(希望如此)。我必須承認我從來不用DBCC INDEXDEFRAG(),因爲很幸運的是我的系統不需要嚴格的7×24在線且要求不阻塞,所以我不敢擔保是否有效率。我已經理解它不是和DBCC DBREINDEX一樣有效率。然而它的確比什麼都沒有強,所以如果你的數據庫運行一個全球的WEB站點並且從來不能停止,這在今天這也很普遍,那麼你需要使用它來代替以改變這個存儲過程。 


添加到調度任務裏 


        可以將這個維護作爲一個獨立的任務或在你存在的維護作業裏的一個步驟,讓其在每週末凌晨執行,這樣可以讓SQL自動的去維護索引,減少對用戶的影響。


結論 
       現在的數據庫維護開銷很大,本文提供的方法在影響用戶和執行時間上保持最小的同時,也提供了高效和良好的數據庫服務器維護。希望這篇文章和代碼能幫助我們在維護數據庫時起到一點作用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章