深入解析SQL Server並行執行原理及實踐(上)


在成熟領先的企業級數據庫系統中,並行查詢可以說是一大利器,在某些場景下他可以顯著的提升查詢的相應時間,提升用戶體驗.如SQL Server, Oracle等, Mysql目前還未實現,而PostgreSQL在2015實現了並行掃描,相信他們也在朝着更健壯的企業級數據庫邁進.RDBMS中並行執行的實現方式大抵相同,本文將通過SQL Server為大家詳細解析SQL Server並行執行的原理及一些實踐.

准備知識

硬件環境-在深入並行原理前,我們需要一些准備知識,用以后面理解並行.首先是當下的硬件環境,社會信息化建設,互聯網的普及,硬件工藝的大發展…我們現在的硬件設備的性能雖然已經不復摩爾定律的神奇,但已經相當豐富了,存儲上15K RPM的硬盤,SSD,PCI-E SSD使得磁盤作為數據庫系統的”終極”瓶頸得到了一定的緩解,而內存上百G內存也早已不是小機的”特性”了,PC Server上已經很普遍.在處理能力上,由於現今物理工藝上的極限,單顆CPU處理能力提升困難,系統架構已經朝着多顆多核的架構上迅猛發展,如8路CPU的服務器也已經有一定的使用案例了.總之我們的硬件資源越來越豐富,而本文所講的並行查詢查詢主要是針對上面敘述的多個CPU協同工作,用以提升SQL查詢中的CPU-BOUND響應時間.(雖然在並行掃描(SCAN)的時候實際中同樣可以提升查詢的響應時間,但這我們就不細述了)

SQL Server關於”任務”的相關知--Schedulers,Tasks,Workers. Schedulers是指SQL Server中SQLOS識別到的硬件環境中的邏輯CPU,它是管理SQL Server計算工作的基本單位,它提供”線程”(workers)用於分配相應的”CPU”資源用於計算.Tasks實際就是SQL Server中的工作單元,本質上就是一個函數指針(C++),用於指向需要執行的代碼.比如LazyWriter實際上就是去調用相應的Function(sqlmin!Lazywriter()).Workers 如果說Tasks是指向工作的地方,那么Workers是處理相應的工作,它綁定到Windows線程的方式用於計算.OK,現在說一條SQL請求SQL Request=Task+Worker應該能理解了吧.為了加深理解,我們可以看圖1-1

 

 

                                  圖1-1

 

最終回到我們的並行查詢,實際就是多個tasks在多個schedulers上同時運行,提升響應速度!

關於SQLOS,微軟在SQL Server 2005年引入,可以說是個重大的突破,就像Michael Stonebraker(2014圖靈獎得主)老爺子的早年Paper” Operating System Support for Database Management”中敘述的那樣,SQLOS,比OS更懂數據庫,感興趣的朋友自行搜索下.限於篇幅這里就不細深入SQLOS了,給出一張簡單架構圖大家感受下J 如圖1-2

 

                                 圖1-2

OK准備知識完畢,我們進入並行查詢執行吧

     並行相關概念

串行執行計划--在了解並行執行之前,我們先了解下串行執行計划的相關知識.其實很簡單,關於執行計划本身這里就不介紹了,只是介紹下與本文相關重要元素.

串行執行計划實際就是由單個線程(thread)執行完成,單個執行上下文(execution context)

執行上下文就是指執行計划中用於執行的一些信息,如對象ID,如執行計划中使用到的臨時表等等.在執行中實際上就是單個(核)CPU在工作,如圖2-1

 

                        圖2-1

而並行執行計划,顧名思義,就是多個(核)CPU在同時工作,用於提升CPU-Bound的響應時間,對應串行,它有多個線程,多個執行上下文,但也會消耗更多的資源.但對於磁盤IO,SQL Server認為是木有幫助的,我們通過一個簡單的實例看下:

注:文章中使用的AdventureWorks,網上可以自行搜索下載

---串行執行

select COUNT(*)

from dbo.bigTransactionHistory option(maxdop 1)

--並行執行

select COUNT(*)

from dbo.bigTransactionHistory option(maxdop 2)

通過觀察上面兩條簡單語句的執行計划可以發現,在預估Subtree cost中 (資源消耗量)實際上單CPU與雙CPU相比只是CPU預估減半,IO預估不變.如圖2-1-a

 

                                     圖2-1-a

