SQL Server 效能優化之——重複索引
1. 概述
很多人都知道索引在資料庫上的是有利有弊的。像其他主流商業資料庫一樣SQL Server允許在一個列上重複建立索引。因為SQL Server沒有限制建立重複索引的數量,只是限制資料庫的一個表上最多可以建立999重複索引,所以這就增加了資料庫中存在重複索引的可能性。表的列上存在重複索引的話,可能會明顯的損害資料庫效能,因為SQL Server必須分別維護每一個重複索引。此外,SQL Server優化查詢語句時,查詢優化器也會考慮這個問題,這就導致一系列效能問題。要理解什麼事實重複索引、怎麼樣找到它們、怎麼樣移除它們。
2. 什麼是重複索引
首先假設有一個表Test_Table有四個列(Col1, Col2, Col3, Col4)
CREATE TABLE Test_TableCREATE TABLE Test_Table
(
Col1 int NOT NULL PRIMARY KEY,
Col2 varchr(30) NOT NULL,
Col3 varchr(30) NOT NULL,
Col4 varchr(30) NOT NULL,
)
1) 在主鍵列上建立不同型別的索引
1: CREATE UNIQUE CLUSTERED INDEX IX1 ON Test_Table(Col1); 2: 3: CREATE INDEX IX2 ON Test_Table(Col1);
2) 在非主鍵列上建立不同順序的包含列的索引
1: CREATE INDEX IX3 ON Test_Table (Col4)
2: INCLUDE (Col2, Col3);
3:
4: CREATE INDEX IX4 ON Test_Table (Col4)
5: INCLUDE (Col3, Col2);
3) 在非主鍵列上建立相同順序包含列的索引
1: CREATE INDEX IX5 ON Test_Table (Col4) 2: INCLUDE (Col2, Col3); 3: 4: CREATE UNIQUE INDEX IX6 ON Test_Table (Col4) 5: INCLUDE (Col2, Col3);
4) 在不同非主鍵列建立不同順序的索引
1: CREATE INDEX IX7 ON Test_Table (Col3, Col2);
2:
3:
CREATE INDEX IX8 ON Test_Table (Col3, Col2);
這樣重複的索引,在執行DML操作(插入、更新、刪除)的時候需要更新索引。
3. 查詢重複索引
一般不會有人特意建立重複索引。有時候,神不知鬼不覺的建立了,有時候 是因為建立新的索引是沒有檢查當前列是否已經存在索引。那麼怎麼樣才能它們暴露來呢?
1) 使用SQL Server Management Studio (SSMS,但是在SQL Server有很多資料庫,資料庫中又有大量表和索引的情況下,使用SSMS並不是一個快捷的方式。 2) 使用sp_helpindex查詢重複索引 3) 使用SQL Server系統目錄,可以在SQL Server資料庫上使用和開發指令碼查詢重複索引,這是一個比較方便並靈活的方式。
SQL系統目錄: a. sys.indexes:包括表格物件(例如,表、檢視或表值函式)的索引或堆的每一行 b. sys.objects:在資料庫中建立的每個使用者定義的架構作用域內的物件在該表中均對應一行。 c. sys.index_columns:屬於 sys.indexes 索引或未排序的表(堆)的每個列都對應一行。 d. sys.columns:返回包含列物件(如檢視或表)的列的每一行 下面是包含列物件型別的表: a) 表值程式集函式 (FT) b) 內聯表值 SQL 函式 (IF) c) 內部表 (IT) d) 系統表 (S) e) 表值 SQL 函式 (TF) f) 使用者表 (U) g) 檢視 (V)
有一種是列出所有索引在哪個表上面,它們被掃描多少次,被更新多少次,在記憶體中的大小, 這些對我們有用的資訊
ViewSELECT
sch.name + '.' + t.name AS [Table Name],
i.name AS [Index Name],
i.type_desc,
ISNULL(user_updates,0) AS [Total Writes],
ISNULL(user_seeks + user_scans + user_lookups,0) AS [Total Reads],
s.last_user_seek,
s.last_user_scan ,
s.last_user_lookup,
ISNULL(user_updates,0) - ISNULL((user_seeks + user_scans + user_lookups),0) AS [Difference],
p.reserved_page_count * 8.0 / 1024 as SpaceInMB
FROM sys.indexes AS i WITH (NOLOCK)
LEFT OUTER JOIN sys.dm_db_index_usage_stats AS s WITH (NOLOCK) ON s.object_id = i.object_id AND i.index_id = s.index_id AND s.database_id=db_id() AND objectproperty(s.object_id,'IsUserTable') = 1
INNER JOIN sys.tables AS t WITH (NOLOCK) ON i.object_id = t.object_id
INNER JOIN sys.schemas AS sch WITH (NOLOCK) ON t.schema_id = sch.schema_id
LEFT OUTER JOIN sys.dm_db_partition_stats AS p WITH (NOLOCK) ON i.index_id = p.index_id and i.object_id = p.object_id
WHERE (1=1)
--AND ISNULL(user_updates,0) >= ISNULL((user_seeks + user_scans + user_lookups),0) --顯示包含沒有使用的約束在內的所有約束
--AND ISNULL(user_updates,0) - ISNULL((user_seeks + user_scans + user_lookups),0)>0 --僅僅顯示那些已經使用的索引
--AND i.index_id > 1 -- 非第一索引
--AND i.is_primary_key<>1 -- 不是作為主鍵被定義的
--AND i.is_unique_constraint<>1 -- 不是UniqueConstraints
ORDER BY [Table Name], [index name]
還有一種是基於列查詢重複索引
View/* 執行這個指令碼後,索引將會以三個報表的實行展現出來
請看下面:
1. 列出所有索引和約束的關鍵資訊
2. 列出表潛在的冗餘索引
3. 列出表潛在的反向索引
*/
-- 建立一個存放索引資訊的表
DECLARE @AllIndexes TABLE (
[Table ID] [int] NOT NULL,
[Schema] [sysname] NOT NULL,
[Table Name] [sysname] NOT NULL,
[Index ID] [int] NULL,
[Index Name] [nvarchar](128) NULL,
[Index Type] [varchar](12) NOT NULL,
[Constraint Type] [varchar](11) NOT NULL,
[Object Type] [varchar](10) NOT NULL,
[AllColName] [nvarchar](2078) NULL,
[ColName1] [nvarchar](128) NULL,
[ColName2] [nvarchar](128) NULL,
[ColName3] [nvarchar](128) NULL,
[ColName4] [nvarchar](128) NULL,
[ColName5] [nvarchar](128) NULL,
[ColName6] [nvarchar](128) NULL,
[ColName7] [nvarchar](128) NULL,
[ColName8] [nvarchar](128) NULL,
[ColName9] [nvarchar](128) NULL,
[ColName10] [nvarchar](128) NULL
)
-- 載入索引資訊到下面語句
INSERT INTO @AllIndexes
([Table ID],[Schema],[Table Name],[Index ID],[Index Name],[Index Type],[Constraint Type],[Object Type]
,[AllColName],[ColName1],[ColName2],[ColName3],[ColName4],[ColName5],[ColName6],[ColName7],[ColName8],
[ColName9],[ColName10])
SELECT o.[object_id] AS [Table ID] ,u.[name] AS [Schema],o.[name] AS [Table Name],
i.[index_id] AS [Index ID]
, CASE i.[name]
WHEN o.[name] THEN '** Same as Table Name **'
ELSE i.[name] END AS [Index Name],
CASE i.[type]
WHEN 1 THEN 'CLUSTERED'
WHEN 0 THEN 'HEAP'
WHEN 2 THEN 'NONCLUSTERED'
WHEN 3 THEN 'XML'
ELSE 'UNKNOWN' END AS [Index Type],
CASE
WHEN (i.[is_primary_key]) = 1 THEN 'PRIMARY KEY'
WHEN (i.[is_unique]) = 1 THEN 'UNIQUE'
ELSE '' END AS [Constraint Type],
CASE
WHEN (i.[is_unique_constraint]) = 1
OR (i.[is_primary_key]) = 1
THEN 'CONSTRAINT'
WHEN i.[type] = 0 THEN 'HEAP'
WHEN i.[type] = 3 THEN 'XML INDEX'
ELSE 'INDEX' END AS [Object Type],
(SELECT COALESCE(c1.[name],'') FROM [sys].[columns] AS c1 INNER JOIN [sys].[index_columns] AS ic1
ON c1.[object_id] = ic1.[object_id] AND c1.[column_id] = ic1.[column_id] AND ic1.[key_ordinal] = 1
WHERE ic1.[object_id] = i.[object_id] AND ic1.[index_id] = i.[index_id]) +
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 2) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id],2) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id], 3) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],3) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id], 4) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],4) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 5) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],5) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id], 6) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],6) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id], 7) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id], 7) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],8) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],8) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 9) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],9) END +
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 10) IS NULL THEN ''
ELSE ', '+INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],10) END AS [AllColName],
(SELECT COALESCE(c1.[name],'') FROM [sys].[columns] AS c1 INNER JOIN [sys].[index_columns] AS ic1
ON c1.[object_id] = ic1.[object_id] AND c1.[column_id] = ic1.[column_id] AND ic1.[key_ordinal] = 1
WHERE ic1.[object_id] = i.[object_id] AND ic1.[index_id] = i.[index_id]) AS [ColName1],
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 2) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],2) END AS [ColName2],
CASE
WHEN INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id], 3) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],3) END AS [ColName3],
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 4) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],4) END AS [ColName4],
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 5) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],5) END AS [ColName5],
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 6) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],6) END AS [ColName6],
CASE
WHEN INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id], 7) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],7) END AS [ColName7],
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id],8) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],8) END AS [ColName8],
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 9) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],9) END AS [ColName9],
CASE
WHEN INDEX_COL('[' + u.[name] + '].['+ o.[name] + ']', i.[index_id], 10) IS NULL THEN ''
ELSE INDEX_COL('[' + u.[name] + '].[' + o.[name] + ']', i.[index_id],10) END AS [ColName10]
FROM [sys].[objects] AS o WITH (NOLOCK)
LEFT OUTER JOIN [sys].[indexes] AS i WITH (NOLOCK)
ON o.[object_id] = i.[object_id]
JOIN [sys].[schemas] AS u WITH (NOLOCK)
ON o.[schema_id] = u.[schema_id]
WHERE o.[type] = 'U' --AND i.[index_id] < 255
AND o.[name] NOT IN ('dtproperties')
AND i.[name] NOT LIKE '_WA_Sys_%'
-----------
SELECT 'Listing All Indexes' AS [Comments]
SELECT I.*
FROM @AllIndexes AS I
ORDER BY [Table Name]
-----------
SELECT 'Listing Possible Redundant Index keys' AS [Comments]
SELECT DISTINCT I.[Table Name], I.[Index Name] ,I.[Index Type], I.[Constraint Type], I.[AllColName]
FROM @AllIndexes AS I
JOIN @AllIndexes AS I2
ON I.[Table ID] = I2.[Table ID]
AND I.[ColName1] = I2.[ColName1]
AND I.[Index Name] <> I2.[Index Name]
AND I.[Index Type] <> 'XML'
ORDER BY I.[Table Name], I.[AllColName]
----------
SELECT 'Listing Possible Reverse Index keys' AS [Comments]
SELECT DISTINCT I.[Table Name], I.[Index Name], I.[Index Type], I.[Constraint Type], I.[AllColName]
FROM @AllIndexes AS I
JOIN @AllIndexes AS I2
ON I.[Table ID] = I2.[Table ID]
AND I.[ColName1] = I2.[ColName2]
AND I.[ColName2] = I2.[ColName1]
AND I.[Index Name] <> I2.[Index Name]
AND I.[Index Type] <> 'XML'
4. 刪除重複索引
把它們暴露出來,剩下的事情就很簡單了,刪除。
1: USE test_table;
2: GO
3: --從表Test_Tabler刪除索引 IX2
4: DROP IX2
5: ON Test_Tabler
6: GO
5. 總結
設計資料庫查詢語句時,需要相當的留意重複索引可能引起DML操作的效能降低。設計新資料庫之前最好檢查一下已有資料庫的索引。在自己的資料庫發現重複索引,明智的選擇就是果斷刪除它,刪除之前最好還是先做資料庫備份,這樣可以避免刪除後對資料庫造成重大影響。其實,刪除重複索引不僅能提高效能而且可以給資料庫瘦身,同時備份檔案也會變小。