SQL Server調優系列進階篇(如何維護數據庫索引)

前言html

上一篇咱們研究瞭如何利用索引在數據庫裏面調優,簡要的介紹了索引的原理,更重要的分析瞭如何選擇索引以及索引的利弊項,有興趣的能夠點擊查看。sql

本篇延續上一篇的內容,繼續分析索引這塊,側重索引項的平常維護以及一些注意事項等。數據庫

閒言少敘,進入本篇的主題。併發

技術準備app

數據庫版本爲SQL Server2012,前幾篇文章用的是SQL Server2008RT,內容區別不大,利用微軟的之前的案例庫(Northwind)進行分析,部份內容也會應用微軟的另外一個案例庫AdventureWorks。post

相信瞭解SQL Server的朋友,對這兩個庫都不會太陌生。性能

 

1、建立索引學習

當咱們要開始對錶進行索引的建立的時候,首先明確的是,一張表內只能建立一個彙集索引,最多能夠建立最多249個非彙集索引(SQL Server2005),在SQL Server2008之後彙集索引數提高至999個,上一篇文章咱們知道對於彙集索引項通常要建立上,而非彙集索引項要根據平常的T-SQL語句進行選擇。測試

關於索引的選擇是一個很考驗調優能力的事情,大部分的狀況下優質的索引新建全靠經驗而論,有興趣的能夠點擊查閱我前面的一系列關於分析查詢計劃的文章,掌握住裏面的精髓纔能有的放矢。優化

固然,小白級別的也能夠參照以下方法嘗試進行建立:

因爲SQL Server有着本身的一套調優技巧,因此在咱們每次運行的T-SQL語句應該怎樣優化,SQL Server是瞭如指掌的,因此它會將缺失的索引項進行記錄,用於提示使用者,嘗試去創建這些索引。

主要記錄在如下幾個DMV中

sys.dm_db_missing_index_details

sys.dm_db_missing_index_groups

sys.dm_db_missing_index_group_stats

sys.dm_db_missing_index_columns(index_handle)

sys.dm_db_missing_index_details

 

關於這些個DMV的使用,來舉一個例子:

--新建表,創建主鍵,造成彙集索引
CREATE TABLE BigTable
(
   [KEY] INT,
   DATA INT,
   PAD CHAR(200),
   CONSTRAINT [PK1] PRIMARY KEY ([KEY])
)
GO
--批量插入測試數據250000行
SET NOCOUNT ON 
DECLARE @i INT
BEGIN TRAN
    SET @i=0
    WHILE @i<250000
    BEGIN
       INSERT BigTable VALUES(@i,@i,NULL)
       SET @i=@i+1
       IF @i%1000=0
       BEGIN
          COMMIT TRAN
          BEGIN TRAN
       END
END    
COMMIT TRAN
GO

利用這個測試腳本,咱們新建了一張測試表,而且插入了一些測試數據,運行一個查詢

SELECT [KEY],[DATA]
FROM BigTable
WHERE DATA<1000
GO

在這個簡單的查詢腳本中,SQL Server已經提示了咱們須要建立的索引項。咱們能夠右鍵,直接生成建立腳本

SQL Server已經提示咱們要建立的索引項內容了,穿件一個非彙集索引在列DATA上,而且INCLUDE列KEY,而且經建立完這個索引後的提高值都給計算出來了。

以上這種方式,在咱們調優的時候是常用的,在咱們拿到須要優化的語句後,直接執行就能夠看到一部分須要調整的信息了。

可是,大部分的T-SQL語句不容許咱們進行這樣的優化流程,甚至有時候是已經存在的系統。因此,咱們下手的方式只能繞道了,幸虧SQL Server爲咱們記錄下了這些缺失索引項的信息,就存在我上面提到的幾個DMV中。咱們來查看下:

SELECT migs.group_handle, mid.* 
FROM sys.dm_db_missing_index_group_stats AS migs 
INNER JOIN sys.dm_db_missing_index_groups AS mig 
ON (migs.group_handle = mig.index_group_handle) 
INNER JOIN sys.dm_db_missing_index_details AS mid 
ON (mig.index_handle = mid.index_handle) 
WHERE migs.group_handle = 2

因此,大部分狀況下,經過查看以上語句基本能確認到須要建立的索引項有哪些。

提示:可是,這裏的DMV信息只是記錄自上次SQL Server啓動之後的信息項,也就是說每次重啓以後這部分信息就丟失了,因此對於生產系統,建議確保運行了一段週期以後再進行查看。

