今天我將介紹在SQLServer 中的三種鏈接操做符類型,分別是:循環嵌套、哈希匹配和合並鏈接。主要對這三種鏈接的不一樣、複雜度用範例的形式一一介紹。算法
本文中使用了示例數據庫AdventureWorks ,下面是下載地址:http://msftdbprodsamples.codeplex.com/releases/view/4004sql
鏈接操做符是一種算法類型,它是SQLServer優化器爲了實現兩個數據集合之間的邏輯鏈接選擇的操做符。優化器能夠基於請求查詢、可用索引、統計信息和估計行數等不一樣的場景爲每一套數據選擇不一樣的算法數據庫
經過查看執行計劃能夠發現操做符如何被使用。接下來咱們看一下如何具體使用。數據結構
咱們經過下面的例子來展現一下(查詢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
圖右上方的叫「outer input」,在其下面的叫作「inner input」 性能
本質上講,「Nested Loops」操做符就是:爲每個記錄的外部輸入找到內部輸入的匹配行。優化
技術上講,這意味着外表彙集索引被掃描獲取外部輸入相關的記錄,而後內表彙集索引查找每個匹配外表索引的記錄。this
咱們能夠經過把鼠標放在彙集索引掃描操做符上面來驗證這個信息,請看這個tooltip:code
看這個執行的估計行數是1,索引查找tooltip以下:
此次發現執行的估計行數是179,這是很接近返回的外部輸入行的。
按照複雜度計算(假設N是外部輸出的行數,M是總行數在SalesOrderDetai表的):查詢複雜度是O(NlogM),這裏logM是在內部輸入表的每次查找的複雜度。
當外部輸入比較小而且內部輸入有索引在鏈接的字段上的時候SQLServer 優化器更喜歡選擇這種操做符類型(Nested Loop)。外部和內部輸入的數據行差距越大,這個操做符提供的性能越高。
「Merge」算法是鏈接兩個較大且按序存儲的在鏈接鍵上最有效的方式。請看一下下面這個查詢例子(查詢返回用戶和銷售表的ID):
SELECT OC.CustomerID, OH.SalesOrderID FROM Sales.SalesOrderHeader AS OH JOIN Sales.Customer AS OC ON OH.CustomerID = OC.CustomerID
查詢執行計劃以下:
就是這三個因素會致使優化器選擇Merge Join查詢操做符。
使用這種鏈接操做符的最大的性能就是兩個輸入操做符執行一次。咱們能把鼠標放在兩個數據的上面看一下執行的次數都是1,也就是說算法是頗有效率的。
合併鏈接同時讀取兩個輸入而後比較他們。若是匹配就返回,不然,行數較小的被放棄,由於兩邊輸入是有序的。放棄的行再也不匹配任何行。
知道其中一個表完畢一直重複匹配,即便另外一個表還有數據,那麼最大的時間複雜的消耗就是兩個表徹底不一樣鍵值,那麼最大的複雜度就是: O(N+M)。
「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:
因爲ContactID列沒有索引,因此選擇哈希操做符。
在深刻理解這個例子以前,介紹兩個重要的概念:一個是「Hashing」函數,一個是「Hash Table」。
函數是一個程序性函數,它接收1或者多個值而後轉換他們爲一個符號值(一般是數字)。這個函數一般是單向的,意味着不能反轉回來原始值,可是肯定性保證若是你提供了相同的值,符號值是徹底同樣的。也就是說,幾個不一樣的輸入值,能夠有相同的Hash值。
「Hash Table」是一個數據結構,把全部行都放到一個相同尺寸的桶裏面。每個桶表明一個哈希值。這意味着當你激活函數的行,使用結果你就會知道它屬於哪一個桶。
利用統計信息,SQLServer 會選擇較小的兩個數據輸入來提供構造輸入,而且輸入被用來在內存中建立哈希表。若是沒有足夠的內存,在tempdb中會使用物理磁盤。在哈希表創建後,SQLServer將從較大的表中獲得數據,叫作探測輸入。利用哈希匹配函數與哈希表比較,而後返回匹配行。在圖形執行計劃中,構造輸入的查詢在上面,探測輸入的查詢在下面。
只要較小的表很是小,這個算法就是很是有效的。可是若是兩個表都很是大,這多是很是低效的執行計劃。
利用Hints,破事SQLServer使用指定的鏈接類型。可是強烈不推薦這麼作,尤爲在生產環境,由於沒有永恆的最佳選擇(由於數據在變化),而且優化器一般是正確的。
添加OPTION 子句做爲查詢的結尾,使用關鍵字LOOP JOIN, MERGE 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)
本篇隨筆詳細介紹了三種連接操做符和它們的觸發機制,固然這些也都是動態的,就像前面說的沒有最佳的操做符,只有最合適的,要根據實際請款選擇不一樣的操做符。