淺談SQL Server中的三種物理連接操作

来源:http://www.cnblogs.com/gallen-n/archive/2016/06/15/5588443.html
-Advertisement-
Play Games

簡介 在SQL Server中,我們所常見的表與表之間的Inner Join,Outer Join都會被執行引擎根據所選的列,數據上是否有索引,所選數據的選擇性轉化為Loop Join,Merge Join,Hash Join這三種物理連接中的一種。理解這三種物理連接是理解在表連接時解決性能問題的基 ...


簡介

    在SQL Server中,我們所常見的表與表之間的Inner Join,Outer Join都會被執行引擎根據所選的列,數據上是否有索引,所選數據的選擇性轉化為Loop Join,Merge Join,Hash Join這三種物理連接中的一種。理解這三種物理連接是理解在表連接時解決性能問題的基礎,下麵我來對這三種連接的原理,適用場景進行描述。

 

嵌套迴圈連接(Nested Loop Join)

    迴圈嵌套連接是最基本的連接,正如其名所示那樣,需要進行迴圈嵌套,這種連接方式的過程可以簡單的用下圖展示:

    1

     圖1.迴圈嵌套連接的第一步

     2

     圖2.迴圈嵌套連接的第二步

 

    由上面兩個圖不難看出,迴圈嵌套連接查找內部迴圈表的次數等於外部迴圈的行數,當外部迴圈沒有更多的行時,迴圈嵌套結束。另外,還可以看出,這種連接方式需要內部迴圈的表有序(也就是有索引),並且外部迴圈表的行數要小於內部迴圈的行數,否則查詢分析器就更傾向於Hash Join(會在本文後面講到)。

    通過嵌套迴圈連接也可以看出,隨著數據量的增長這種方式對性能的消耗將呈現出指數級別的增長,所以數據量到一定程度時,查詢分析器往往就會採用這種方式。

    下麵我們通過例子來看一下迴圈嵌套連接,利用微軟的AdventureWorks資料庫:

    3

    圖3.一個簡單的嵌套迴圈連接

   

    圖3中ProductID是有索引的,並且在迴圈的外部表中(Product表)符合ProductID=870的行有4688條,因此,對應的SalesOrderDetail表需要查找4688次。讓我們在上面的查詢中再考慮另外一個例子,如圖4所示。

    4

    圖4.額外的列帶來的額外的書簽查找

  

    由圖4中可以看出,由於多選擇了一個UnitPrice列,導致了連接的索引無法覆蓋所求查詢,必須通過書簽查找來進行,這也是為什麼我們要養成只Select需要的列的好習慣,為瞭解決上面的問題,我們既可以用覆蓋索引,也可以減少所需的列來避免書簽查找。另外,上面符合ProductID的行僅僅只有5條,所以查詢分析器會選擇書簽查找,假如我們將符合條件的行進行增大,查詢分析器會傾向於表掃描(通常來說達到表中行數的1%以上往往就會進行table scan而不是書簽查找,但這並不絕對),如圖5所示。

    5

    圖5.查詢分析器選擇了表掃描

 

    可以看出,查詢分析器此時選擇了表掃描來進行連接,這種方式效率要低下很多,因此好的覆蓋索引和Select *都是需要註意的地方。另外,上面情況即使涉及到表掃描,依然是比較理想的情況,更糟糕的情況是使用多個不等式作為連接時,查詢分析器即使知道每一個列的統計分佈,但卻不知道幾個條件的聯合分佈,從而產生錯誤的執行計劃,如圖6所示。

    6

    圖6.由於無法預估聯合分佈,導致的偏差

 

   由圖6中,我們可以看出,估計的行數和實際的行數存在巨大的偏差,從而應該使用表掃描但查詢分析器選擇了書簽查找,這種情況對性能的影響將會比表掃描更加巨大。具體大到什麼程度呢?我們可以通過強製表掃描和查詢分析器的預設計划進行比對,如圖7所示。

    7

    圖7.強製表掃描性能反而更好

 

