聯接算法



本文摘自《鋒利的SQL》:http://item.jd.com/10380652.html


Microsoft SQLServer Management Studio中執行查詢時,如果選定工具欄中的按鈕,可以看到爲查詢生成的執行計劃。執行計劃以圖形方式顯示了SQL Server查詢優化器選擇的數據檢索方法,如表掃描、排序、哈希匹配等。對於聯接查詢,SQL Server會根據聯接表之間的數據、索引等情況,選擇使用嵌套循環聯接、合併聯接或哈希聯接。

7.7.1嵌套循環聯接

嵌套循環聯接也稱爲“嵌套迭代”,它將一個聯接輸入用作外部輸入表(顯示爲圖形執行計劃中的頂端輸入),將另一個聯接輸入用作內部(底端)輸入表。外部循環逐行處理外部輸入表。內部循環會針對每個外部行執行,在內部輸入表中搜索匹配行。簡單地講,就是掃描其中的一個聯接表,併爲該表中的每一行在另一個聯接表中搜索匹配行。

如果外部輸入較小(不到10行)而內部輸入較大且預先創建了索引,則嵌套循環聯接尤其有效。在許多小事務中(如那些隻影響較小的一組行的事務),索引嵌套循環聯接優於合併聯接和哈希聯接。但在大型查詢中,嵌套循環聯接通常不是最佳選擇。

例如,下面的查詢由於Sales.Customer錶行數只有1行,而Sales.SalesOrderHeader數據量較大,因此將使用嵌套循環聯接,生成的執行計劃如圖7-11所示。

USE AdventureWorks;

GO

SELECT *

FROM Sales.Customer  

 INNER JOINSales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID

WHERE Customer.CustomerID = 1;

7-11使用嵌套循環的執行計劃

在該計劃中存在兩個嵌套循環,其中只有左邊的嵌套循環符用於Sales.CustomerSales.SalesOrderHeader的聯接,而右邊的嵌套循環是用於Sales.SalesOrderHeader的索引查找與物理行定位(鍵查找)之間的聯接。執行計劃右上角的Sales.Customer表被作爲外部輸入,在聚集索引中查找客戶。對於每個客戶,嵌套循環運算將對SalesOrderHeader.CustomerID列上的IX_SalesOrderHeader_CustomerID索引執行一次查找,然後再跟一個鍵查找來定位要訪問的數據行。

7.7.2合併聯接

合併聯接要求兩個輸入都在合併列上排序,合併列由聯接謂詞的等效(ON)子句定義。由於每個輸入都已排序,因此合併聯接將從每個輸入獲取一行並將其進行比較。例如,對於內聯接操作,如果行相等則返回。如果行不相等,則廢棄值較小的行並從該輸入獲得另一行。這一過程將重複進行,直到處理完所有的行爲止。

合併聯接操作可以是常規操作,也可以是多對多操作。多對多合併聯接使用臨時表存儲行。如果每個輸入中有重複值,則在處理其中一個輸入中的每個重複項時,另一個輸入必須重繞到重複項的開始位置。

合併聯接本身的速度很快,但是如果合併列上未建立索引,選擇合併聯接有可能會非常費時,因爲它首先要對列進行排序操作。然而,如果數據量很大且能夠從索引中獲得預排序的所需數據,則合併聯接通常是最快的可用聯接算法。

例如,下面的查詢語句將獲取訂單的詳細信息,由於SalesOrderHeaderSalesOrderDetail在合併列SalesOrderID上都具有聚集索引,已經將列進行了排序,所以查詢優化器會選擇合併聯接。如圖7-12所示。

USE AdventureWorks;

GO

SELECT *

FROM Sales.SalesOrderHeader  

 INNER JOINSales.SalesOrderDetail 

   ONSalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID;

7-12使用合併聯接的執行計劃

7.7.3哈希聯接

哈希聯接可以有效處理未排序的大型非索引輸入。因此,它對處理複雜查詢的中間結果很有用。查詢的中間結果是未經索引的,而且通常不會爲查詢計劃中的下一個操作進行適當的排序。並且,查詢優化器只估計中間結果的大小。而對於複雜查詢,估計可能有很大的誤差,因此如果中間結果比預期的大得多,則處理中間結果的算法不僅必須有效而且必須適度弱化。再像合併聯接那樣嚴格要求具備排序列,對於中間結果而言是不現實的,排序成本的付出可能要遠遠大於數據的直接檢索成本。

選擇哈希聯接的兩種情況:一是沒有爲聯接創建合適的索引,二是中間結果比較大。

哈希聯接有兩種輸入:生成輸入和探測輸入。查詢優化器會選擇二者中較小的那個作爲生成輸入,對聯接列值應用哈希函數,將生成輸入中的行分配到哈希桶中。哈希桶是一種存放所訪問數據位置的結構,有了它,進行數據檢索時,可以避免不必要的表掃描。

爲了驗證無索引情況下的哈希聯接使用,首先使用下面的語句創建Sales.CustomerSales.SalesOrderHeader表的副本。

USE AdventureWorks;

GO

SELECT TOP 10 *

INTO MyCustomer

FROM Sales.Customer

ORDER BY CustomerID;

 

SELECT TOP 100 *

INTO MySalesOrderHeader

FROM Sales.SalesOrderHeader

ORDER BY CustomerID;

