理解和使用SQL Server中的並行

 

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

    充分高效地利用並行查詢須要對調度、查詢優化和引擎工做等有一個比較好的瞭解,可是針對通常場景的應用咱們只須要如何常規使用便可,這裏也就不深刻描述了,感興趣能夠一塊兒討論。算法

    那麼這裏我就簡單介紹下SQLServer中並行的應用?sql

什麼是並行?

咱們從小就據說過「人多力量大」、「人多好辦事」等,其思想核心就是把一個任務分給許多人,這樣每一個人只須要作不多的事情就能完成整個任務。更重要的是,若是額外的人專門負責分配工做,那麼任務的完成時間就能夠大幅減小了。數據庫

數糖豆

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

    若是你有四個朋友幫助你去作這個任務。你就有了多種策略來安排這個數糖豆任務,那讓咱們模仿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,當保證是正確的結果集時,容許多個線程能夠協同掃描表和索引。

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

相關文章
相關標籤/搜索