1. 程式人生 > >T-SQL執行內幕(5)——執行

T-SQL執行內幕(5)——執行

本文屬於SQL Server T-SQL執行內幕系列

 



    一旦優化器選擇了開銷最低的預估執行計劃之後,就會把預估執行計劃轉換成實際執行樹(Actual Execution Tree)進行查詢執行。樹的每個節點都是一個操作符。操作符及一系列的有向箭頭(代表資料流的方向及結果集的資料量(箭頭粗細)) 組成了整個執行計劃,這裡指的是圖形化執行計劃。

    所有的操作符都實現一個具有三個方法(open()/next()也叫GetRow()/close())的抽象介面。執行過程會迴圈呼叫包括根節點在內的每個操作符的open()方法,然後重複呼叫next()方法直到方法返回false,最終呼叫close()。

    執行樹的根節點操作符會按同樣的方式依次呼叫下級的每個子操作符,而這些子操作符又以相同方式呼叫自己的下級操作符,以此類推到最後級別的操作符(葉子節點)為止。然後葉子節點進行操作之後把資料返回給自己的父操作符,然後父操作符把資料處理完之後再彙總給自己的父操作符,以此類推最終把結果彙總給執行樹的根節點並返回給客戶端。比如下面的簡單查詢:


    紅色箭頭代表執行的方向,黃色箭頭代表資料返回的方向。

    在葉子節點的操作符通常是物理訪問操作符(比如上圖的最右端兩個聚集索引掃描),用於實際地從表和索引上查詢資料。在中間結點的操作符進行一些如過濾資料、JOIN、排序等操作的資料操作符。後續小節會陸續介紹。

    效能優化的順序通常是從葉子節點(如上圖的聚集索引掃描)開始自下而上優化,因為葉子節點代表了後續要處理的資料集的量和訪問資料的方式,如果過程資料非常多,不僅佔用快取和CPU等各種資源,也會在資料集關聯過程明顯降低效能。

    對於並行操作的查詢的執行,使用一種叫Exchange操作符,這個操作符分配多個執行緒用於執行,然後把子樹的執行緒結果彙總作為這個操作符的輸出,是一個典型的“多生產者單消費者”模式。在並行執行的過程中,一個worker會按照伺服器配置,按實際情況被拆分成多個workers,每個workers分別執行一部分的操作,然後再彙總,這種情況下最常見的兩種問題就是執行緒間阻塞和執行緒間死鎖。比如如果一個庫有多個數據檔案,而且資料檔案的大小及所在的磁碟I/O非常不均勻,那麼在檢索資料的時候,I/O快的盤上的資料可能會很快被檢索,但是由於慢盤上的執行緒檢索慢,就會出現木桶效應,整個操作都需要等待最慢的那個執行緒完成為止。另外如果多執行緒對資料出現不合理的訪問順序及寫法、索引等因素都不理想的情況下,很可能會出現死鎖。這種死鎖通常可以通過改進語句效能來減緩。

    這種模式(不是指並行執行)不僅用於查詢,還包括資料修改(增刪改),但是對於一些無法優化的語句如Insert into… values…等,會產生一個普通計劃(trivial plan)。不管複雜的執行計劃還是普通執行計劃,執行時還是以迭代呼叫next()方法直到執行樹完成為止。

    在各種操作符中,有些是很簡單的操作符,如top(N):先對其呼叫open()方法,然後呼叫next(),由於TOP操作符必有子操作符(TOP的資料來源),所以這個操作符只需要對子操作符呼叫next()方法並獲取一個值(呼叫一次),直到呼叫完N次為止,此時top操作符會收到來自於子操作符的false訊息然後停止對子樹的迭代。

    但是也有一些複雜行為的操作符,如巢狀迴圈聯接(nested loop),它需要持續跟蹤關聯的兩表(內表和外表)的位置,對外表(圖形化執行計劃中處於上方的表)呼叫next(),然後在內表中呼叫next()並進行join條件的匹配,直到條件滿足後進行外表的下一次呼叫。

    除了簡單和複雜這兩個分類之外,某些操作符還具有“走走停停”(stop-and-go)行為,也就是直到所有子操作符的輸入處理完成之後才輸出父操作符的結果,比如SORT:開始呼叫next()時並不會返回資料,直到所有子操作符的資料被檢索並排序後,才會返回結果給sort操作符。

    有些操作符如HASH JOIN同時具有複雜和stop-and-go行為:為了建立hash表,需要呼叫build side(其中一個表)的next()方法直到操作符返回false,然後呼叫probe side(另外一個表)的next()方法直到與build side資料匹配為止,才返回資料。

    當資料檢索完畢之後,在記憶體充足的前提下,很多相關資料會快取到記憶體的資料快取中(Data Cache),這部分是SQL Server佔用記憶體的最大部分。也是為什麼經常有人說SQL Server很吃記憶體的“表現”,因為資料快取在記憶體中,記憶體的速度遠超於磁碟,避免了磁碟IO的開銷。從而最大化檢索速度,隨著技術的發展,在SQL 2014開始出現的In-Memory技術又進一步把記憶體速度提升到極致。