合併連接(Merge Join)

    談到合併連接,我突然想起在西雅圖參加SQL Pass峰會晚上酒吧排隊點酒,由於我和另外一哥們站錯了位置,貌似我們兩個在插隊一樣,我趕緊說:I’m sorry,i thought here is end of line。對方無不幽默的說:”It’s OK,In SQL Server,We called it merge join”。

    由上面的小故事不難看出,Merge Join其實上就是將兩個有序隊列進行連接,需要兩端都已經有序,所以不必像Loop Join那樣不斷的查找迴圈內部的表。其次,Merge Join需要表連接條件中至少有一個等號查詢分析器才會去選擇Merge Join。

    Merge Join的過程我們可以簡單用下麵圖進行描述:

    8

    圖8.Merge Join第一步

 

    Merge Join首先從兩個輸入集合中各取第一行,如果匹配,則返回匹配行。假如兩行不匹配,則有較小值的輸入集合+1,如圖9所示。

    9

    圖9.更小值的輸入集合向下進1

    用C#代碼表示Merge Join的話如代碼1所示。

public class MergeJoin
{
    // Assume that left and right are already sorted
    public static Relation Sort(Relation left, Relation right)
    {
        Relation output = new Relation();
        while (!left.IsPastEnd() && !right.IsPastEnd())
        {
            if (left.Key == right.Key)
            {
                output.Add(left.Key);
                left.Advance();
                right.Advance();
            }
            else if (left.Key < right.Key)
                left.Advance();
            else //(left.Key > right.Key)
                right.Advance();
        }
        return output;
    }
}

 

    代碼1.Merge Join的C#代碼表示

    因此,通常來說Merge Join如果輸入兩端有序,則Merge Join效率會非常高,但是如果需要使用顯式Sort來保證有序實現Merge Join的話,那麼Hash Join將會是效率更高的選擇。但是也有一種例外,那就是查詢中存在order by,group by,distinct等可能導致查詢分析器不得不進行顯式排序,那麼對於查詢分析器來說,反正都已經進行顯式Sort了,何不一石二鳥的直接利用Sort後的結果進行成本更小的MERGE JOIN?在這種情況下,Merge Join將會是更好的選擇。

    另外,我們可以由Merge Join的原理看出,當連接條件為不等式(但不包括!=),比如說> < >=等方式時,Merge Join有著更好的效率。

    下麵我們來看一個簡單的Merge Join,這個Merge Join是由聚集索引和非聚集索引來保證Merge Join的兩端有序,如圖10所示。

    10

    圖10.由聚集索引和非聚集索引保證輸入兩端有序

 

    當然,當Order By,Group By時查詢分析器不得不用顯式Sort,從而可以一箭雙雕時,也會選擇Merge Join而不是Hash Join,如圖11所示。

    11

    圖11.一箭雙雕的Merge Join

 

哈希匹配(Hash Join)

    哈希匹配連接相對前面兩種方式更加複雜一些,但是哈希匹配對於大量數據,並且無序的情況下性能均好於Merge Join和Loop Join。對於連接列沒有排序的情況下(也就是沒有索引),查詢分析器會傾向於使用Hash Join。

    哈希匹配分為兩個階段,分別為生成和探測階段,首先是生成階段,第一階段生成階段具體的過程可以如圖12所示。

    12

    圖12.哈希匹配的第一階段

 

    圖12中,將輸入源中的每一個條目經過散列函數的計算都放到不同的Hash Bucket中,其中Hash Function的選擇和Hash Bucket的數量都是黑盒,微軟並沒有公佈具體的演算法,但我相信已經是非常好的演算法了。另外在Hash Bucket之內的條目是無序的。通常來講,查詢優化器都會使用連接兩端中比較小的哪個輸入集來作為第一階段的輸入源。

    接下來是探測階段,對於另一個輸入集合,同樣針對每一行進行散列函數,確定其所應在的Hash Bucket,在針對這行和對應Hash Bucket中的每一行進行匹配,如果匹配則返回對應的行。

    通過瞭解哈希匹配的原理不難看出,哈希匹配涉及到散列函數,所以對CPU的消耗會非常高,此外,在Hash Bucket中的行是無序的,所以輸出結果也是無序的。圖13是一個典型的哈希匹配,其中查詢分析器使用了表數據量比較小的Product表作為生成,而使用數據量大的SalesOrderDetail表作為探測。

    13

    圖13.一個典型的哈希匹配連接

 

    上面的情況都是記憶體可以容納下生成階段所需的記憶體,如果記憶體吃緊,則還會涉及到Grace哈希匹配和遞歸哈希匹配,這就可能會用到TempDB從而吃掉大量的IO。這裡就不細說了,有興趣的同學可以移步:http://msdn.microsoft.com/zh-cn/library/aa178403(v=SQL.80).aspx

 

