SQL Server調優系列基礎篇(子查詢運算總結)

前言html

前面咱們的幾篇文章介紹了一系列關於運算符的介紹,以及各個運算符的優化方式和技巧。其中涵蓋:查看執行計劃的方式、幾種數據集經常使用的鏈接方式、聯合運算符方式、並行運算符等一系列的咱們常見的運算符。有興趣的童鞋能夠點擊查看。數據庫

本篇咱們介紹關於子查詢語句的一系列內容,子查詢通常是咱們造成複雜查詢的一些基礎性操做,因此關於子查詢的應用方式就很是重要。併發

廢話少說,開始本篇的正題。dom

技術準備工具

數據庫版本爲SQL Server2008R2,利用微軟的一個更簡潔的案例庫(Northwind)進行分析。oop

1、獨立的子查詢方式post

所謂的獨立的子查詢方式,就是說子查詢和主查詢沒有相關性,這樣帶來的好處就是子查詢不依賴於外部查詢,因此能夠獨立外部查詢而被評估,造成本身的執行計劃執行。性能

舉個例子學習

SELECT O1.OrderID,O1.Freight
FROM Orders O1
WHERE O1.Freight>
(
   SELECT AVG(O2.Freight)
   FROM Orders O2
)

這句SQL執行的目標是查詢訂單中運費大於平均運費數的訂單。優化

這裏提取平均運費的子句就是一個徹底獨立的子查詢,徹底不依賴主查詢而獨立執行。同時這裏咱們這裏利用利用一個標量計算(AVG),所以正好返回一行。

查看一下該語句的查詢計劃:

這個查詢計劃沒啥好介紹的,關於子查詢的執行計劃造成能夠參照個人第二篇:SQL Server調優系列基礎篇(經常使用運算符總結)

不過這裏須要提示一下就是,關於流聚合和計算標量造成的結果值(AVG)只包含一個結果值,因此該語句能正常的執行。

 

咱們再來看另一種狀況

SELECT O.OrderID
FROM Orders O
WHERE O.CustomerID=
(
   SELECT C.CustomerID
   FROM Customers C
   WHERE C.ContactName=N'Maria Anders'
)

該語句的也是獲取名字爲'Maria Anders'的顧客有多少訂單。這句T-SQL語句可否執行的前提是在顧客表裏存不存在同名的「'Maria Anders'」顧客,若是存在同名狀況,該語句就不能正確執行,而若是恰巧只有一名顧客爲'Maria Anders',則能正常執行。

咱們來分析一下對於這種執行的時候才能判斷可否正確執行的SQL Server如何判斷的

在這裏出現了一個新的運算符,名字是:斷言。咱們用文本執行計劃來查看一下,這個運算符的主要功能是什麼

 

通過上面的分析,咱們已經分析出了上面的「斷言」運算符的做用,由於咱們的子查詢語句不能保證返回的結果爲一行,因此,這裏引入了一個斷言運算符來作判斷。

因此,斷言的做用就是根據下文的條件,判斷子查詢句的查詢結果是否知足主語句的查詢要求。

若是,斷言發現子語句不知足,就會直接報錯,好比上面的Expr1005>1

而且,斷言運算符還常常用來檢測其它條件是否知足,好比:約束條件、參數類型、值長度等。

其實,這裏斷言要解決的問題就是判斷咱們的篩選條件中ContactName中的值是否存在重複值的,對於這種判斷相對性能消耗仍是比較小的,有時候對於別的複雜的斷言操做須要消耗大量資源,因此咱們就能夠根據適當狀況狀況避免斷言操做。

好比,上面的語句咱們能夠明確的告訴SQL Server在表Customers中ContactName列就不存在重複值,它就不須要斷言了。咱們在上面創建一個:惟1、非彙集索引實現

CREATE UNIQUE INDEX ContactNameIndex ON Customers(ContactName)
GO
SELECT O.OrderID
FROM Orders O
WHERE O.CustomerID=
(
   SELECT C.CustomerID
   FROM Customers C
   WHERE C.ContactName=N'Maria Anders'
)
drop index Customers.ContactNameIndex
GO

通過咱們惟一非彙集索引的提示,SQL Server已經明確的知道咱們的子查詢語句不會返回多行的狀況,因此就去掉了斷言操做。

 

2、相關的子查詢方式

相比上面的獨立子查詢方式,這裏的相關的子查詢方式相對複雜點,就是咱們的子查詢依賴於主查詢的的結果,對於這種子查詢就不能單獨執行。

咱們來看個這樣的子查詢例子

SELECT O1.OrderID
FROM Orders O1
WHERE O1.Freight>
(
   SELECT AVG(O2.Freight)
   FROM Orders O2
   WHERE O2.OrderDate<O1.OrderDate
)

這個語句就是返回以前訂單中運費量大於平均值的頂點編號。