知道了應該建立什麼樣的索引,下一步就是建立索引了,來看建立索引的腳本

CREATE [ UNIQUE ] [ CLUSTERED | NONCLUSTERED ] INDEX index_name 
    ON <object> ( column [ ASC | DESC ] [ ,...n ] ) 
    [ INCLUDE ( column_name [ ,...n ] ) ]
    [ WHERE <filter_predicate> ]
    [ WITH ( <relational_index_option> [ ,...n ] ) ]
    [ ON { partition_scheme_name ( column_name ) 
         | filegroup_name 
         | default 
         }
    ]
    [ FILESTREAM_ON { filestream_filegroup_name | partition_scheme_name | "NULL" } ]

建立腳本很簡單,指定索引類型、索引名稱、所屬表、包含列、篩選項、所屬文件組以及操做項就能夠建立了。
我相信基本搞過SQL Server數據的這塊腳本通常不會陌生。

固然,若是不熟悉腳本的方式,SQL Server也默認給提供了圖形化操做界面,傻瓜式操做

 

這裏咱們重點分析幾點注意事項。

  • UNIQUE:

該關鍵字指定索引項爲惟一值,也就是非重複值,在實際應用中很是的有用,應爲惟一就意味着這個索引的高選擇性,也就意味着當前索引的可用性高低。

前面文章已經分析了SQL Server會默認的在主鍵列上建立彙集索引,也是利用了主鍵的非空和惟一性特色。

固然,這裏也提示下彙集索引要求的就是惟一性,若是當前列確實存在重複值,那在建立彙集索引的時候SQL Server會默認的在當前列上加上一個惟一標識符(uniqueifiter)在內部來保證索引的惟一性。但這個時候就不須要顯式的指定UNIQUE了,不然會報以下錯誤:

  • CLUSTERED|NONCLUSTERED:

這個就是指定建立的索引爲彙集仍是非彙集索引。

關於它,這裏有幾點須要注意,由於非彙集索引的葉子節點存儲的就是彙集索引鍵值,因此在建立順序上要保證優先建立彙集索引,然後再建立非彙集索引,保證有足夠的存儲空間來存放非彙集索引。

在咱們從新建立彙集索引的時候,SQL Server會默認的從新生成所有非彙集索引,若是表數據量特別大,這個過程會很漫長,若是不指定ONLINE的話,這個過程會是鎖定索引B-Teee的,這就意味着是阻塞的,業務就要停下來等待完成操做,切記不要將此事發生在生產機上。

固然,以上問題是能夠避免的。 

  • index_name:索引的名字。
  • column :

建立索引所選的列了,提示下: 不能將大型對象 (LOB) 數據類型 ntext、text、varchar(max)、 nvarchar(max)、varbinary(max)、xml 或 image 的列指定爲索引的鍵列。 另外,即便 CREATE INDEX 語句中並未引用 ntext、text 或 image 列。若是想用這些類型的列能夠存放於INCLUDE裏面。

  • INCLUDE:

索引包含列,這個關鍵字很是有用,尤爲在應對T-SQL的隨機IO問題上,具體內容可參照我前面的一系列的文章介紹。

還有前面提到的那些大型對象(LOB)數據類型,也能夠包含進去,不過這裏有一點須要提示下,若是包含了大型對象,則建立索引不支持在線(ONLINE)操做,這就意味着必須選擇非業務器進行操做。

  • PAD_INDEX = { ON | OFF }|FILLFACTOR =fillfactor

這個兩個選項是爲了設置填充因子使用的,也是咱們在建立索引的時候最經常使用的。

關於填充因子的做用簡單點講就是爲了減小分頁而在索引空間中提早先預留空間。咱們知道對於彙集索引在葉級別就包含了數據,因此用戶在這裏能夠指定每一個葉子保留的空間的大小,經過預留空間,就能夠避免用戶新的數據填充而產生分頁現象,產生索引碎片影響性能。

固然,關於填充因子的內容支撐,是須要一部分基礎知識的,有興趣的能夠點擊此參照聯機叢書的官方介紹。

索引默認的的選項是OFF,也就是說基本不會預留太多空間。

關於這裏填充因子設置的數值大小問題,其實沒有一個固定的值,純粹是一個經驗值,來自於系統的場景和長期運行的總結。固然,若是非要給出的話,能夠參照以下進行設置:

    1.當讀寫比例大於100:1時,不要設置填充因子,100%填充

    2.當寫的次數大於讀的次數時,設置50%-70%填充

    3.當讀寫比例位於二者之間時80%-90%填充