總結

    下麵我們通過一個表格簡單總結這幾種連接方式的消耗和使用場景:

  嵌套迴圈連接 合併連接 哈希連接
適用場景 外層迴圈小,記憶體迴圈條件列有序 輸入兩端都有序 數據量大,且沒有索引
CPU 低(如果沒有顯式排序)
記憶體 低(如果沒有顯式排序)
IO 可能高可能低 可能高可能低

    理解SQL Server這幾種物理連接方式對於性能調優來說必不可少,很多時候當篩選條件多表連接多時,查詢分析器就可能不是那麼智能了,因此理解這幾種連接方式對於定位問題變得尤為重要。此外,我們也可以通過從業務角度減少查詢範圍來減少低下性能連接的可能性。

 

 

參考文獻:

http://msdn.microsoft.com/zh-cn/library/aa178403(v=SQL.80).aspx

http://www.dbsophic.com/SQL-Server-Articles/physical-join-operators-merge-operator.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 索引是什麼大家都知道是加快查詢用的,是的,沒錯,索引的根本作用是縮小掃描範圍,而不是直接定位記錄,直接定位記錄只是索引的一種特殊情況,縮小範圍之後最終都是線性掃描得到結果。 就是按照某個值排序,這是最最基本的索引了,RDBMS里的聚集索引,別小看這種簡陋的東西,它是大數據里常用甚至是唯一可用的索引。 ...
  • 開始事物:begin transaction 提交事物:commit transaction 回滾事物:rollback transaction http://wenku.baidu.com/link?url=sOj3AnJPBbeWg6gu2NYcMSfTK4gj8BobB-URG2rCiH8_2 ...
  • 無論是資料庫管理員,還是普通用戶,都需要經常對資料庫對象進行管理,如資料庫對象的創建、刪除、修改等。Oracle 中的資料庫對象包括表、索引、視圖、存儲程式、序列等,這些資料庫對象以一種邏輯關係組織在一起,這就是模式( schema )。模式是一個用戶所擁有的所有資料庫對象的集合。 每個資料庫對象都 ...
  • 在做練習的時候經常表沒設計好,後來有要去資料庫修改表結構但是沒詞用界面修改的時候都會提示要保存 轉自http://www.57xue.com/ItemView/Sql/2016061600160.html 假設我們有一張表 在我們的程式開發中,有時候會由於需求的變化而要修改資料庫中的表結構。可能是增 ...
  • 一. 準備工作 1. 準備兩台伺服器(電腦),接入區域網中,使互相ping得通對方 2. 兩台伺服器都安裝mysql-server-5.1,必須保證mysql的版本一致 3. 假設,伺服器A:192.168.0.2,伺服器B:192.168.0.3 二. 創建同步用戶 在主伺服器上為從伺服器建立一個 ...
  • 關係模型與ER模型、Database & Schema & Instance ...
  • --創建學生表create table XS_543 ( XH char(6) not null , XM varchar2(20) not null, ZYM varchar2(10), XB char(4) default '男', CSSJ date, ZXF number(2), BZ va ...
  • 1、連接Mysql 格式: mysql -h主機地址 -u用戶名 -p用戶密碼 1、連接到本機上的MYSQL。 首先打開DOS視窗,然後進入目錄mysql\bin,再鍵入命令mysql -u root -p,回車後提示你輸密碼.註意用戶名前可以有空格也可以沒有空格,但是密碼前必須沒有空格,否則讓你重 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...