理解和使用SQL Server中的並行


 

    許多有經驗的數據庫開發或者DBA都曾經頭痛於並行查詢計划,尤其在較老版本的數據庫中(如sqlserver2000、oracle 7、mysql等)。但是隨着硬件的提升,尤其是多核處理器的提升,並行處理成為了一個提高大數據處理的高效方案尤其針對OLAP的數據處理起到了很好的作用。

    充分高效地利用並行查詢需要對調度、查詢優化和引擎工作等有一個比較好的了解,但是針對一般場景的應用我們只需要如何常規使用即可,這里也就不深入描述了,感興趣可以一起討論。

    那么這里我就簡單介紹下SQLServer中並行的應用?

什么是並行?

我們從小就聽說過“人多力量大”、“人多好辦事”等,其思想核心就是把一個任務分給許多人,這樣每個人只需要做很少的事情就能完成整個任務。更重要的是,如果額外的人專門負責分配工作,那么任務的完成時間就可以大幅減少了。

數糖豆

    設想你正面對一個裝滿各式各樣糖豆的罐子,並且要求書有多少個。假設你能平均每秒數出五個,需要大於十分鍾才能數完這個盒子里的3027個糖豆。

    如果你有四個朋友幫助你去做這個任務。你就有了多種策略來安排這個數糖豆任務,那讓我們模仿SQLServer 將會采取的策略來完成這個任務。你和4個朋友圍坐在一個桌子四周,糖果盒在中心,用勺子從盒子中拿出糖豆分給大家去計數。每個朋友還有一個筆和紙去記錄數完的糖豆的而數量。

    一旦一個人輸完了並且盒子空了,他們就把自己的紙給你。當你收集完每個人的計數,然后把所有的數字加在一起就是糖豆的數量。這個任務也就完成了。大概1-2分鍾,完成的效率提高了四倍多。當然四個人累加也是十分鍾左右甚至還要多(因為多出來了分配和累加的過程)。這個任務很好的展示了並行的優點,也沒有其他額外的工作需要處理。

使用SQLServer 完成“數糖豆”

    當然SQLServer 不會去數罐子里的糖豆,那我就讓它去計算表里的行數。如果表很小那么執行計划如圖1:

1250-Fig1.jpg

圖1  串行執行計划:

這個查詢計划使用了單一進程,就好像自己一個人數糖豆一樣。計划本身很簡單:流聚合操作符負責統計接收來自索引掃描操作符的行數,然后統計出總行數。相似的情況下,如果盒子里面糖豆非常少,雖然分配糖豆的時間會減少很多,但是統計步驟就顯得效率不是那么高了,因為相對於大數量的糖豆這部分的所占時間就高很多了。所以當表足夠大,SQLServer 優化器可以選擇增加更多的線程,執行計划如圖2:

1250-Fig2.jpg

圖2 並行計數計划

 

右側三個操作符中的黃色箭頭圖標表示引入了多線程。每個線程被分配了一部分工作,然后完成分分部工作被聚集在一起成為最終結果。如同前面人工數糖豆的例子一樣,並行計划有很大可能提高完成速度,因為多線程在計數上更優。

並行如何工作?

 

設想一下,如果SQLServer沒有內置對於並行的支持。或許我們只能手動去平均划分並行查詢來實現性能優化,然后分別運行分配的流,獨立地訪問服務器。

1250-Fig3.jpg

圖3 手動分配並行


每次查詢都必須手寫分隔表行數的獨立查詢,確保全表數據都被查詢到。幸運的是SQLServer 能在一個處理單元內完成每一個分隔的獨立線程,然后接收三個部分結果集只需要三分之一的時間左右。自然地我們還需要額外的時間來合並三個結果集。

並行執行多個串行計划

回想一下圖2中顯示的並行查詢計划,然后假設SQLServer 分配了三個額外的線程在運行時去查詢。概括的講,重新生成並行計划來展示SQLServer 運行三個獨立串行的計划流(這個表示是我自己起的不是很精確。)

1250-Fig4.jpg

圖4: 多串行計划

 

