SQL鏈接操做符介紹(循環嵌套, 哈希匹配和合並鏈接)

 

  今天我將介紹在SQLServer 中的三種鏈接操做符類型,分別是:循環嵌套、哈希匹配和合並鏈接。主要對這三種鏈接的不一樣、複雜度用範例的形式一一介紹。算法

  本文中使用了示例數據庫AdventureWorks ,下面是下載地址:http://msftdbprodsamples.codeplex.com/releases/view/4004sql

簡介:什麼是鏈接操做符

  鏈接操做符是一種算法類型,它是SQLServer優化器爲了實現兩個數據集合之間的邏輯鏈接選擇的操做符。優化器能夠基於請求查詢、可用索引、統計信息和估計行數等不一樣的場景爲每一套數據選擇不一樣的算法數據庫

  經過查看執行計劃能夠發現操做符如何被使用。接下來咱們看一下如何具體使用。數據結構

NESTED LOOPS(循環嵌套)

  咱們經過下面的例子來展現一下(查詢2001年7月份的數據):函數

SELECT
OH.OrderDate, OD.OrderQty, OD.ProductID, OD.UnitPrice
FROM
Sales.SalesOrderHeader AS OH
JOIN
Sales.SalesOrderDetail AS OD
ON
OH.SalesOrderID = OD.SalesOrderID
WHERE
OH.OrderDate BETWEEN '2001-07-01' AND '2001-07-31'

 

執行計劃的結果以下:oop

join types 1 exec plan

圖右上方的叫「outer input」,在其下面的叫作「inner input」 性能

本質上講,「Nested Loops」操做符就是:爲每個記錄的外部輸入找到內部輸入的匹配行。優化

技術上講,這意味着外表彙集索引被掃描獲取外部輸入相關的記錄,而後內表彙集索引查找每個匹配外表索引的記錄。this

咱們能夠經過把鼠標放在彙集索引掃描操做符上面來驗證這個信息,請看這個tooltip:code

join types 2 details

看這個執行的估計行數是1,索引查找tooltip以下:

join types 3 details

此次發現執行的估計行數是179,這是很接近返回的外部輸入行的。

按照複雜度計算(假設N是外部輸出的行數,M是總行數在SalesOrderDetai表的):查詢複雜度是O(NlogM),這裏logM是在內部輸入表的每次查找的複雜度。

當外部輸入比較小而且內部輸入有索引在鏈接的字段上的時候SQLServer 優化器更喜歡選擇這種操做符類型(Nested Loop)。外部和內部輸入的數據行差距越大,這個操做符提供的性能越高。

MERGE Join(合併鏈接)

「Merge」算法是鏈接兩個較大且按序存儲的在鏈接鍵上最有效的方式。請看一下下面這個查詢例子(查詢返回用戶和銷售表的ID):

 

SELECT
OC.CustomerID, OH.SalesOrderID
FROM
Sales.SalesOrderHeader AS OH
JOIN
Sales.Customer AS OC
ON
OH.CustomerID = OC.CustomerID

 

查詢執行計劃以下:

join types 4 exec plan

  • 首先咱們注意到兩套數據是在CustomerID上是有序的:由於彙集索引是有序的且在SalesorderHeader表上該字段是非彙集索引。
  • 根據在操做符的箭頭(鼠標放在上面),咱們能看到每一個返回結果行數都是很大的。
  • 除此以外,在On 的子句後面要用=操做符。

就是這三個因素會致使優化器選擇Merge Join查詢操做符。 

 

使用這種鏈接操做符的最大的性能就是兩個輸入操做符執行一次。咱們能把鼠標放在兩個數據的上面看一下執行的次數都是1,也就是說算法是頗有效率的。

合併鏈接同時讀取兩個輸入而後比較他們。若是匹配就返回,不然,行數較小的被放棄,由於兩邊輸入是有序的。放棄的行再也不匹配任何行。

知道其中一個表完畢一直重複匹配,即便另外一個表還有數據,那麼最大的時間複雜的消耗就是兩個表徹底不一樣鍵值,那麼最大的複雜度就是: O(N+M)。

HASH Match(哈希匹配)

