SQL Server調優系列玩轉篇(如何利用查詢提示(Hint)引導語句運行)
前言
前面幾篇我們分析了關於SQL Server關於性能調優的一系列內容,我把它分為兩個模塊。
第一個模塊註重基礎內容的掌握,共分7篇文章完成,內容涵蓋一系列基礎運算算法,詳細分析了如何查看執行計劃、掌握執行計劃優化點,並一一列舉了日常我們平常所寫的T-SQL語句所會應用的運算符。我相信你平常所寫的T-SQL語句在這幾篇文章中都能找到相應的分解運算符。
第二個模塊註重SQL Server執行T-SQL語句的時候一些內幕解析,共分為5篇文章完成,其中包括:查詢優化器的運行方式、運行時幾個優化指標值檢測,統計信息、利用索引等一系列內容。通過這塊內容讓我們了解SQL Server為我們所寫的T-SQL語句如何進行優化及運行的。
從本篇進入第三個模塊的內容,該篇為第一篇,該模塊主要讓我們來指導SQL Server進行定向調整,達到優化的目的。本模塊的內容是以前面一系列內容為前提的,希望充分掌握了前面基礎內容,方能進入本模塊內容。
技術準備
數據庫版本為SQL Server2012,利用微軟的以前的案例庫(Northwind)進行分析,部分內容也會應用微軟的另一個案例庫AdventureWorks。
相信了解SQL Server的朋友,對這兩個庫都不會太陌生。
概念理解
談到hint,其實概念很簡單,正如詞義理解:提示,也就是說讓我們通過給予SQL Server提示(hint)讓數據庫運行時按照我們的思路進行,我估計很多不怎麽了解SQL Server的童鞋都不怎麽知道,因為一般應用的不多。
其實,SQL Server本身的查詢優化器已經做到很好了,所以大部分情況下不需要我們人工幹預,自己就能運行的很好,並且最大限度的優化運行項。但是,俗話說:老虎也有打盹的時候,所以,在有些場景下,就需要我們來給數據庫指導一個方向,讓其運行的更流暢。
但是,記住了:你所應用的hint是在現在的場景中基於現有的環境下,相對是一個好的方式,不能確保你所給予的提示(Hint)永久有效,並且隨著時間推移,數據量的變更,你所發出的提示(Hint)有可能會成為數據庫優化的絆腳石。所以沒有充分的把握不要輕易使用Hint,並且最好采用目標導向Hint。
Hint主要分為三類應用:查詢Hint、表Hint、連接Hint。查詢Hint影響整個查詢,主要應用於查詢語句優化,本篇主要分析查詢Hint。
表Hint影響查詢引用的單個表,而連接Hint影響一個單獨的連接。
Hint應用方式分為兩類:目標導向Hint和物理運算符Hint。
目標導向Hint傳遞邏輯的目標給優化器,而不會具體指定優化器應該如何達到這個目標,應該使用什麽物理運算符,或者如何排列這些運算符。所以這種運算符使我們所推薦的,原因很簡單:我告訴丫按照這個思路執行就可以,至於怎麽達到,自己想辦法!這種方式從長期看對於數據庫的影響會小很多。
另外一個就是物理運算符,此方式就更直接了:直接告訴丫的步驟,你按照這個去做就行。這種方式不推薦,原因很簡單:你的思路暫時會是好的,但是過段時間就不好了。
一、查詢提示(Hint)
首先,查詢提示(Hint)是我們在調優中應用最廣泛的,因為大部分時間我們是在調整查詢的性能。
關於查詢中的優化選項就是在指導SQL Server的連接類型、聚合類型、聯合類型等物理連接運算符。關於此塊的詳細解析,可以參照我調優系列中前幾篇文章,分析的相當的詳細。
a、FAST N Hint提示
關於此方式的提示,我在前面的文章中已經有使用到,在介紹索引那篇文章中,可以點擊這裏查看。
首先,這個Hint是一個目標導向hint。提示目標很簡單:告訴數據庫給我速度出前N行數據就可以,而其它的數據你愛咋地咋地。
這個提示最優的應用環境就是:應用系統中的分頁查詢,當然其它環境可以用。有點類似於SELECT TOP N....
其次,在我們的應用環境中,尤其數據量多的情況下,如果這時候我們的場景是:我想速度的看到前面的部分數據,其它的數據你可以稍後再顯示,但是在執行T-SQL的時候,SQL Server會多方面的考慮耗費(cost),然後再平衡各種利弊選擇出它認為相對好的執行計劃去執行,顯然這種方式獲取數據的方式是很浪費的,並且速度就會相對慢很多。
所以,我們利用FAST N Hint提示,這樣,SQL Server會阻止優化器使用哈希連接、哈希聚合、排序、甚至是並行這些大消耗的動作,而轉變成為這N條數據做快速的優化並輸出。這在大數據量的情況下,是一種非常高明的方式。
來個例子:
SELECT OrderID,CustomerID,OrderDate FROM Orders ORDER BY OrderDate
簡單的查詢,並且按照OrderDate排序,不看執行計劃,我們就已經推測出這個執行計劃中最耗損的就是這個OrderDate了,排序永遠是高耗損,這也是為什麽各種類型的索引都要提前排序的原因。
然後,我們再來看一下加上這個FAST N Hint提示的執行
SELECT OrderID,CustomerID,OrderDate FROM Orders ORDER BY OrderDate OPTION(FAST 1)
為了快速獲取這一行數據,利用HINT後,改為了索引掃描+書簽查找,因為這是獲取一條數據的最優的一種方式。
因為數據量的關系,所以我上述演示沒能很好的表現出FAST 提示的優越性來,其實在實際生產中,在面臨龐大的數據量的時候,一般利用FAST N提示獲取出部分數據之後,就不再繼續運行了,因為我們關註的就是這一部分數據。
當然,此HINT也有弊端:在快速獲取前N行結果之後,可能會延遲整個查詢的總體相應時間。也就是說,盡管FAST N HINT可能會使優化器快速產生前N個輸出計劃。但是它會使優化器產生一個在結束最後一行前花費更多時間,消耗更多CPU,甚至於更多IO。
b、OPTIMIZE FOR Hint提示
此HINT是一種非常有用的提示,也是我們在日常中經常使用的。
這個HINT目標很簡單:告訴優化器目標以Hint值進行分配或者執行。此Hint提示是從SQL Server2005版本以上開始支持,能夠根據指定的參數值產生一個計劃,尤其適用於非對稱數據集中,因為這種數據集中數據分布不均勻,不同的參數值可能導致不同的基數評估和不同的查詢計劃,我們可以從不同的參數中選擇一個最優的執行計劃,作為後續不同參數的執行計劃,避免了SQL Server的重新評估和重編譯的耗費的動作。
來個例子:
SELECT OrderID,OrderDate FROM Orders WHERE ShipPostalCode=N‘51100‘
此語句很簡單,就是通過查詢郵政編碼(ShipPostalCode),獲取出訂單ID和訂單日期。
來看這個查詢語句,最理想的情況就是直接通過索引查找(index seek)動作獲取出數據。其實最好的方式也是通過INCLUDE將兩列值包含進去。
我們來看一下實際的執行計劃:
SQL Server通過了索引查找+書簽查找方式獲取,這種方式也湊合吧,其實我們還可以繼續優化。
但是,這不是問題重點,問題重點是該段T-SQL一般我們會利用參數進行查詢或者包裝成存儲過程通過傳參調用。是吧??不會你永遠只查詢一個固定值吧....來看語句
DECLARE @ShipPostalCode NVARCHAR(50) SET @ShipPostalCode=N‘51100‘ SELECT OrderID,OrderDate FROM Orders WHERE [email protected]
是吧,這種方式才能做到重用嘛,不過包裝成一個存儲過程或者一個函數等,估計核心代碼肯定就這樣子了。
來看看生成的執行計劃:
本來很爽的非聚集索引查找(Seek),通過我加了一個參數之後變成了聚集索引掃描(Scan)了,聚集索引掃描的性能跟表掃描基本一樣,沒有啥質的提高!
如果該表數據量特別大的話,我們為該語句設計的非聚集索引就失效了。只能通過依次掃描獲取數據了。有意思嗎???沒意思!!!
怎麽解決呢?這就是我們此處提到Hint出場的時候了,告訴數據庫:丫就按照執行 “51100” 的查詢一樣去執行我傳過來的參數。
DECLARE @ShipPostalCode NVARCHAR(50) SET @ShipPostalCode=N‘51100‘ SELECT OrderID,OrderDate FROM Orders WHERE [email protected] OPTION(OPTIMIZE FOR( @ShipPostalCode=N‘51100‘))
看到了,這裏又回歸了快速的非聚集索引查找(Seek)狀態,並且不受限制於傳過來的參數是啥。
這個提示只是告訴SQL Server查詢按照這個目標值進行操作,並不會實際影響結果值。
當然上面的問題,如果封裝成存儲過程的時候,可以采用重編譯的方式解決,但是相比利用Hint的方式,重編譯帶來的消耗遠大的多。尤其高並發的環境下重編譯所帶來CPU消耗是非常高的。
c、物理連接提示(Hint)
關於物理連接我們在前面的文章中已經詳細的分析了,在SQL Server中共分為三種物理連接方式:嵌套循環、合並、哈希連接。
詳細的內容可以參照我的基礎篇中的鏈接:SQL Server調優系列基礎篇(常用運算符總結——三種物理連接方式剖析)
文章中對三種連接的利弊進行了詳細的對比,並且對三種連接的使用環境進行了詳細的介紹。但是,有時候SQL Server為我們評估的連接並不是最優的,或者說並不是符合我們的要求,這時候,就要利用我們的物理連接提示進行指導。
總共分為三種查詢級別的連接Hint,正好對應三種物理連接運算符,依次是:LOOP JOIN、MERGE JOIN 和 HASH JOIN
在應用時候,可以指定一個或者多個,如果指定一個,那麽查詢計劃中的全部連接使用指定的連接類型,如果指定兩個,SQL Server會在這兩個連接類型中選擇最好的一個,也就是斃掉了第三個。
應用場景蠻多的,根據三種連接的特性,我們可以有選擇的進行提示,比如我們想一個查詢不消耗內存,那麽就可以指定OPTION(LOOP JOIN,MERGER JOIN),這樣就去掉消耗內存的哈希連接,當然這是減小內存消耗但會增加執行時間。如果采用了合並連接(MERGER JOIN)方式不會消耗內存,但是合並連接需要提前排序(sort),排序會消耗大量的內存。
當然,有時候嵌套循環連接執行的時間不理想,就可以指定為哈希連接(hash join)進行連接。
來看個例子:
SELECT o.OrderID FROM Customers C JOIN Orders O ON C.CustomerID=O.CustomerID WHERE C.City=N‘London‘
上面的查詢計劃采用了嵌套循環的連接方式,兩張表依次進行循環嵌套執行。
如果,經過測試這裏發現采用合並連接的方式更好一點,我們可以采用如下Hint進行提示操作
SELECT o.OrderID FROM Customers C JOIN Orders O ON C.CustomerID=O.CustomerID WHERE C.City=N‘London‘ OPTION(MERGE JOIN)
經過調整之後,這時候該語句就利用到了我們設計的非聚集索引,並且由原來的索引SCAN變成了索引Seek運算。
通過如下方式,可以指導SQL Server在哈希連接和合並連接之間做出選擇,但是一定要放棄嵌套循環連接。
SELECT o.OrderID FROM Customers C JOIN Orders O ON C.CustomerID=O.CustomerID WHERE C.City=N‘London‘ OPTION(HASH JOIN,MERGE JOIN)
看以看到,經過評估SQL Server還是依然的選擇了合並連接
其實,這個很正常,首先數據量不大,其次是在City列上存在非聚集索引,所以要充分利用,並且在兩張表的CustomerID是都為索引所覆蓋,這就保證了兩張表在這列上都是預先排序(sort)了,這完全滿足了合並連接的條件。當然,默認選擇嵌套循環連接的原因,我估計的原因就一個:兩張表數據量不大。
當然,出來上面的HINT方式可以指定連接的物理連接方式,還有另外更為粗暴的一種方式,強制執行。如下:
SELECT o.OrderID FROM Customers C INNER MERGE JOIN Orders O ON C.CustomerID=O.CustomerID WHERE C.City=N‘London‘
當然,這種方式也手動的達到了指定采用合並連接的方式。
但是,此種方式有嚴重的弊端:
1、通過采用這種方式貌似暫時解決問題了,但是經過一段時間,此連接方式可能會嚴重阻礙數據庫的優化,而要解決此問題就不得不更改代碼。
2、只能粗暴的指定一種物理連接方式,不能順應SQL Server本身自己的優化策略。
上述的方式是非常不推薦的一種,大部分新手會選擇這種方式。
當然,利用Hint的方式是並非一種萬全之策,但在當前基本能解決問題,當運行到一段周期之後,如果當前的HINT幹預了SQL Server數據庫的正常運行,我們也可以采用適當的方式予以停用Hint。使數據庫得到完美的平穩的正常運行。後續文章我們依次介紹。
關於Hint這塊的使用,內容還是挺多的,其中一部分還包含鎖提示等,後續文章我們依次介紹,有興趣的童鞋提前關註。
其實Hint是平常我們調優時候一種重要的工具。但是,這個工具的正確的使用則要依靠牢靠的基礎知識掌握和經驗累積。正所謂:厚積薄發! 不要輕易的看到了使用場景就妄自的進行盲目的使用。如果使用不當,還會擾亂SQL Server數據庫本身正常的生態環境,得不償失,越調越亂。
所以:施主,三思而行呀......
參考文獻
- 微軟聯機叢書邏輯運算符和物理運算符引用
- 參照書籍《SQL.Server.2005.技術內幕》系列
結語
此篇文章先到此吧,關於SQL Server調優工具Hint的使用還有很多內容,後續依次介紹,有興趣的童鞋可以提前關註。
有問題可以留言或者私信,隨時恭候有興趣的童鞋加入SQL SERVER的深入研究。共同學習,一起進步。
文章最後給出前面幾篇的連接,以下內容基本涵蓋我們日常中所寫的查詢運算的分解以及調優內容項,皆為原創,看來有必要整理一篇目錄了.....
SQL Server調優系列基礎篇
SQL Server調優系列基礎篇(常用運算符總結)
SQL Server調優系列基礎篇(聯合運算符總結)
SQL Server調優系列基礎篇(並行運算總結)
SQL Server調優系列基礎篇(並行運算總結篇二)
SQL Server調優系列基礎篇(索引運算總結)
SQL Server調優系列基礎篇(子查詢運算總結)
-----------------以下進階篇-------------------
SQL Server調優系列進階篇(查詢優化器的運行方式)
SQL Server調優系列進階篇(查詢語句運行幾個指標值監測)
SQL Server調優系列進階篇(深入剖析統計信息)
SQL Server調優系列進階篇(如何索引調優)
SQL Server調優系列進階篇(如何維護數據庫索引)
SQL Server調優系列玩轉篇(如何利用查詢提示(Hint)引導語句運行)