前言html
上幾篇文章咱們介紹瞭如何查看查詢計劃、經常使用運算符的介紹、並行運算的方式,有興趣的能夠點擊查看。性能優化
本篇將分析在SQL Server中,如何利用先有索引項進行查詢性能優化,經過了解這些索引項的應用方式能夠指導咱們如何創建索引、調整咱們的查詢語句,達到性能優化的目的。oop
閒言少敘,進入本篇的正題。post
技術準備性能
基於SQL Server2008R2版本,利用微軟的一個更簡潔的案例庫(Northwind)進行解析。學習
簡介優化
所謂的索引應用就是在咱們平常寫的T-SQL語句中,如何利用現有的索引項,再分析的話就是咱們所寫的查詢條件,其實大部分狀況也無非如下幾種:url
一、等於謂詞:select ...where...column=@parameterspa
二、比較謂詞:select ...where...column> or < or <> or <= or >= @parameter3d
三、範圍謂詞:select ...where...column in or not in or between and @parameter
四、邏輯謂詞:select ...where...一個謂詞 or、and 其它謂詞 or、and 更多謂詞....
咱們就依次分析上面幾種狀況下,如何利用索引進行查詢優化的
1、動態索引查找
所謂的動態索引查找就是SQL Server在執行語句的時候,才格式化查詢條件,而後根據查詢條件的不一樣自動的去匹配索引項,達到性能提高的目的。
來舉個例子
SET SHOWPLAN_TEXT ON GO SELECT OrderID FROM Orders WHERE ShipPostalCode IN (N'05022',N'99362')
由於咱們在表Orders的列ShipPostalCode列中創建了非彙集索引列,因此這裏查詢的計劃利用了索引查找的方式。這也是須要創建索引的地方。
咱們來利用文本的方式來查看該語句的詳細的執行計劃腳本,語句比較長,我用記事本換行,格式化查看
咱們知道這張表的該列裏存在一個非彙集索引,因此在查詢的時候要儘可能使用,若是經過索引掃描的方式消耗就比較大了,因此SQL Server儘可能想採起索引查找的方式,其實IN關鍵字和OR關鍵字邏輯是同樣的。
因而上面的查詢條件就轉換成了:
[Northwind].[dbo].[Orders].[ShipPostalCode]=N'05022'
OR
[Northwind].[dbo].[Orders].[ShipPostalCode]=N'99362'
這樣就能夠採用索引查找了,先查找第一個結果,而後再查找第二個,而這個過程在SQL Server中就被稱爲:動態索引查找。
是否是有點智能的感受了....
因此有時候咱們寫語句的時候,儘可能要使用SQL Server的這點智能了,讓其能自動的查找到索引,提高性能。
有時候恰恰咱們寫的語句讓SQL Server的智能消失,舉個例子:
--參數化查詢條件 DECLARE @Parameter1 NVARCHAR(20),@Parameter2 NVARCHAR(20) SELECT @Parameter1=N'05022',@Parameter2=N'99362' SELECT OrderID FROM Orders WHERE ShipPostalCode IN (@Parameter1,@Parameter2)
咱們將這兩個靜態的篩序值改爲參數,有時候咱們寫的存儲過程灰常喜歡這麼作!咱們來看這種方式的生成的查詢計劃
原本很簡單的一個非彙集索引查找搞定的執行計劃,咱們只是將這兩個數值沒有直接寫入IN關鍵字中,而是利用了兩個變量來代替。
看看上面SQL Server生成的查詢計劃!尼瑪...這都是些啥???還用起來嵌套循環,我就查詢了一個Orders表...你嵌套循環個啥....上面動態索引查找的能力去哪了???
好吧,咱們用文本查詢計劃來查看下,這個簡單的語句到底在幹些啥...
|--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1009], [Expr1010], [Expr1011])) |--Merge Interval | |--Sort(TOP 2, ORDER BY:([Expr1012] DESC, [Expr1013] ASC, [Expr1009] ASC, [Expr1014] DESC)) | |--Compute Scalar(DEFINE:([Expr1012]=((4)&[Expr1011]) = (4) AND NULL = [Expr1009], [Expr1013]=(4)&[Expr1011], [Expr1014]=(16)&[Expr1011])) | |--Concatenation | |--Compute Scalar(DEFINE:([Expr1004]=[@Parameter2], [Expr1005]=[@Parameter2], [Expr1003]=(62))) | | |--Constant Scan | |--Compute Scalar(DEFINE:([Expr1007]=[@Parameter1], [Expr1008]=[@Parameter1], [Expr1006]=(62))) | |--Constant Scan |--Index Seek(OBJECT:([Northwind].[dbo].[Orders].[ShipPostalCode]), SEEK:([Northwind].[dbo].[Orders].[ShipPostalCode] > [Expr1009] AND [Northwind].[dbo].[Orders].[ShipPostalCode] < [Expr1010]) ORDERED FORWARD)
挺複雜的是吧,其實我分析了一下腳本,關於爲何會生成這個計劃腳本的緣由,是爲了解決以下幾個問題:
一、前面咱們寫的腳本在IN裏面寫的是兩個常量值,而且是不一樣的值,因此造成了兩個索引值的查找經過OR關鍵字組合,
這種方式貌似沒問題,可是咱們將這兩個數值變成了參數,這就引來了新的問題,假如這兩個參數咱們輸入的是相等的,那麼利用前面的執行計劃就會生成以下
[Northwind].[dbo].[Orders].[ShipPostalCode]=N'05022'
OR
[Northwind].[dbo].[Orders].[ShipPostalCode]=N'05022'
這樣執行產生的輸出結果就是2條同樣的輸出值!...可是表裏面確實只有1條數據...因此這樣輸出結果不正確!
因此變成參數後首先解決的問題就是去重問題,2個同樣的變成1個。
二、上面變成參數,還引入了另一個問題,加入咱們兩個值有一個傳入的爲Null值,或者兩個都爲Null值,一樣輸出結果面臨着這樣的問題。因此這裏還要解決的去Null值的問題。
爲了解決上面的問題,咱們來粗略的分析一下執行計劃,看SQL Server如何解決這個問題的
簡單點將就是經過掃描變量中的值,而後將內容進行彙總值,而後在進行排序,再將參數中的重複值去掉,這樣獲取的值就是一個正確的值,最後拿這些去重後的參數值參與到嵌套循環中,和表Orders進行索引查找。
可是分析的過程當中,有一個問題我也沒看明白,就是最好的通過去重以後的常量彙總值,用來嵌套循環鏈接的時候,在下面的索引查找的時候的過濾條件變成了 and 查找
我將上面的最後的索引查找條件,整理以下:
|--Index Seek(OBJECT:([Northwind].[dbo].[Orders].[ShipPostalCode]), SEEK:
(
[Northwind].[dbo].[Orders].[ShipPostalCode] > [Expr1009]
AND
[Northwind].[dbo].[Orders].[ShipPostalCode] < [Expr1010]
) ORDERED FORWARD)
這個地方怎麼搞的?我也沒弄清楚,還望有看明白童鞋的稍加指導下....
好了,咱們繼續
上面的執行計劃中,提到了一個新的運算符:合併間隔(merge interval operator)
咱們來分析下這個運算符的做用,其實在上面咱們已經在執行計劃的圖中標示出該運算符的做用了,去掉重複值。
其實關於去重的操做有不少的,好比前面文章中咱們提到的各類去重操做。
這裏怎麼又冒出個合併間隔去重?其實緣由很簡單,由於咱們在使用這個運算符以前已經對結果進行了排序操做,排序後的結果項重複值是牢牢靠在一塊兒的,因此就引入了合併間隔的方式去處理,這樣性能是最好的。
更重要的是合併間隔這種運算符應用場景不只僅侷限於重複值的去除,更重要的是還應用於重複區間的去除。
來看下面的例子
--參數化查詢條件 DECLARE @Parameter1 DATETIME,@Parameter2 DATETIME SELECT @Parameter1='1998-01-01',@Parameter2='1998-01-04' SELECT OrderID FROM ORDERS WHERE OrderDate BETWEEN @Parameter1 AND DATEADD(DAY,6,@Parameter1) OR OrderDate BETWEEN @Parameter2 AND DATEADD(DAY,6,@Parameter2)
咱們看看這個生成的查詢計劃項
能夠看到,SQL Server爲咱們生成的查詢計劃,和前面咱們寫的語句是如出一轍的,固然咱們的語句也沒作多少改動,改動的地方就是查詢條件上。
咱們來分析下這個查詢條件:
WHERE OrderDate BETWEEN @Parameter1 AND DATEADD(DAY,6,@Parameter1)
OR OrderDate BETWEEN @Parameter2 AND DATEADD(DAY,6,@Parameter2)
很簡單的篩選條件,要獲取訂單日期在1998-01-01開始到1998-01-07內的值或者1998-01-04開始到1998-01-10內的值(不包含開始日期)
這裏用的邏輯謂詞爲:OR...其實也就等同於咱們前面寫的IN
可是咱們這裏再分析一下,你會發現這兩個時間段是重疊的
這個重複的區間值,若是用到前面的直接索引查找,在這段區間以內的搜索出來的範圍值就是重複的,因此爲了不這種問題,SQL Server又引入了「合併間隔」這個運算符。
其實,通過上面的分析,咱們已經分析出這種動態索引查找的優缺點了,有時候咱們爲了不這種複雜的執行計劃生成,使用最簡單的方式就是直接傳值進入語句中(固然這裏須要重編譯),固然大部分的狀況咱們寫的程序都是隻定義的參數,而後進行的運算。可能帶來的麻煩就是上面的問題,固然有時候參數多了,爲了合併間隔所應用的排序就消耗的內存就會增加。怎麼使用,根據場景本身酌情分析。
二、索引聯合
所謂的索引聯合,就是根據就是根據篩選條件的不一樣,拆分紅不一樣的條件,去匹配不一樣的索引項。
舉個例子
SELECT OrderID FROM ORDERS WHERE OrderDate BETWEEN '1998-01-01' AND '1998-01-07' OR ShippedDate BETWEEN '1998-01-01' AND '1998-01-07'
這段代碼是查詢出訂單中的訂單日期在1998年1月1日到1998年1月7日的或者發貨日期一樣在1998年1月1日到1998年1月7日的。
邏輯很簡單,咱們知道在這種表裏面這兩個字段都有索引項。因此這個查詢在SQL Server中就有了兩個選擇:
一、一次性的來個索引掃描根據匹配結果項輸出,這樣簡單有效,可是若是訂單表數據量比較大的話,性能就會不好,由於大部分數據就根本不是咱們想要的,還要浪費時間去掃描。
二、就是經過兩列的索引字段直接查找獲取這部分數據,這樣能夠直接減小數據表的掃描量,可是帶來的問題就是,若是分開掃描,有一部分數據就是重複的:那些同時在1998年1月1日到1998年1月7日的訂單,發貨日期也在這段時間內,由於兩個掃描項都包含,因此再輸出的時候須要將這部分重複數據去掉。
咱們來看SQL Server如何選擇
看來SQL Server通過評估選擇了第2中方法。可是上面的方法也不盡完美,採用去重操做耗費了64%的資源。
其實,上面的方法,咱們根據生成的查詢計劃能夠變通的使用如下邏輯,其效果和上面的語句是同樣的,而且生成的查詢計劃也同樣
SELECT OrderID FROM ORDERS WHERE OrderDate BETWEEN '1998-01-01' AND '1998-01-07' UNION SELECT OrderID FROM ORDERS WHERE ShippedDate BETWEEN '1998-01-01' AND '1998-01-07'
咱們再來看一個索引聯合的例子
SELECT OrderID FROM ORDERS WHERE OrderDate = '1998-01-01' OR ShippedDate = '1998-01-01'
咱們將上面的Between and不等式篩選條件改爲等式篩選條件,咱們來看一下這樣造成的執行計劃
基本相同的語句,只是咱們改變了不一樣的查詢條件,可是生成的查詢計劃仍是變化蠻大的,有幾點不一樣之處:
一、前面的用between...and 的篩選條件,經過索引查找返回的值進行組合是用的串聯的方式,所謂的串聯就是兩個數據集拼湊在一塊兒就行,無所謂順序鏈接什麼的。
二、前面的用between...and 的篩選條件,經過串聯拼湊的結果集去重的方式,是排序去重(Sort Distinct)...而且耗費了大量的資源。這裏採用了流聚合來幹這個事,基本不消耗
咱們來分析如下產生着兩點不一樣的緣由有哪些:
首先、這裏改變了篩選條件爲等式鏈接,所經過索引查找所產生的結果項是排序的,而且按照咱們所要查詢的OrderID列排序,所以在兩個數據集進行彙總的時候,正適合合併鏈接的條件!須要提早排序。因此這裏最優的方式就是採用合併鏈接!
那麼前面咱們用between...and 的篩選條件經過索引查找獲取的結果項也是排序的,可是這裏它沒有按照OrderID排序,它是按照OrderDate或者ShippedDate列排序的,而咱們的結果是要OrderID列,因此這裏的排序是沒用的......因此SQL Server只能選擇一個串聯操做,將結果匯聚到一塊兒,而後在排序了......我但願這裏我已經講明白了...
其次、關於去重操做,毫無疑問採用流聚合(Aggregate)這種方式最好,消耗內存少,速度又快...可是前提是要提早排序...前面選用的排序去重(Sort Distinct)純屬無奈之舉...
總結下:咱們在寫語句的時候能肯定爲等式鏈接,最好採用等式鏈接。還有就是若是能肯定輸出條件的最好能寫入,避免多餘的書籤查找,還有萬惡的SELEECT *....
若是寫了萬惡的SELECT *...那麼你所寫的語句基本上就能夠和非彙集索引查找告別了....頂多就是彙集索引掃描或者RID查找...
瞅瞅如下語句
SELECT * FROM ORDERS WHERE OrderDate = '1998-01-01' OR ShippedDate = '1998-01-01'
最後,奉上一個AND的一個鏈接謂詞的操做方式,這個方式被稱爲:索引交叉,意思就是說若是兩個或多個篩選條件若是採用的索引是交叉進行的,那麼使用一個就能夠進行查詢。
來看個語句就明白了
SELECT OrderID FROM ORDERS WHERE OrderDate = '1998-01-01' AND ShippedDate = '1998-03-05'
這裏咱們採用了的謂詞鏈接方式爲AND,因此在實際執行的時候,雖然兩列都存在非彙集索引,理論均可以使用,可是咱們只要選一個最優的索引進行查找,另一個直接使用書籤查找出來就能夠。省去了前面介紹的各類神馬排序去重....流聚合去重....等等不人性的操做。
看來AND鏈接符是一個很帥的運算符...因此不少時候咱們在嘗試寫OR的狀況下,不如換個思路改用AND更高效。
參考文獻
結語
此篇文章主要介紹了索引運算的一些方式,主要是描述了咱們日常在寫語句的時候所應用的方式,而且舉了幾個例子,算做拋磚引玉吧,其實咱們日常所寫的語句中無非也就本篇文章中介紹的各類方式的更改,拼湊。並且根據此,咱們該怎樣創建索引也做爲一個指導項。
下一篇咱們介紹子查詢一系列的內容,有興趣可提早關注,關於SQL Server性能調優的內容涉及面很廣,後續文章中依次展開分析。
有問題能夠留言或者私信,隨時恭候有興趣的童鞋加入SQL SERVER的深刻研究。共同窗習,一塊兒進步。
文章最後給出上幾篇的鏈接,看來有必要整理一篇目錄了.....
若是您看了本篇博客,以爲對您有所收穫,請不要吝嗇您的「推薦」。