每個線程被分配三個branch 中的一個,最后匯聚到Gather Streams(流聚合) 操作符。注意這個圖中只有流聚合操作符帶有黃色並行箭頭;所以這個操作符是這個計划中僅有的與多線程交互的操作符。這種通用策略有兩個原因始適合SQLServer的。首先,所有必要地執行串行計划SQL代碼已經存在並且已經被優化多年和在線發布。其次,方法的方位很合適:如果更多線程被調用,SQLServer 能輕易添加額外計划分之來分配更多線程。

額外的線程數量分配給每一個並行計划,這被稱為並行度(縮寫為DOP)。SQLServer 在查詢開始之前就選擇了DOP,然后不需要計划重新編譯就能改變並行度。最大DOP對於每一個並行區域都是由SQLServer的邏輯處理單元的可利用數量決定的(物理核)

並行掃描和並行頁支持

    圖4中的問題是每個索引掃描操作符都會去數整個輸入集的每一行。不及時糾正,計划就會產生錯誤的結果集並且和可能花費更多時間。手工並行的例子通過使用where子句來避免這個問題。

    SQLServer 沒有用相同的方法,因為分配工作假定平均地使每個查詢接收相等的可利用資源,並且每個數據行需要相同的處理。在一個簡單例子中,例如統計一個表中的行數,這種假定可能會效果很好(同一個服務器沒有其他活動的時候),並且三個查詢可能返回的查詢也是完全等時的。

    與分配固定數量行數給每個線程不同,SQLServer使用存儲引擎的功能叫做“Parallel Page Supplier ”來按需分配行數給線程。在查詢計划中是看不到“Parallel Page Supplier ”的,因為它不是查詢處理器的一部分,但是我們能拓展圖4來形象的展示他的連接方式:

1250-Fig5.jpg

圖5:  Parallel Page Supplier

    這里的關鍵點就是demand-based (基於需求)架構;通過響應現成的請求提供一個行數的批處理給需要更多工作的線程去做。對比數糖豆的案例,Parallel Page Supplier 就像是專門用勺子從罐子里面拿出糖豆的過程。只有一個勺子防止兩個人都去數相同的豆子。並且其他線程將會數更多豆子來補償。

   注意Parallel Page Supplier 的使用並不阻止現有的優化像預讀掃描(在硬盤上提前讀取數據)。事實上,這種預讀在這種情況下效率要比單線程還要好,這個單線程是底層的物理掃描而不是之前我們看到的三個獨立的手動並行的例子。

    Parallel Page Supplier 也不會限制索引掃描;SQLServer利用它當多線程協同讀取一個數據架構。數據架構可能是堆、聚集索引表、或者一個索引,並且操作可以是掃描或者查找。如果后者(查找)更高效,考慮索引查找操作就像一個部分掃描,例如它能查找到第一個符合條件的行然后掃面范圍的結尾。

執行上下文

    與手動並行例子的機制相似,但是又與創建獨立連接的串行查詢,SQLServer 使用了一個輕量級的構造稱之為“執行上下文”來實現並行。

    一個執行上下文來自查詢計划的一部分,該內容通過填寫在計划重新編譯和優化后的細節來產生。這些細節包括了直到運行才有的引用對象(如批處理中的臨時表)和運行時的參數以及局部變量。這里就不展開講了,微軟的白皮書中由於詳細的介紹。

    SQLServer 運行一個並行計划,通過為每一個查詢計划的並行區域派生一個DOP執行上下文,利用獨立的線程在上下文中運行串行計划包含的部分。為了幫助概念的理解,圖6中展示了三個執行上下文,每個顏色區分執行上下文的范圍。雖然並不是明顯地展示出來,但是一個Parallel Page Supplier 還是被用來協調索引掃描,避免重復讀取。

1250-Fig6.jpg

圖6: 並行計划執行上下文

 

    為了更具體的觀察抽象概念,圖7展示了並行行計數查詢包含的信息,在SSMS的選項中,“Actual Execution Plan”(實際執行計划),打開左側擴展+。

1250-Fig7.jpg