讓我們真正的進入並行執行計划,每個並行執行計划都有一個主的交換操作(root exchange),即圖形執行計划中最左面的Gather stream運算符(后面會講到)(SQL Server有圖形執行計划,不小小伙伴很羡慕吧J)而所有的並行執行計划都會有一個固定的串行部分—即最左邊Gather stream運算符中所有靠左的部分(包含Gather中消費者,后面會講到生產-消費模型)的所有操作,他是由主線程Thread Zero控制,同時它的執行上下文也是context zero,如圖2-2所示

 

                                   圖2-2

 

而並行區域,顧名思義就是root exchange所有靠右的部分,而並行部分又有可能有多個分支

(Branches),每個Branch都可以同時執行(分支有自己的tasks),分支自身可以是並行,也可以是串行.但分支不會使用主線程thread zero.關於分支大家可以用如下語句自己看下相關執行計划屬性(SQL Server 2012版本及之后版本可以顯現) 如圖2-3所示

如圖2-3-2,我的最大並行度設置為4,有三個branches,而這里我使用的線程數就是

4*3=12,再加上一個主線程 thread zero 這個並行查詢我所使用的線程總數為13個.

select a.productid,count_big(*) as rows

from dbo.bigproduct as a

inner  join dbo.bigtransactionhistory as b

on a.productid=b.productid

where a.ProductID between  1000 and 5000

group by a.productid

order by a.productid

 

 

                                     圖2-3-1


                                    圖2-3-2

 

而並行和串行的區別聯系,大家可以理解成下圖2-4

主線程在串行中實際就是他的執行線程

 

                             圖2-4

 

圖2-3中,並行的表掃描后聚合,實際上是等於兩個串行的表掃描聚合.這個提到並行表掃描table scan(range scan)就稍微展開下:在SQL Server 2005及以前版本中並行掃描實際上是

” parallel page supplier”及每個線程掃描的單位是數據頁,掃描多少有數據頁中含有的數據行多少決定,而在SQL Server 2008及以后是”Range Enumerators” 每個線程掃描的數據實際上是由數據的區間分布決定的,至此也就有了我們在08及以后版本中經常看到的          “access_methods_dataset_parent”閂鎖等待,你可能會疑問怎么高版本還帶來新問題,實際上在 ” parallel page supplier”中的問題也是不少的,比如在快照隔離級別下的數據頁掃描確認問題等等,總得來說還是更高效了.這里需要大家注意的是並行掃描只支持前滾掃描(forward),不支持反向掃描(backward)感興趣的朋友可以根據下面代碼自行測試.

 

/*************************並行掃描相關測試*********************************/

----並行掃描中只有前滾掃描才可以並行

USE [AdventureWorks2008R2]

GO

select color,COUNT(1) from [bigProduct]

group by Color option(querytraceon 8649)--- Parallel scan only forward

go

CREATE NONCLUSTERED INDEX [inx_color] ON [dbo].[bigProduct]

(

         [Color] desc----asc can parallel desc can not

)WITH ( DROP_EXISTING = ON) ON [PRIMARY]

---修改索引排序規則(asc,desc)

GO

select color,COUNT(1) from [bigProduct]

group by Color option(querytraceon 8649)--- Parallel scan only forward

 

/*************************並行掃描相關測試*********************************/

 

看到這里不少朋友可能有疑惑了,上文中又是串行,又是並行,又是多tasks,到底什么時候用串行,什么時候並行,用多少個tasks啊,實際上SQL Server實例級有兩個設置:並行閾值”cost threshold for parallelism” 即當Subtree cost(前面提到的估計資源消耗)大於設定值時優化器才會觸發並行優化,進而可能生成並行執行計划.二:最大並行度(max degree of parallelism)用於設置分支(branches)中到底可以多少個CPU同時工作.這里給大家畫個簡單的圖,大家就一目了然了.如圖2-4

 

                         圖2-5

至此,並行相關的基本概念已經介紹完畢,接下來讓我們深入並行..

 

Exchanges

顧名思義,就是交換,在並行里指的是threads間的數據交換,這也是在只有並行執行的情況下才會有的操作符.具體到SQL Server中有三種exchanges 操作,分別為Gather Streams, Repartition Streams,以及Distribute Streams其對應起到的作用就是聚集,重定向,分發Threads間的數據,操作符如圖3-1所示

 

圖3-1

 

而實際上上述運算符每一個都是兩個運算符的”合集”這里就是我們之前提到的生產者與消費者操作.操作符中右邊為生產者,將生成的數據放入packets中,而左邊為消費者,從packets中將數據取出.如圖3-2所示

 

                               圖3-2-1

 

                                 圖3-2-2

