在成熟領先的企業級數據庫系統中,並行查詢可以說是一大利器,在某些場景下他可以顯著的提升查詢的相應時間,提升用戶體驗.如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