圖7: 並行計划行計數

    兩個圖片對比,行處理的數字一個是3一個是113443。信息來自於屬性窗口,通過點擊操作符(或者鏈接線)然后按下F4,或者右鍵屬性。右鍵操作符或者線,並且選擇彈出菜單的屬性。

    右邊的插圖中我們能看到每個線程讀取的行數和總行數;注意兩個線程處理了相似的行數(40000左右),但是第三個線程值處理了32000行。如上所述,基於需求的架構取決於每個線程時間因素和處理器負載等等,及時是輕負載的機器也會有不平衡的現象。

    左側的這個圖展示了三個結果結被收集在一起的過程,匯總了每個進程的結果集。它的元素是並行執行線程的數量。

Schedulers, Workers, 以及Tasks

這篇文章到目前為止‘thread’ 和‘worker’理解上是一致的。現在我們需要定義更加精確,如下。

Schedulers

一個scheduler 在SQLserver 中代表一個邏輯處理器,或者是一個物理CPU,或許是一個處理核心,或許是在一個核(超線程)上運行的多個硬件線程之一。調度器的主要目的就是允許SQLServer精確控制線程調度,而不是依賴Windows操作系統的泛型算法。每個調度器確保僅有一個協調執行線程在運行(就操作系統而言)在指定時間內。這樣做的重要好處就是減少了上下文切換,並且減少了調用windows內核的次數。串行的三個部分覆蓋了任務調度和執行的內部詳細信息。

    關於任務調度在可以在DMV(sys.dm_os_schedulers)中查看。

Workers 和Threads

   一個SQLServer 工作線程是一個抽象表示一個單一的操作系統線程或者一個光纖。很少系統運行光纖模式任務調度,因此大部分文檔都是使用了工作線程來強調對於大多數實際目的而言,一個worker就是一個線程。一個工作線程綁定一個具體的調度。關於工作線程的信息可以通過DMVsys.dm_os_workers來查看。

Tasks

可以這樣定義Tasks:

一個任務表示一個被SQLServer 調度的線程的單位。一個批處理能映射一個或者多個任務。例如,一個並行查詢將被多個任務執行。

    擴展這個簡單的定義,一個任務就被SQLServer 工作線程運行的一件工作。一個批處理僅包含一個串行執行計划就是單任務,並且將被單一連接提供的線程執行(從開始到結束)。這種情況下,執行必須等待另一個事件(例如從硬盤讀取)完成。單線程被分配一個任務,然后直到被完全完成否則不能運行其他任務單元。

執行上下文

    如果一個任務描述被完成的工作,一個執行上下文就是工作發生的地方。每個任務在一個執行上下文內運行,標識在DMVsys.dm_os_tasks中的exec_context_id列中(你也可以看到執行上下文使用ecid 列在sys.sysprocesses視圖中)

交換操作符

    簡要回顧,我們已經看到SQLServer通過並發執行一個串行計划的多個實例來執行一個並行計划。每個串行計划都是一個單獨的任務,在各自的執行上下文內獨立運行各自的線程。最終這些線程的結果成為交換操作符的組成部門,就是將並行計划的執行上下文連接在一起。一般來說,一個復雜的查詢計划可以包含多個串行或者並行區域,這些區域由交換操作符來連接。

到目前為止,我們已經看到只有一種形式的連接操作符,叫做流聚合,但是它能以另外兩種進化的形式繼續出現如下:

1250-Fig8.jpg

圖8: 交換邏輯操作符

這些形式的交換操作符就是在一個或者多個線程內移動行,分配獨立的行給多個線程。不同的邏輯形式的操作符要么是引入新的串行或者並行區域,要么是分配重定向行給在兩個並行區域的接口。

不僅可以分割、合並、重定向行在多線程上,還可以做到如下事情:

  • 使用五中不同的策略來確定輸出輸入行的路線。
  • 如果需要,可以保留輸入行的順序。
  • Much of this flexibility stems from its internal design, so we will look at that first. 靈活源自其內部設計,因此我們要先觀察

交換操作符內部

交換操作符有兩個完全不同的子組件:

  • 生產者, 連接輸入端的線程
  • 消費者, 連接輸出端的線程

圖9 展示了一個流聚合操作符的放大視圖(圖6)

