4、分析執行計劃建立索引數據庫
根據語句的執行計劃來判斷應該對什麼表建立什麼索引,是經常使用優化技巧。其實文章前面的例子已經告訴讀者如何結合statistics profile 和statistics IO語句的輸出來建立索引。這裏分析一個稍微複雜一些的例子。函數
SQL語句以下:oop
SELECT CurrentseNo FROM v_ptdata_edss WHERE MRN = @P1 優化
Statistics IO的輸出以下:設計
Table 'ptseoutpat'. Scan count 2, logical reads 8, physical reads 0, read-ahead reads 0.索引
Table 'ptdata'. Scan count 1, logical reads 3218, physical reads 0, read-ahead reads 0.seo
部分執行計劃以下:ip
Rows Executes StmtText string
------ -------- -----------------------------------------------------------------------------------------------io
0 1 SELECT CurrentseNo FROM v_ptdata_edss WHERE MRN = @P1
0 1 |--Nested Loops(Inner Join, OUTER REFERENCES:([ptdata].[CurrentseNo]))
1 1 |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([TTSH_Neon_ADT].[dbo].[ptdata]))
1 1 | |--Filter(WHERE:(Convert([ptdata].[PatExtID])=[@P1]))
571955 1 | |--Index Scan(OBJECT:([TTSH_Neon_ADT].[dbo].[ptdata].[PK_ptdata]))
0 1 |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1009], [Expr1010], [Expr1011]))
2 1 |--Merge Interval
2 1 | |--Sort(TOP 2, ORDER BY:([Expr1012] DESC, [Expr1013] ASC, [Expr1009] ASC, [Exp
2 1 | |--Compute Scalar(DEFINE:([Expr1012]=4&[Expr1011]=4 AND NULL=[Expr1009],
2 1 | |--Concatenation
1 1 | |--Compute Scalar(DEFINE:([Expr1006]=NULL, [Expr1007]=NULL, [Ex
1 1 | | |--Constant Scan
1 1 | |--Compute Scalar(DEFINE:([Expr1009]='Jan 1 1900 12:00AM', [Ex
1 1 | |--Constant Scan
0 2 |--Index Seek(OBJECT:([TTSH_Neon_ADT].[dbo].[ptseoutpat].[ptseoutpat1]), SEEK:([pts
分析的關鍵是:
步驟1)找出最昂貴的表(也就是logical reads最多的表),是'ptdata' 表。
步驟2)從執行計劃中找出對ptdata表的相應的操做,一般是左邊行數最多的那一行如上圖中的標誌行。對錶的操做是index scan操做。
步驟3)根據操做判斷如何建立index或如何改寫語句。從執行計劃中咱們看到index scan以後的操做也就是下面的filter操做把數據大大減小了:
Filter(WHERE:(Convert([ptdata].[PatExtID])=[@P1]))
通常狀況下,對這個字段創建索引問題就解決了。但對咱們的例子語句而言還不夠。實際上PatExtID字段已經有索引了。那麼爲何用index scan而不用index seek呢? 後來發現緣由是傳遞的參數@P1和表字段PatExtID的類型是不一致的。@P1是nvarchar類型,而PatExtID是varchar類型。這致使了SQL Server 產生了對索引字段進行index scan的Convert操做。解決方法很簡單,把傳遞的參數改爲varchar或把表字段類型改爲nvarchar,使得它們類型一致就能夠了。
五.語句的寫法影響SQL Server 可否利用索引
僅僅有索引是不夠的。語句的寫法會影響SQL Server 對索引的選擇。好比下面的語句:
select 學生姓名, 入學時間 from tbl1 where DATEDIFF(mm,'20050301',入學時間)=1
理所固然,須要在入學時間字段上創建索引:
create nonclustered index idx_入學時間 on tbl1(入學時間)
而後運行以下script 5看看該索引是否有用:
/******Script 5***********************************/
set statistics profile on
set statistics io on
go
select 學生姓名, 入學時間 from tbl1 where DATEDIFF(mm,'20050301',入學時間)=1
go
set statistics profile off
set statistics io off
/*************************************************/
語句的部分輸出以下:
Table 'tbl1'. Scan count 1, logical reads 385, physical reads 0, read-ahead reads 0.
Rows Executes StmtText
----------- ----------- ----------------------------------------------------------------------
56 1 select 學生姓名, 入學時間 from tbl1 where DATEDIFF(mm,'20050301',入學
56 1 |--Table Scan(OBJECT:([tempdb].[dbo].[tbl1]), WHERE:(datediff(month,
不幸的是,是Table Scan,剛創建的索引並無被使用。這是由於WHERE語句中的DATEDIFF函數引發的。由於函數做用在索引字段上, SQL Server 沒法直接利用索引定位數據,必須對該字段全部的值運算該函數才能得知函數結果是否知足where條件。在這種狀況下,Table Scan是最好的選擇。爲了使用索引,能夠把語句改爲以下的樣子:
select 學生姓名, 入學時間 from tbl1
where 入學時間>='20050401' and 入學時間<'20050501'
把該語句替換script 5中select語句而後運行該script,結果以下:
Table 'tbl1'. Scan count 1, logical reads 58, physical reads 0, read-ahead reads 0.
Rows Executes StmtText
-----------------------------------------------------------------------------------------
56 1 SELECT [學生姓名]=[學生姓名],[入學時間]=[入學時間] FROM [tbl1] WHERE [入學時間]>=
56 1 |--Bookmark Lookup(BOOKMARK:([Bmk1000]), OBJECT:([tempdb].[dbo].[tbl1]) WITH PR
56 1 |--Index Seek(OBJECT:([tempdb].[dbo].[tbl1].[idx_入學時間]), SEEK:([tbl1].
能夠看到Table Scan變成了Index seek, Logical Reads 也減小到58。從上面的例子能夠知道,爲了利用索引,不要對where語句中的字段直接使用各類函數或表達式。要儘可能把函數或表達式放在操做符的右邊。
再多舉一些例子,下面的where語句寫法是很差的:
Where substring(colum1,1,4)>'ddd'
Where convert(varchar(200),column1)>'aaa'
若是你實在沒法避免上面的狀況,而相關的語句又是數據庫系統的關鍵語句,那麼建議你從系統設計的高度來考慮問題。比方說,改變表的結構等,使得再也不須要在where子句中的字段上直接使用函數或表達式等。
使用前置百分號或不等號也是很差的Where寫法:
Where column1 like ‘%abc%’
Where column1 <> 'bb'
第一個where語句中由於第一個百分號會致使SQL Server 進行索引掃描(index scan)或Table Scan。要儘可能不使用前置百分號。比方說改爲以下的語句就會好得多:
Where column1 like ‘abc%’
再多看一個例子:
Where column1 =2 OR column2=30
這個where語句中若是column1 和column2中任何一個字段沒有索引,那麼整條語句就會致使全表掃描。(想想爲何?)因此在有OR的where語句要特別注意OR兩邊的字段都要有必要的索引。