可是,這個值並非被SQL Server所維護的,也就是說在這部分預留空間填滿以後,後者改數據頁刪除部分數據以後,仍是會產生索引碎片,因此在系統運行過一段週期以後,咱們須要手動的去從新整理索引,來維護好索引的秩序,維護方式也就是:從新建立,從新組織等。文章後面的會介紹。

  • SORT_IN_TEMPDB = { ON | OFF }

這個就是指定當前索引排序是否要藉助TempDB庫,默認值爲OFF。若是想快速的生成索引請將此選項指定爲ON,固然弊端就是會擴大TempDB的大小,若是原表數據量特別多的話,這可能會是一個很大的空間值。

  • STATISTICS_NORECOMPUTE = { ON | OFF}

這個指定是否同時更新統計信息。默認是開啓的。我知道統計信息的重要性,因此在建立的時候不要更改此值。

  • DROP_EXISTING = { ON | OFF }

刪除或重建的時候是否從新生成已經命名先前存在的彙集或非彙集索引。默認是OFF。

這個選項很是的有用。刪除或者重建索引的時候整個流程是做爲一個事務來處理的。因此,一般狀況下,若是打算重建一個彙集索引的時候,須要先刪除彙集索引,然後再新創建一個,可是這個流程中,在刪除的時候SQL Server必須重建每個非彙集索引將每個非彙集索引的葉子節點有彙集索引鍵改爲RID,而後新建過程,在重複的將全部的每個非彙集索引的葉子節點由RID鍵更改爲新的彙集索引鍵值。

這就是須要重建非彙集索引兩次,若是表數據量特別大的話,這個時間消耗就會很長很長...並且是阻塞的....

可是若是指定DROP_EXISTING選項爲ON的話,就能夠在建立或者刪除的時候只須要一次更改全部非彙集索引就能夠。固然此方式也能夠經過ALTER INDEX作到,後面分析。

  •  ONLINE = { ON | OFF }

是否在線提供索引建立,此方式也是數據庫的在05版本之後新添加的一大亮點,提供了在線狀態下索引的建立,可是僅限於 Enterprise版本。

若是在生產系統中,業務併發時期能夠採用這個選項進行索引的建立及維護,但相對離線建立的時間週期要明顯長不少,可是不會形成業務停機。

若是深刻研究此方式的底層原理,其實就是數據的快照隔離機制,簡單點將就是在建立索引的時候,將相應的數據行提供了版本控制,避免了和正常業務系統的鎖爭用從而避免了阻塞,屬於樂觀鎖機制原理。

  • MAXDOP = max_degree_of_parallelism

 設置並行計劃的數量值。這個選項也頗有用,若是是非業務高發期,能夠適當調高此值來並行進行索引的建立,加快索引的建立速度。

固然,也受限於物理的CPU核數。還有就是此功能也只有 Enterprise版提供。

  • ALLOW_ROW_LOCKS = { ON | OFF }|ALLOW_PAGE_LOCKS = { ON | OFF }

 此方式指定是否行鎖或者頁鎖,固然,只因此索引的建立和修改大部分狀況下須要離線操做,就是由於在索引建立的時候加鎖了。爲了加快索引的生成就必須添加相應的鎖。

若是 ALLOW_ROW_LOCKS = ON 且 ALLOW_PAGE_LOCK = ON,則訪問索引時容許行級、頁級和表級鎖。數據庫引擎將選擇相應的鎖,而且能夠將鎖從行鎖或頁鎖升級到表鎖。

若是 ALLOW_ROW_LOCKS = OFF 且 ALLOW_PAGE_LOCK = OFF,則訪問索引時僅容許使用表級鎖。

一個有用的索引的建立須要耐心的建立出來,切勿草率的魯莽進行,若是操做不當有可能還會產生更多意外的狀況。因此要充分把握好數據的特性,合理的建立好每個有用的索引。

2、索引管理

通過上面一步的索引的建立,其實在平常的大部分時間就須要維護好索引。關於索引的維護基本就集中在如下幾個方面

a、索引的重建

當咱們發現索引索引覆蓋範圍不夠或者存在大量索引鎖片,影響性能的時候,咱們就須要對索引進行重建。

索引範圍的問題其實大部分來源於對於T-SQL語句性能的把握,也就是咱們前面幾篇文章中分析的須要調優的內容項。