「Hash」鏈接是咱們稱爲 「the go-to guy」 的操做符。當其餘鏈接操做符都不支持的場景時,就會選擇這種操做符。好比當表剛好不排序,或者沒有索引時。當優化器選擇這種操做符,通常來講多是咱們沒有作好一些基礎工做(例如,加索引)。可是有些狀況(複雜查詢)沒有別的方式,只能選擇它。

請看下面這個查詢(獲取contacts 表中姓和名中以「John」開始的包含銷售的ID字段的數據集):

 

SELECT
OC.FirstName, OC.LastName, OH.SalesOrderID
FROM
Sales.SalesOrderHeader AS OH
JOIN
Person.Contact AS OC
ON
OH.ContactID = OC.ContactID
WHERE
OC.FirstName LIKE 'John%'

 

The execution plan looks like this:

join types 5 exec plan

因爲ContactID列沒有索引,因此選擇哈希操做符。

在深刻理解這個例子以前,介紹兩個重要的概念:一個是「Hashing」函數,一個是「Hash Table」。

函數是一個程序性函數,它接收1或者多個值而後轉換他們爲一個符號值(一般是數字)。這個函數一般是單向的,意味着不能反轉回來原始值,可是肯定性保證若是你提供了相同的值,符號值是徹底同樣的。也就是說,幾個不一樣的輸入值,能夠有相同的Hash值。

「Hash Table」是一個數據結構,把全部行都放到一個相同尺寸的桶裏面。每個桶表明一個哈希值。這意味着當你激活函數的行,使用結果你就會知道它屬於哪一個桶。

利用統計信息,SQLServer 會選擇較小的兩個數據輸入來提供構造輸入,而且輸入被用來在內存中建立哈希表。若是沒有足夠的內存,在tempdb中會使用物理磁盤。在哈希表創建後,SQLServer將從較大的表中獲得數據,叫作探測輸入。利用哈希匹配函數與哈希表比較,而後返回匹配行。在圖形執行計劃中,構造輸入的查詢在上面,探測輸入的查詢在下面。

只要較小的表很是小,這個算法就是很是有效的。可是若是兩個表都很是大,這多是很是低效的執行計劃。

查詢Hints

利用Hints,破事SQLServer使用指定的鏈接類型。可是強烈不推薦這麼作,尤爲在生產環境,由於沒有永恆的最佳選擇(由於數據在變化),而且優化器一般是正確的。

添加OPTION 子句做爲查詢的結尾,使用關鍵字LOOP JOINMERGE JOIN 或者 HASH JOIN能夠強制執行鏈接。

看看如何實現:

 

SELECT OC.CustomerID, OH.SalesOrderID
FROM Sales.SalesOrderHeader AS OH
JOIN Sales.Customer AS OC
ON OH.CustomerID = OC.CustomerID
OPTION (HASH JOIN)

SELECT OC.FirstName, OC.LastName, OH.SalesOrderID
FROM Sales.SalesOrderHeader AS OH
JOIN Person.Contact AS OC
ON OH.ContactID = OC.ContactID
WHERE OC.FirstName LIKE 'John%'
OPTION (LOOP JOIN)

SELECT OC.FirstName, OC.LastName, OH.SalesOrderID
FROM Sales.SalesOrderHeader AS OH
JOIN Person.Contact AS OC
ON OH.ContactID = OC.ContactID
WHERE OC.FirstName LIKE 'John%'
OPTION (MERGE JOIN)

 

總結

Nested Loops

  • 複雜度: O(NlogM)。
  • 其中一個表很小的時候。
  • 較大的表容許使用索引查找鏈接字段。

Merge Join

  • 複雜度: O(N+M)。
  • 兩個輸入的鏈接字段是有序的。
  • 使用=操做符
  • 適合很是大的表

Hash Match

  • 複雜度: O(N*hc+M*hm+J)
  • 最後默認的操做符
  • 使用哈希表和動態哈希匹配函數匹配行

     本篇隨筆詳細介紹了三種連接操做符和它們的觸發機制,固然這些也都是動態的,就像前面說的沒有最佳的操做符,只有最合適的,要根據實際請款選擇不一樣的操做符。

相關文章
相關標籤/搜索