語句很簡單的邏輯,可是這裏面的子查詢就依賴於主查詢的結果項,篩選條件中 WHERE O2.OrderDate<O1.OrderDate,因此這個子查詢就不能獨立運行。

咱們來看一下這個語句的執行計劃

 

這裏的查詢計劃有出現了一個新的運算符:索引假脫機。

其實,關於索引假脫機的做用主要是用於子查詢的獨立運行,由於咱們知道這裏的子查詢的查詢條件是依賴於主查詢的,因此,這裏想運行的話就的先提早獲取出主查詢的結果項,而這裏獲取的主查詢的結果項須要一箇中間表來暫存,這裏暫存的工具就是:(索引池)Index Spool,而對這個索引池的操做,好比:新建、增長等操做就是上面咱們所標示的「索引假脫機」了。

索引假脫機分爲兩種:Eager Spool和Lazy Spool,其實簡單點講就是需不須要馬上將結果存入Index Spool裏面,仍是經過延遲操做。

而這裏造成的索引池(Index Spool)是存放於系統的臨時庫Tempdb中。

咱們經過文本查詢計劃,來分析下兩個索引假脫機裏面的值是什麼

 

通過上面的分析,咱們已經看到了,裏面的Eager Spool是和主查詢比較造成的結果值,由於這個必需要及時的造成,以便於子查詢的進行,因此它的類型爲Eager Spool,

而子查詢外面的那個Index Spool爲Lazy Spool,這個結果項的保存不須要那麼及時了,這個保存的就是子查詢的造成的結果項了,就是相對每一個訂單運費的平均值。

我上面的分析,但願各位看官能看懂了。

其實,關於這個Index Spool的設計的目的,徹底爲了就是提高性能,由於咱們知道上面的查詢語句每一個子查詢的進行,都必須回調主查詢的結果,因此爲了不每次都回調,就採用了Index Spool進行暫存,而這個Index Spool存儲的位置就是Tempdb,因此Tempdb運行的快慢直接關乎這種查詢語句的性能。

這也是咱們爲何強調大併發的數據庫搭建,建議將Tempdb庫單獨存放於高性能的硬件環境中。

 

曬曬聯機叢書中關於假脫機數據運算符官方介紹:

Index Spool 物理運算符在 Argument 列中包含 SEEK:() 謂詞。Index Spool 運算符掃描其輸入行,將每行的副本放置在隱藏的假脫機文件(存儲在 tempdb 數據庫中且只在查詢的生存期內存在)中,併爲這些行建立非彙集索引。這樣可使用索引的查找功能來僅輸出那些知足 SEEK:() 謂詞的行。

若是重繞該運算符(例如經過 Nested Loops 運算符重繞),但不須要任何從新綁定,則將使用假脫機數據,而不用從新掃描輸入。

 

跟索引脫機相似的還有一個類似的運算符:表脫機,其功能相似,表脫機存儲的應該是鍵值列,而表脫機則是存儲的是多列數據了。

來看例子

SELECT O1.OrderID,O1.Freight
FROM Orders O1
WHERE O1.Freight>
(
   SELECT AVG(O2.Freight)
   FROM Orders O2
   WHERE O2.CustomerID=O1.CustomerID
)

這個查詢和上面的相似,只不過是查詢的同一個客戶加入的超過全部訂單運費平均值的訂單。

此語句一樣不是獨立的子查詢語句,每一個子查詢的結果的造成都須要依賴主查詢的結果項,爲了加快速度,提高性能,SQL Server會將主表查詢的的結果項暫存到一張臨時表中,這個表就被稱爲表脫機

咱們來看這句話的執行計劃:

 

這裏就用到了一個表脫機的運算符,這個運算符的做用就是用來暫存後面掃描獲取的結果集合,用於下面的子查詢的應用

這個表脫機造成的結果項也是存儲到臨時庫Tempdb中,因此它的應用和前面提到的索引脫機相似。

上面的執行計劃中,還提到了一個新的運算符:段(Segment)

這個運算符的解釋是:
Segment 既是一個物理運算符,也是一個邏輯運算符。它基於一個或多個列的值將輸入集劃分紅多個段。這些列顯示爲 Segment 運算符中的參數。而後運算符每次輸出一個段。

其實做用就是將結果進行彙總整理,將相同值匯聚到一塊兒,跟排序同樣,只不過這裏能夠對多列值進行匯聚。

 

咱們再來看一個例子,加深 一下關於段運算的做用

SELECT CustomerID,O1.OrderID,O1.Freight
FROM Orders O1
WHERE O1.Freight=
(
   SELECT MAX(O2.Freight)
   FROM Orders O2
   WHERE O2.CustomerID=O1.CustomerID
)

這個語句查詢的是:每一個顧客所產生的最大運費的訂單數據。

以上語句,若是理解起來有難度,咱們能夠變通如下的相同邏輯的T-SQL語句,相同的邏輯