1250-Fig9.jpg

圖9: 流聚合內部構造

    每個生產者 收集它的輸入行並且將輸入包裝成一個或者多個內存中的緩存。一旦緩存滿了,生產者將會將其推入到消費者端。每個生產者和消費者都運行在相同的線程作為其連接執行上下文(如同連接的顏色暗示)。消費者端的交換操作符當它被上級操作符要求就從緩存中讀取一行數據(如同本例中的紅色的陰影數據流聚合)。

    主要好處之一就是復雜度通常與分享數據的多個執行的線程有關,而這些線程由SQLServer一個內部操作符處理。另外,在計划中的非交換操作符是完全串行執行的,並且不需要去關心這些問題。

    交換操作符使用緩存來減少開銷,並且為了實現控制基本種類的流(例如為了阻止快速生產者比慢速消費者快太多)。精確分配緩沖區,隨着交換的不同緩存區也變化,不論是否需要保留順序,並且決定如何匹配生產者和消費者的數據行,

路由行

    如上所述,一個交換操作符能決定一個生產者應該匹配哪一個特定的行數據。這個決定依賴於被交換操作符指定的分塊類型。並且有五個可選類型,

 

類型 描述
Hash

最常見,通過計算當前行的一個或者多個列上的哈希函數來選擇消費者。

輪循

每個新的行按照固定的序列被發送給下一個消費者
廣播 每一行被發送給所有消費者。
請求 每一行被發送給第一個請求的消費者。這是僅有的通過消費者內部的交換符拉出行的分割類型。
范圍 每一個消費者被分配一個不重疊的范圍值。特定的輸入列分成范圍決定消費者獲得的行。

 

請求和范圍分割類型是比前面三種更少見的,並且一般只在操作分區表的查詢計划中能看到。請求類型是用來收集分區的連接來分配分區ID給下一個工作線程。例如,當創建分區索引的時候使用范圍分割類型,那么如果要想查到屬於哪種類型需要在查詢計划中查找:

1250-Fig10.jpg

圖10: 交換操作分割類型

 

保留輸入順序

一個交換操作符可以選擇配置來保留排序順序。在計划中輸入的行已經排序的時候對后面的操作符是很有用的(沿用開始的排序,或者作為一個從索引中讀取的已經排序的序列)。如果交換操作符沒有保留上順序,在交換器需要重新建立排序后優化器將必須引入額外的排序操作符。普通的請求排序輸入的操作符包括流聚合、分段和合並連接。圖11展示一個需要重新分配流的排序操作:

1250-Fig11.jpg

圖11: 保留順序的重新分配流

 

 

注意合並交換自身不會排序,它要求輸入行必須進行排序嗎。合並交換是效率更低比非保留順序的,並且是有一定的性能問題的。

最大並行度

微軟給出的官方指導:

image

請遵循以下准則:

1. 服務器的有8個或更少的處理器,使用下列配置其中N等於處理器數:MAXDOP=0到N。

2. 對於具有NUMA配置的服務器,MAXDOP不應超過分配給每個NUMA節點的cpu數。

3. 超線程已啟用的服務器的MAXDOP值不應超過物理處理器的數量。默認為0表示數據庫引擎自行分配。

image

 

總結

    通過一個簡單的查詢引入並行,並且對照了一個真實的數糖豆的案例,為了研究SQLServer中並行的使用的優點,暫時沒有考慮與多線程設計相關的復雜情況。我們發現了並行查詢計划可以包含多個並行和串行區域,通過交換操作符綁定在一起。並行區域擴展出多個串行查詢,每個串行都使用了獨立線程來處理執行上下文的任務。交換操作符被用來匹配線程之間的行並且在並行計划中實現與不止一個線程交互。最后,我們看到了SQLServer 提供了一個Parallel Page Supplier,當保證是正確的結果集時,允許多個線程可以協同掃描表和索引。

    除此之外還介紹了交換操作符以及操作符內部詳細構造以及最佳實踐中的並行度配置。這里都這是從概念上做了介紹,如果線下有問題可以一起研究選擇出最好的實現方式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM