這一篇文章修修改改,已經寫了很久了,還是感覺好像自己沒講清楚,鑒於本人水平,就先這樣寫吧,待本人水平提高之后,再進行修補。
在寫作的過程也學習到了,SQL查詢優化程序也並不一定會使用查詢參數中字段的相關索引,而是根據查詢數據量的多少而產生的查詢成本,來決定是使用查詢參數中的字段索引,還是使用聚集索引或全表掃描。
中心思想就是關於SQL語句的“查詢參數”(SARG)與索引的使用。符合SARG格式的數據肯定會使用到相應的索引呢?先給出答案,不是。
例如:Select * from WBK_PDE_LIST_ORG where cop_g_no='11000' ,假設在cop_g_no上建立了非聚集索引,那么當查詢語句得出的結果數量小於某個數量閥值時,例如查詢結果的數量小於600條時,會使用到非聚集索引,但當查詢結果數量大於600條時,卻可能不會使用非聚集索引,可能會使用聚集索引或全表掃描。
在編寫SQL語句的WHERE 子句時,你是否考慮過WHERE子句中的條件參數的編寫格式要符合“ (查詢參數:SARG )”規則,SQL SERVER的查詢優化程序才能建立有效的利用索引的計划。
在進行具體分析之前,首先建立以下索引。當然索引2、3與索引4、5的名稱需要自己修改。
序號 |
索引類型 |
SQL語句 |
1 |
主鍵聚集索引 |
ALTER TABLE [dbo].[WBK_PDE_LIST_ORG_HISTROY] ADD CONSTRAINT [PK_WBK_PDE_LIST_ORG_HISTROY] PRIMARY KEY CLUSTERED( [WBOOK_NO] ASC, [G_NO] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] |
2 |
非聚集索引(無INCLUDE) |
CREATE NONCLUSTERED INDEX [idx_WBK_PDE_LIST_QTY1] ON [dbo].[WBK_PDE_LIST_ORG_HISTROY] ( [QTY_1] ASC ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON , ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] |
3 |
CREATE NONCLUSTERED INDEX [idx_WBK_PDE_LIST_COP_G_NO] ON [dbo].[WBK_PDE_LIST_ORG_HISTROY] ( [COP_G_NO] ASC ) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] |
|
4 |
非聚集索引(有INCLUDE) |
CREATE NONCLUSTERED INDEX [idx_WBK_PDE_LIST_QTY1] ON [dbo].[WBK_PDE_LIST_ORG_HISTROY] ( [QTY_1] ASC ) INCLUDE ( [WBOOK_NO],[G_NO],[CODE_T],[COP_G_NO],[UNIT_1],[TRADE_TOTAL],[GROSS_WT]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF , IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] |
5 |
CREATE NONCLUSTERED INDEX [idx_WBK_PDE_LIST_COP_G_NO] ON [dbo].[WBK_PDE_LIST_ORG_HISTROY] ( [COP_G_NO] ASC ) INCLUDE ( [WBOOK_NO],[G_NO],[CODE_T],[QTY_1],[UNIT_1],[TRADE_TOTAL],[GROSS_WT]) WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] |
|
Index Seek 運算符利用索引的查找功能從非聚集索引中檢索行。
Index Scan 運算符從 Argument 列中指定的非聚集索引中檢索所有行。如果可選的 WHERE:() 謂詞出現在 Argument 列中,則只返回滿足該謂詞的那些行。
Clustered Index Scan 運算符會掃描查詢執行計划的 Argument 列中指定的聚集索引。如果出現可選 WHERE:()謂詞,則只返回滿足該謂詞的行。
Clustered Index Seek 運算符可以利用索引的查找功能從聚集索引中檢索行。Argument 列包含所使用的聚集索引名稱和 SEEK:() 謂詞。存儲引擎僅使用索引來處理滿足此 SEEK:() 謂詞的行。它還包括 WHERE:() 謂詞,其中存儲引擎對滿足 SEEK:() 謂詞的所有行進行計算,但此操作是可選的,並且不使用索引來完成此過程。
Table Scan 運算符從查詢執行計划的 Argument 列所指定的表中檢索所有行。如果 WHERE:()謂詞出現在 Argument 列中,則僅返回滿足此謂詞的那些行。
Filter 運算符掃描輸入,僅返回那些符合 Argument 列中的篩選表達式(謂詞)的行。
Inner Join 邏輯運算符返回滿足第一個(頂端)輸入與第二個(底端)輸入所組成的聯接的每一行。
SQL Server 2005 Service Pack 2 中引入的 Key Lookup 運算符是在具有聚集索引的表上進行的書簽查找。Argument 列包含聚集索引的名稱和用來在聚集索引中查找行的聚集鍵。
RID Lookup 是在使用提供的行標識符 (RID) 在堆上進行的書簽查找。Argument 列包含用於查找行的書簽標簽和從中查找行的表的名稱。RID
1. 有效地查詢參數
得到相同查詢結果的SQL語句的寫法有很多種,那么應該如何決定采用哪種SQL語句編寫方式比較有用呢?最重要的考慮因素之一是WHERE 條件子句, WHERE子句限制了查詢所要返問的記錄數量,查詢優化程序會嘗試判斷己有的索引,分析對查找符合WHERE子句條件的記錄是否有幫助。
查詢優化程序首先就要查看WHERE 子句中所有的條件,以決定這些條件在限制SQL SERVER 訪問數據時是否有用。換句話說,查詢子句是否有用要看查詢參數(Searchable Arguments , SARG〕
很多人不知道SQL語句在SQL SERVER中是如何執行的,他們擔心自己所寫的SQL語句會被SQL SERVER誤解。比如:
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where QTY_1>53 and COP_G_NO='90206884'
和執行:
SELECT *
FROM [WBK_PDE_LIST_ORG_HISTROY] where COP_G_NO='90206884' and QTY_1>53
一些人不知道以上兩條語句的執行效率是否一樣,因為如果簡單的從語句先后上看,這兩個語句的確是不一樣,如果QTY_1是一個非聚集索引,那么前一句僅僅從QTY_1大於53的記錄中查找就行了;而后一句則要先從全表中查找看有幾個COP_G_NO='90206884'的,而后再根據限制條件條件QTY_1>53來提出查詢結果。
事實上,這樣的擔心是不必要的。SQL SERVER中有一個“查詢分析優化器”,它可以根據WHERE子句中的搜索條件進行自動優化,建立有效的索引使用計划。
上面兩句的IO情況是一樣的,都是250次邏輯讀取操作。具體執行結果如下:
(61 行受影響)
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取250 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
(61 行受影響)
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取250 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。
從Managemenet studio中可以看出上面兩句的查詢執行計划都是一樣的。如下圖。
所以上面兩句的執行效率是一樣的。
雖然查詢優化器可以根據WHERE子句自動的進行查詢優化,但大家仍然有必要了解一下“查詢優化器”的工作原理,我們有時會以查詢參數這個名詞來泛指在WHERE 子句中所有的條件,但此處使用SARG縮寫來代表查詢參數的有效格式。在大多數狀況下,查詢優化程序只能對符合SARG 條件的WHERE子句通過索引找到優化的執行方式。如果一個階段可以被用作一個掃描參數(SARG),那么就稱之為可優化的,並且可以利用索引快速獲得所需數據。
SARG的定義:用於限制搜索的一個操作,因為它通常是指一個特定的匹配,一個值得范圍內的匹配或者兩個以上條件的AND連接。SARG 包含常量描述式(或是可以解析成常量的變量)來與數據表中的字段做比較。SARG 的格式是:
列名 操作符 <常數 或 變量>
或
<常數 或 變量> 操作符 列名
列名出現在操作符的一邊,而常量或變量出現在另一邊。如果列名同時出現在操作的兩邊就不算是SARG。
SARG包含以下操作符=、>、<、>=、<=、BETWEEN及部分情況下的LIKE。LIKE是否符合SARG,要看通配符%所在的位置。例如:LIKE '胡%'就是符合SARG,但是'%胡'就不符合SARG。因為以通配符開頭無法限制SQL SERVER查詢記錄的數量,索引的擺放依然是以小到大,或以大到小順序排列,如果以通配符“%”開頭就無法利用有序的結構,以二分法來快速查找數據。
簡言之,在查詢子句中,SARG代表用來查找的常量或變量可以直接與索引鍵值進行比較,下面是一些常用SARG與執行索引的關系。
序號 | 索引 |
SQL語句與查詢執行計划 | 記錄數 | 執行成本 |
1 |
索引4 |
SELECT [WBOOK_NO] ,[COP_G_NO] ,[G_NO],[CODE_T] ,[QTY_1],[UNIT_1],[TRADE_TOTAL] ,[GROSS_WT] FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1=1 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取29 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
29 |
0.0267688 |
||
2 |
索引1 |
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1=1 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取1314 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
1314 |
1.03687 |
||
|
||||
3 |
索引4 |
SELECT [WBOOK_NO],[COP_G_NO],[G_NO],[CODE_T],[QTY_1] ,[UNIT_1],[TRADE_TOTAL] ,[GROSS_WT] FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1>=312 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取29 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
29 |
0.0268468 |
||
4 |
索引1 |
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1>=312 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取1314 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
1314 |
1.03687 |
||
5 |
索引4 |
SELECT [WBOOK_NO],[COP_G_NO],[G_NO],[CODE_T] ,[QTY_1],[UNIT_1],[TRADE_TOTAL] ,[GROSS_WT] FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1<2 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取29 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
29 |
0.026875 |
||
6 |
索引1 |
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1<2 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取1314 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
1314 |
1.03687 |
||
7 |
索引4 |
SELECT [WBOOK_NO],[COP_G_NO] ,[G_NO],[CODE_T] ,[QTY_1],[UNIT_1],[TRADE_TOTAL] ,[GROSS_WT] FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1!>1 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取29 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
29 |
0.026875 |
||
8 |
索引1 |
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where qty_1!>1 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取1314 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
1314 |
1.03687 |
||
9 |
索引2 |
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where QTY_1 between 412 and 500 |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取1021 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
1021 |
0.959746 |
||
10 |
索引3 |
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where cop_g_no like '80215%' |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取320 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
||||
|
320 |
0.316824 |
||
11 |
索引3 |
SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where cop_g_no like '802%' SELECT * FROM [WBK_PDE_LIST_ORG_HISTROY] where cop_g_no like '%21%' |
||
表'WBK_PDE_LIST_ORG_HISTROY'。掃描計數1,邏輯讀取1314 次,物理讀取0 次,預讀0 次,lob 邏輯讀取0 次,lob 物理讀取0 次,lob 預讀0 次。 |
1314 |
1.03687 |
||
|
SQL SERVER查詢分析優化器對於每一條查詢語句的WHERE子句進行評估,看是使用索引的查詢成本還是使用聚集索引掃描的查詢成本低。
從上表中我們可以看出根據不同的查詢語句與不同的查詢字段,會使用不同的索引,如果當查詢出來的記錄數比較多時,也就是超過了直接使用聚集索引掃描或全表掃描查詢出來的數據時,即使WHERE子名是SARG格式的寫法,他也將使用放棄使用相應的索引,而使用全表掃描與聚集索引掃描(例如上表中的2,4,6,8,11)。
使用索引 |
查詢語句 |
查詢記錄數量 |
執行成本 |
索引2 |
9 |
1021 |
0.959746 |
索引3 |
10 |
320 |
0.316824 |
索引4 |
1,3,5,7 |
29 |
0.026875 |
索引5 |
|||
索引1 |
2,4,6,8,11 |
1314 |
1.03687 |