查詢執行的總圖:sql
根據總圖的流程,詳細說明每一個部分:數據庫
1. 請求(Request)緩存
SQL Server是C/S架構的平臺。與它交互的惟一方式就是發送包含數據庫命令的請求。應用程序和數據庫以前的通訊協議叫作TDS(Tabular Data Stream)協議。應用程序可使用如下幾種實現了TDS協議的客戶端:網絡
TDS的請求分爲如下幾類:架構
這種請求只包括T-SQL文本,不包含參數,可是能夠包含本地變量。在SqlClient中帶有空參數列表的SqlCommand對象上執行SqlCommand.ExecuteReader(), ExecuteNonQuery(), ExecuteScalar()或者ExecuteXmlReader(),就會是批請求。經過Profile會觀察到SQL:BatchStarting事件。併發
它包含過程標識符(Procedure Identifier,用於)和任意數理的參數。不一樣的過程標識符表明了不一樣的系統存儲過程。執行帶有非空參數列表的SqlCommand對象時,就是這種請求類型。經過Profile能夠觀察到RPC:Starting事件。ide
批量加載是批量插入操做所使用的一種特別的請求類型。例如BCP工具、OleDB的IRowsetFastLoad接口和SqlBulkcopy類。它是惟一一種在TDS協議中不須要完成包發送就能夠開始執行的請求。開始執行後,就可使用數據流中的數據進行插入操做了。工具
2. 任務(Task)oop
當完整的請求到達數據庫引擎,SQL Server會建立一個Task去處理此請求。能夠經過sys.dm_exec_requests觀察請求狀況。一個任務表明一個完整的請求,而不會是請求的一部分語句。一樣,對於請求中的部分語句,也不會建立新的任務。有些請求的中語句會並行執行,而Task會生成sub-task處理並行。當客戶端取走全部請求返回的結果集中的數據後,Task就完成了。學習
能夠經過sys.dm_os_tasks觀察Task狀況。
3. 工做進程(Workers)
根據新請求所建立的Task,初始狀態是PENDING。這個階段,SQL Server並不知道請求的內容。Task需要執行這個請求,引擎就會分配worker去執行。(是分配,不是建立)
Workers是SQL Server的線程池。在SQL Server啓動過程當中會初始化必定數量的Workers。能夠經過Max_Worker_Threads參數,按須要配置最大線程數。只有Worker才執行代碼。當沒有空閒的worker時,task變成Pending狀態。worker完成task後,變成可用狀態,纔會去選擇Pending狀態的task執行。
在SQL批請求中,worker選擇task後,執行批請求中的每個語句。很明顯,批請求中語句,是串行執行的。前一個完成,纔會開始下一個。批中的某些語句會並行執行,這個並行是Task建立sub-task來完成的。而每個sub-task會經歷和task同樣處理過程(如等待可用的worker來選取它並執行)。
能夠經過sys.dm_os_workers查看worker及其狀態。
4. 語法解析和編譯(Parsing & Compilation)
當task開始執行,首先它要弄明白請求的具體的內容。這個階段SQL Server對請求中的T-SQL文本進行語法解析並生成表示請求的抽象語法樹(abstract syntax tree)。整個請求會被解析和編譯。若是在這個階段產生錯誤,則會返回編譯錯誤,並結果任務並釋放task和worker。
編譯T-SQL不會產生像本地CPU指令同樣的可執行代碼,也產生相似於字節碼的東西。它產生查詢計劃(Query Plan)。查詢計劃描述了數據訪問的路徑和訪問對象的方法。
5. 優化(Optimization)
優化是從不少個查詢計劃中選擇出最優的一個。SQL Server採用基於成本的優化器。它會估算全部可能(大多數)的查詢計劃的成本,並選擇出成本最低的一個。成本主要經過計算查詢計劃須要讀取的數據大小(data size)。爲了知道數據大小,SQL Server須要知道每一個表的大小和列值的分佈狀況(經過統計信息數據)。成本還會考慮CPU和內存使用量。再經過一個公式將這些數據綜合個成本值,而後選取出成本值最小的那個執行計劃。
優化過程須要消耗時間和CPU,因此一當查詢計劃最終生成,則會被緩存到計劃緩存中,以備重用。
6. 執行(Execution)
一旦優化器選定了執行計劃,請求就能夠開始執行了。執行計劃會被轉換成實際的執行樹。樹中的每一個節點是一個操做符。全部操做符都實現三個抽象接口:open()、next()和close()。循環執行包括調用根節點的open(),而後逐級調用next()直到返回false,再調用close()。
葉級節點一般是一些物理數據訪問操做符(訪問實際的數據和索引),中間節點一般是一些實現數據過濾、排序和鏈接等數據操做的操做符。並行執行有一個專門的操做符:Exchange操做符。Exchange操做符發出多個線程,每一個線程執行一個查詢計劃的子樹,而後再使用multiple-producers-one-consumer 方式聚合全部子線程的輸出。
數據修改操做也適用於這個執行方式。
有些操做符很是簡單,如TOP(N)。當調用它的next(),它會去調用子節點的next()並記錄數據。當重複執行N次後,它就返回false,並終止對子節點的調用和對相應分支子樹的迭代執行。
有些操做符很是複雜,如nested loop操做符。這須要跟蹤內外子節點循環迭代的位置,調用外節點的next(),值重繞(rewind)內節點並不斷調用內節點的next()直到找到匹配的值。
有些操做符須要等到獲取到它的全部子節點的輸出數據時,才能產生本身的輸出數據。這種行爲方式也叫stop-and-go。如sort操做符,它第一次調用netxt(),不會返回數據,須要等到全部的數據被返回並排序,這才能返回數據。
HASH JOIN是一個很是複雜而且又是stop-and-go類型的操做符。爲了構造hash表,它要調用構建側(build side)節點的next(),直到返回false。而後再調用探測側(probe side)的next(),直到找到在hash表中找到匹配的值,而後返回。重複探測側的操做,直到next()返回false。
7. 返回結果(Results)
查詢一旦開始執行就能夠開始返回數據給客戶端程序。當執行樹開始產生返回數據後,最頂端的操做符會負責把數據寫入網絡緩存併發送給客戶端。執行中產生的返回結果,不會被緩存到任何地方,一但產生就開始返回給客戶端。
顯然,經過網絡返回數據給客戶端會受到網絡流量控制協議的約束。若是客戶端不能及時地取走返回的數據,最終會阻塞數據發送方的發送行爲,並使得查詢執行被掛起。當客戶端的數據接收能力正常後,發送方的發送行爲和查詢執行會被重置,正常產生返回結果數據。
OUTPUT參數的輸出值,只能在執行計劃完成後,才能被寫入到數據流中。因此它也只能在全部返回結果被客戶端取走後,才能被讀取到。
總結:
1. 這是一篇譯文,計劃分爲3部分。學習之用,非逐字翻譯,不少是結合本身的理解譯的,與原文內容相比,有一些增和刪。