這里解釋下我們在SQL Server中常見的CXPACKET等待,不少同學都將這個等待看做並行中某些線程工作快,某些線程工作慢而產生的差異,這個對,但說法不專業,實際上根據生產者消費者模型來看分析是:

CXPACKET Waits=Class eXchange PACKET

針對生產者:所有的 Buffer packets 都已經被填滿,無法繼續生產(填充)

針對消費者:所有的Buffer packets都是空的,沒有數據可以消費

在生產者與消費者的模型中,數據是通過一定規則由生產者流動到消費者那里,在SQL Server有如下5種規則

 

Broadcast:廣播,當數據量較小時將生產者的所有數據都廣播到所有的消費者中.阿里的朋友在分布式中間件中小表廣播應該有不少應用吧J

 

Hash:通過哈希函數將制定的數據一個或多個欄位哈希化(簡單的如取模),根據不同的值填充到不同的Buffer packets中供消費者使用.

 

Round Robin 顧名思義,將數據順序逐條地添加到每個Buffer packets中.

 

Demand 之前的都是發送數據,而這個是消費者從生產者中拉數據,如某個消費者拉某個常量的數據,這個只在分區表中采用.

 

Range 顧名思義,根據數據中某些欄位的分布,分別填充到不同的Buffer packets中,這個一般在並行索引,統計信息重建時采用.

有了生產者和消費者的數據使用方式,你可能覺得還差點什么吧?沒錯,數據是否是有序的呢,SQL Server中exchange分別Merge Exchange 和Non-Merge Exchange,分別對應數據在exchage時是否要求有序,當然大家也都知道了有序的成本Merge Exchange明顯高於無序

如圖3-3所示

 

                                     圖3-3

Parallel joins

最后我們來談談SQL Server中的並行Join.SQL Server中對於三種基本join: nested loop join ,merge join, hash Join都支持,我們分別說明下他們的優缺點,在今后的並行優化時,你可能用到.在這里我就不復述三類join鏈接的基本算法了,不懂的同學wikipedia下

Parallel merge join 如圖4-1

將參與join的key(兩個表)都進行hash化,然后同時進行匹配.

並行merge join幾乎無優點,缺點倒是不少,首先是需要merge exchange,其次增加CPU本身對性能幫助實際不大(參考相應算法),而且merge join還會增加並行死鎖的幾率.所以實際生產中我們還是應盡量避開merge join.

 

                圖4-1

 

 

Parallel hash join

Build階段,如果數據量小,所有broadcast到所有threads中 如圖4-2-1,否則就將join key 哈希化,進行匹配,如圖4-2-2

這里應注意parallel hash join是Non-merge exchanges,且隨着CPU的增加,性能是線性擴展的(具體參考相應算法),但在parallel hash join中應注意可能由於統計信息問題,復雜join導致的hash join在構建,探測階段都有可能因為內存不足而溢出的現象,從而拖慢查詢.關於溢出可以使用跟蹤,擴展事件捕捉等.

 

                         圖4-2-1

 

                         圖4-2-2

 

Parallel nested loop join

外表多個threads同時運行(如掃描數據),而內表則在每個thread上串行同時進行匹配.

Parallel nested loop join在實際生產中可能為我們解決許多一些棘手問題,比如他會減少並行計划中的exchange使用,使用更少的內存等等.但也應注意可能由於數據分布傾斜,nested loop預讀等情形導致的性能提升有限,而消耗反而相比串行提高等問題.而且SQL Server的優化器本身是”不喜歡並行循環嵌套的”,有時我們需要特定的寫法才能實現他.如圖4-3

 

                       圖4-3

 

 

最后我們再提下Bitmap過濾吧,SQL Server是沒有位圖索引的,這個也頗為詬病,但實際SQL在執行時是有可能用到位圖過濾的.

簡單說下SQL Server位圖過濾實現方式(具體大家也可以wikipedia bloom filter)

實現方式:通過構建一個長度X的位數組(bit array)(所有位為0),將要匹配的集合通過哈希函數映射到位數組中的相應點中(相應位為1),當判斷一個值是否存在時找bit array中對應位是否為1就可以了.這個過程由SQL Server內部自己完成. 如圖4-4-1,圖4-4-2我將一個現有數組哈希化,然后在其中搜索 ”悟空..”是否在數組中

 

                  圖4-4-1

 

                   圖4-4-2

 

好了,SQL Server 相關的並行知識就給大家介紹這么多吧,后面有時間給大家帶來相關的實踐應用.

更多Inside SQL Server請關注

微信公眾號InsideSQLServer及我的新浪微博@shanksgao

 


免責聲明!

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



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