而關於索引碎片的造成,也是源於數據庫長時間的運行,大量的增刪該查形成了B-Tree結構的不許確,確切的說是不能正確的提供平衡查詢的性能,或者大量的數據分頁形成索引碎片,進而增大了IO,影響了性能。

 

關於索引碎片的查看,能夠經過如下DMV語句進行 

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT 
DB_NAME() AS DatbaseName 
, SCHEMA_NAME(o.Schema_ID) AS SchemaName 
, OBJECT_NAME(s.[object_id]) AS TableName 
, i.name AS IndexName 
, ROUND(s.avg_fragmentation_in_percent,2) AS [Fragmentation %] 
INTO #TempFragmentation 
FROM sys.dm_db_index_physical_stats(db_id(),null, null, null, null) s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id 
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?]; 
INSERT INTO #TempFragmentation 
SELECT TOP 20 
DB_NAME() AS DatbaseName 
, SCHEMA_NAME(o.Schema_ID) AS SchemaName 
, OBJECT_NAME(s.[object_id]) AS TableName 
, i.name AS IndexName 
, ROUND(s.avg_fragmentation_in_percent,2) AS [Fragmentation %] 
FROM sys.dm_db_index_physical_stats(db_id(),null, null, null, null) s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id 
WHERE s.database_id = DB_ID() 
AND i.name IS NOT NULL 
AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0 
ORDER BY [Fragmentation %] DESC' 
SELECT top 20 * FROM #TempFragmentation ORDER BY [Fragmentation %] DESC 
DROP TABLE #TempFragmentation

看到了,這部分索引的碎片到大了99%...這就須要咱們重建進行維護了,不然將嚴重拖垮數據的性能。

維護的方式也就主要集中在如下幾種:

一、重建索引

這種方式簡單高效也就是咱們上面分析的CREATE INDEX 命令後面加上DROP_EXISTING方式。固然能夠聯機操做,操做方式參考文章前面

二、修改索引

這種方式是05版本之後才提供的,簡單點將就是ALTER INDEX命令進行。其實底層的運行方式同索引重建,只不過這種方式更改的選項多一些。

三、索引重組

這種方式就是從新填充索引裏面的數據,對於解決索引碎片的方式不如前面兩種來的直接。不過也是一種推薦的方式,由於此方式在運行的時候,也是隨時中止。

不像前面兩種方式爲原子性操做,而且業務阻塞。

 

b、索引的禁用

關於索引的禁用,這個功能也是SQL Server2005版本之後纔出現的新功能,這個功能通常應用的很少。

由於大部分狀況下將索引禁用了,還倒不如直接將索引刪除掉來的直接。

可是,記住了既然SQL Server設計了它就是有它的用武之地的。

不少狀況下,數據庫在運行很長一段時間以後,會發生壞頁的狀況。而若是經過命令查找,發現損壞也處於索引項上,那麼你所作的操做就是禁用這個索引(記住只能是禁用)

而後從新創建一個新索引就能夠了。

在這種狀況下咱們可選的最快處理方式就是禁用該索引,由於一旦發生壞頁的狀況,該索引項是不容許刪除的。

 

不少朋友就好奇了,索引來了個禁用,那我何時啓用呢?.......

.嘿嘿...一旦問出了此問題,就說明了你對數據庫的理解還很淺...基本上還算沒有入門了......一旦索引禁用就意味着這個因此再也不維護更新了....再也不維護更新了那它裏面的數據就是過期的或者說不許確的...那還啓用它幹嗎...與其啓用還不如從新維護一個呢...

 

關於數據庫壞頁的狀況,能夠參照我前面寫的一篇文章,點擊此

 

c、索引的刪除

關於索引的刪除,就不須要太多的介紹了,緣由很簡單,索引的存在會影響數據插入數據的速度,而且在查詢的時候須要維護等多的鎖,進而影響併發。

因此,一旦索引存在着一點優化的做用沒有,咱們就要及時的刪除掉,由於百害而無一利嘛。