SELECT O1.CustomerID,O1.OrderID,O1.Freight 
FROM Orders O1
INNER JOIN
(
    SELECT CustomerID,max(Freight) Freight
    FROM Orders
    GROUP BY CustomerID
) AS O2
ON O1.CustomerID=O2.CustomerID
AND O1.Freight=O2.Freight

先根據客戶編號分組,而後獲取出最大的運費項,再關聯主表獲取訂單信息。

以上兩種語句生成的相同的查詢計劃:

 

這裏咱們來解釋一下,SQL Server的強大之處,也是段運算符使用的最佳方式。

原本這句話要實現,按照邏輯須要有一個嵌套循環鏈接,參照上面的方式,使用表脫機的方式進行數據的獲取。

可是,咱們這句話獲取的結果項是每一個顧客的最大運費的訂單明細項,並且CustomerID列做爲輸出項,因此這裏採用了,先按照運費列(Freight)排序,

而後採用段運算符進行將每一個顧客相同的數據匯聚到一塊兒,而後再輸出每一個顧客的前一列(TOP 1)獲取的就是最每一個顧客的運費最大的訂單項。

省去了任何的表假脫機、索引假脫機、關聯鏈接等一系列複雜的操做。

SQL Server看來這種智能化的操做仍是挺強的。

 

咱們再來分析SQL Server關於子查詢這塊的智能特性,由於通過上面的分析經過對比,相關的子查詢語句在運行時須要更多的消耗:

一、有時候須要經過索引假脫機(Index Spool)、表脫機(Table Spool)進行中間結果項的暫存,而這一過程的中間項須要建立、增長、刪除、銷燬等操做都須要消耗大量的內存和CPU

二、關於相關子查詢中以上提到的中間項的造成都是位於Tempdb臨時庫中,有時候會增大Tempdb的空間,增長Tempdb庫的消耗、頁爭用等問題。

因此,要避免上面的問題,最好的方式是避免使用相關子查詢,儘可能使用獨立子查詢進行操做。

固然,SQL Server一樣提供了自動轉換的功能,智能的去分析語句,避免相關的子查詢操做進行:

 

來看一個稍差的寫法:

SELECT o.OrderID 
FROM Orders O
WHERE EXISTS
(
   SELECT c.CustomerID
   FROM  Customers C
   WHERE C.City=N'Londom' AND C.CustomerID=O.CustomerID
)

上面的語句,咱們寫的是相關的子查詢操做,可是在執行計劃中造成的確實獨立的子查詢,這樣從而避免相關的子查詢所帶來的性能消耗。

其實上面語句,相對好的寫法是以下

SELECT o.OrderID 
FROM Orders O
WHERE O.CustomerID IN
(
   SELECT c.CustomerID
   FROM  Customers C
   WHERE C.City=N'Londom'
)

這樣所造成的就是徹底獨立的子查詢,這也是SQL Server要執行的意圖。因此這個語句造成的查詢計劃是和上面的查詢計劃同樣。

這裏的優化所有得益於SQL Server的智能化。

可是咱們在寫語句的時候,須要本身瞭解,掌握好,這樣才能寫出高效的T-SQL語句。

 

參考文獻

結語

本篇篇幅有點長,可是介紹的子查詢內容也還不是很全,後續慢慢的補充上,咱們寫的SQL語句中不少都涉及到子查詢,因此這塊應用仍是挺廣泛的。到本篇文章關於平常調優的T-SQL中的查詢語句常常用到的一些運算符基本介紹全了,固然,還有一些別的增刪改一系列的運算符,這些平常生活中咱們通常不採用查詢計劃調優,後續咱們的文章會將這些運算符也添加上,以供參考之用。

在完成本系列關於查詢計劃相關的調優以後,我打算將數據庫有關統計信息這塊也作一個詳細的分析介紹。由於統計信息是支撐SQL Server評估最優執行計劃的最重要的決策點,

因此統計信息的重要性不言而喻。有興趣的童鞋能夠提早關注。

關於SQL Server性能調優的內容涉及面很廣,後續文章中依次展開分析。

 

有問題能夠留言或者私信,隨時恭候有興趣的童鞋加入SQL SERVER的深刻研究。共同窗習,一塊兒進步。

 

文章最後給出上幾篇的鏈接,看來有必要整理一篇目錄了.....

SQL Server調優系列基礎篇

SQL Server調優系列基礎篇(經常使用運算符總結)

SQL Server調優系列基礎篇(聯合運算符總結)

SQL Server調優系列基礎篇(並行運算總結)

SQL Server調優系列基礎篇(並行運算總結篇二)

SQL Server調優系列基礎篇(索引運算總結)

 

 

若是您看了本篇博客,以爲對您有所收穫,請不要吝嗇您的「推薦」。

相關文章
相關標籤/搜索