執行下面的查詢,可以看到如圖7-13所示的執行計劃。

SELECT *

FROM MyCustomer

 INNER JOINMySalesOrderHeader

   ONMyCustomer.CustomerID = MySalesOrderHeader.CustomerID;

7-13使用哈希聯接的執行計劃

下面再來看一個比較有趣的示例。下面的查詢語句中僅選擇了Sales.CustomerCustomerID = 1的行與Sales.SalesOrderHeader進行聯接,由於聯接行數很小,所產生中間結果的數據量也比較小,因此,可以看到查詢優化器爲語句使用了嵌套循環聯接。如圖7-14所示。

USE AdventureWorks;

GO

SELECT *

FROM Sales.Customer  

 INNER JOINSales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID

WHERE Customer.CustomerID = 1;    

7-14數據量較小時使用嵌套循環聯接

 同樣是上面的聯接,去除掉WHERE篩選條件後數據量明顯增大,執行該語句會發現查詢優化器使用了哈希聯接方式。如圖7-15所示。

SELECT *

FROM Sales.Customer  

 INNER JOINSales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID;  

7-15數據量較大時使用哈希聯接

7.7.4使用聯接提示強制聯接策略

聯接提示用於指定查詢優化器在兩個表之間強制執行聯接策略,提示符包括LOOP JOINMERGE JOINHASH JOIN,分別用於嵌套循環、哈希和合並聯接。如果指定了多個聯接提示,則優化器從允許的聯接策略中選擇開銷最少的聯接策略。此外,也可以使用OPTION子句指定聯接策略。但是這種方式會影響查詢中的所有聯接,通常用於舊式聯接語法。

1.爲每個聯接指定單獨的聯接策略

可以在FROM子句中使用LOOP JOINMERGE JOINHASH JOIN提示符爲每個聯接單獨指定聯接策略。例如,下面的查詢語句指定使用嵌套循環聯接。

USE AdventureWorks;

GO

SELECT *

FROM Sales.Customer  

 INNER LOOPJOIN Sales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID;

又如,下面的查詢語句指定使用合併聯接。

USE AdventureWorks;

GO

SELECT *

FROM Sales.Customer  

 INNERMERGE JOIN Sales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID;

在多表聯接中使用聯接提示時,會影響聯接的執行順序。在前面介紹了,在不影響返回結果正確的情況下,查詢優化器會按照效率優先的原則,選擇首先執行的聯接。例如,下面語句的執行計劃如圖7-16所示,可以看到首先執行的是Sales.SalesOrderHeaderSales.SalesOrderDetail的聯接,然後將聯接結果再與Sales.Customer進行聯接。

USE AdventureWorks;

GO

SELECT *

FROM Sales.Customer  

 INNER JOINSales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID

 INNER JOINSales.SalesOrderDetail

   ONSalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID; 

7-16未使用聯接提示的執行計劃

下面的語句爲Sales.CustomerSales.SalesOrderHeader指定了合併聯接提示,並且這個提示僅對這兩個表起作用,與Sales.SalesOrderDetail的聯接策略仍舊由查詢優化器決定。由於明確指定了Sales.CustomerSales.SalesOrderHeader使用合併聯接,優化器會先執行該聯接,而不是先執行Sales.SalesOrderHeaderSales.SalesOrderDetail的聯接。否則,就會造成Sales.CustomerSales.SalesOrderHeaderSales.SalesOrderDetail的聯接結果再執行合併聯接。圖7-17是該語句的執行計劃。

SELECT *

FROM Sales.Customer  

 INNERMERGE JOIN Sales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID

 INNER JOINSales.SalesOrderDetail

   ONSalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID; 

7-17使用聯接提示後的執行計劃

如果希望Sales.CustomerSales.SalesOrderHeaderSales.SalesOrderDetail的聯接結果執行合併聯接,則應當使用嵌套聯接的方式實現,參考下面的語句:

SELECT *

FROM Sales.Customer  

 INNERMERGE JOIN (Sales.SalesOrderHeader

                   INNER JOIN Sales.SalesOrderDetail

                     ONSalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID)

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID;

2.爲全部聯接指定統一的聯接策略

當使用舊式聯接語法時,應當使用OPTION子句指定聯接策略,但是,這種策略會影響語句中的全部聯接,無法爲每個聯接單獨指定不同的聯接策略,如:

SELECT *

FROM Sales.Customer, Sales.SalesOrderHeader,Sales.SalesOrderDetail

WHERE Customer.CustomerID =SalesOrderHeader.CustomerID

 ANDSalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID

OPTION (MERGE JOIN); 

該語句的執行計劃如圖7-18所示,可以看到三個表之間全部使用了合併聯接策略。

7-18爲全部聯接使用統一聯接策略的執行計劃

ANSI SQL:1992規範中,也可以使用OPTION子句,它同樣也是影響語句中的全部聯接,如:

SELECT *

FROM Sales.Customer  

 INNER JOINSales.SalesOrderHeader

   ONCustomer.CustomerID = SalesOrderHeader.CustomerID

 INNER JOINSales.SalesOrderDetail

   ONSalesOrderHeader.SalesOrderID = SalesOrderDetail.SalesOrderID

OPTION (MERGE JOIN);

 

發佈了92 篇原創文章 · 獲贊 107 · 訪問量 116萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章