查看未使用的索引DMV腳本以下:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT 
DB_NAME() AS DatbaseName 
, SCHEMA_NAME(O.Schema_ID) AS SchemaName 
, OBJECT_NAME(I.object_id) AS TableName 
, I.name AS IndexName 
INTO #TempNeverUsedIndexes 
FROM sys.indexes I INNER JOIN sys.objects O ON I.object_id = O.object_id 
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?]; 
INSERT INTO #TempNeverUsedIndexes 
SELECT 
DB_NAME() AS DatbaseName 
, SCHEMA_NAME(O.Schema_ID) AS SchemaName 
, OBJECT_NAME(I.object_id) AS TableName 
, I.NAME AS IndexName 
FROM sys.indexes I INNER JOIN sys.objects O ON I.object_id = O.object_id 
LEFT OUTER JOIN sys.dm_db_index_usage_stats S ON S.object_id = I.object_id 
AND I.index_id = S.index_id 
AND DATABASE_ID = DB_ID() 
WHERE OBJECTPROPERTY(O.object_id,''IsMsShipped'') = 0 
AND I.name IS NOT NULL 
AND S.object_id IS NULL' 
SELECT * FROM #TempNeverUsedIndexes 
ORDER BY DatbaseName, SchemaName, TableName, IndexName 
DROP TABLE #TempNeverUsedIndexes

固然,這些記錄都是自動SQL Server啓動以來不曾使用的索引,因此在生產系統中,必定要確保已經運行了一段週期了。

索引腳本的刪除,很簡單和表刪除相似,直接drop掉就能夠了。

固然,最後再贈送一個DMV,查看那些常常被大量更新,可是卻基本不適用的索引項

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED 
SELECT 
DB_NAME() AS DatabaseName 
, SCHEMA_NAME(o.Schema_ID) AS SchemaName 
, OBJECT_NAME(s.[object_id]) AS TableName 
, i.name AS IndexName 
, s.user_updates 
, s.system_seeks + s.system_scans + s.system_lookups 
AS [System usage] 
INTO #TempUnusedIndexes 
FROM sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id 
WHERE 1=2 
EXEC sp_MSForEachDB 'USE [?]; 
INSERT INTO #TempUnusedIndexes 
SELECT TOP 20 
DB_NAME() AS DatabaseName 
, SCHEMA_NAME(o.Schema_ID) AS SchemaName 
, OBJECT_NAME(s.[object_id]) AS TableName 
, i.name AS IndexName 
, s.user_updates 
, s.system_seeks + s.system_scans + s.system_lookups 
AS [System usage] 
FROM sys.dm_db_index_usage_stats s 
INNER JOIN sys.indexes i ON s.[object_id] = i.[object_id] 
AND s.index_id = i.index_id 
INNER JOIN sys.objects o ON i.object_id = O.object_id 
WHERE s.database_id = DB_ID() 
AND OBJECTPROPERTY(s.[object_id], ''IsMsShipped'') = 0 
AND s.user_seeks = 0 
AND s.user_scans = 0 
AND s.user_lookups = 0 
AND i.name IS NOT NULL 
ORDER BY s.user_updates DESC' 
SELECT TOP 20 * FROM #TempUnusedIndexes ORDER BY [user_updates] DESC 
DROP TABLE #TempUnusedIndexes

關於這些腳本,就要本身酌情考慮是否刪除了,不能一律而論。

 

索引的知識實在是太普遍..我經過兩篇文章進行此塊內容的分析,關於如何利用索引能夠參照我上一篇文章....點擊此....

..此篇先到此吧...文章寫的有點糙....

關於調優內容太普遍,咱們放在之後的篇幅中介紹,有興趣的能夠提早關注。

結語 

有問題能夠留言或者私信,隨時恭候有興趣的童鞋加入SQL SERVER的深刻研究。共同窗習,一塊兒進步。

 

文章最後給出前面幾篇的鏈接,如下內容基本涵蓋咱們平常中所寫的查詢運算的分解以及調優內容項,皆爲原創,看來有必要整理一篇目錄了.....

SQL Server調優系列基礎篇

SQL Server調優系列基礎篇(經常使用運算符總結)

SQL Server調優系列基礎篇(聯合運算符總結)

SQL Server調優系列基礎篇(並行運算總結)

SQL Server調優系列基礎篇(並行運算總結篇二)

SQL Server調優系列基礎篇(索引運算總結)

SQL Server調優系列基礎篇(子查詢運算總結)

-----------------如下進階篇-------------------

SQL Server調優系列進階篇(查詢優化器的運行方式)

SQL Server調優系列進階篇(查詢語句運行幾個指標值監測)

SQL Server調優系列進階篇(深刻剖析統計信息)

SQL Server調優系列進階篇(如何索引調優)

 

若是您看了本篇博客,以爲對您有所收穫,請不要吝嗇您的「推薦」。 

相關文章
相關標籤/搜索