SQL研究 - 查詢語句被處理的過程

我們向數據庫提交了一個查詢,假如我們的語句是正確的,我們理所當然的期望的到一個正確的結果。

 

如果要牛角尖一下,怎樣纔是一個正確的查詢呢?

顯然首先你查詢的對象是存在的,不管是表還是視圖,函數或者存儲過程。你指定的條件是合法,不管是Jion的條件,Where中的條件,還是Having中的條件。你希望展現的列是存在的,如果一個表裏存放的機器的話,你不大可能從裏面選出一個性別的字段。

那麼我再問你,如果你的查詢要1天才能完成,而稍作改動就可以在1分鐘內完成,你的查詢還是正確的嗎?

如果你查詢讓數據庫引擎佔用的內存暴漲,讓服務器幾乎陷於停頓,而這個查詢本可以讓1000個人同時使用而暢通無阻,你的查詢還是正確的嗎?

 

可見要寫出一個正確的查詢,絕不僅僅需要知道SQL的語法,還需要了解這個查詢語句是如何被處理,如何從硬盤上那一堆0或1中組合成我們需要的結果的。本文就試着回答這個問題。

 

首先讓我們來看一個完整的SQL語句,它包含所有可能的元素:

 

(8)  SELECT (9) DISTINCT (11) <TOP_specification> <select_list>
(1)  FROM <left_table>
(3)    <join_type> JOIN <right_table>
(2)      ON <join_condition>
(4)  WHERE <where_condition>
(5)  GROUP BY <group_by_list>
(6)  WITH {CUBE | ROLLUP}
(7)  HAVING <having_condition>
(10) ORDER BY <order_by_list>

 

請注意那些寫在括號中的數字,它們代表的是各個部分被處理的順序,每一步生成一個虛擬表,該表作爲下一步的輸入並生成一個新的虛擬表。

做一個簡單的說明把:

首先,From後的兩個表做笛卡爾積,插入VT1中;

接着,使用On的條件,將VT1中符合的行放入VT2中;

然後,將VT2中符合Where條件的放入VT3中;

隨後,Group by 將VT3分組放入VT4中;

With Cube|RollUp進一步產生分組的方法,新的行和VT4加入VT5中;

後面的您就可以明白了。

 

雖然所有的SQL都遵循這一個過程,過程的每一步的代價是不同的, 甚至訪問表的順序也會導致性能的巨大差異。作爲數據庫引擎中最重要的組件,查詢優化器的功能便是爲查詢語句找到一個最優的執行計劃。

查詢優化器有兩個輸入,一個是我們的查詢語句,另一個是存放在數據引擎的統計信息,例如表格有多少行,索引樹有多深,每個索引也多少鍵值等。基於這兩個輸入,它輸出的查詢計劃包括源表被訪問的順序,以及從源表中提取數據的方法,可以是掃描表的所有Page,也可以是查找索引樹。

關於執行計劃您可以通過Management Studio的查詢窗口上右鍵菜單:Include actual execution plan 來查看。它顯示的執行計劃包含了SQL被分解成的所有操作,以及各個操作的代價。我們在此不詳細討論這些操作。

 

正如上面所講的,翻譯查詢計劃並不是一個容易的工作,尤其是在面對複雜的查詢的時候,因爲要比較衆多的可能路徑的代價。我們顯然不希望這樣一個寶貴的執行計劃被隨便的抹掉,這樣複雜的過程不必要的重複。

 

數據庫引擎也不願意,所以它用一塊專門的內容緩衝區來存放執行計劃。

 

在數據庫引擎裏,執行計劃由QueryPlan和執行上下文組成。QueryPlan都是一個只讀的,可重入的數據結構,並且從來不會超過兩份,一份是順序執行,另一份是並行執行。執行上下文存放專用於一次調用的內容,例如參數,用戶權限等。

執行計劃在緩衝區中並不會一直存在,它有一個參數表示它的生命值。該值在執行計劃每次被調用時就會增加,每一個脈衝,生命值減少1.因此產生該計劃的代價越大,它留在在緩衝區的時間就越長。

 

當數據庫執行一個查詢語句,爲之生成執行計劃之前,它會首先掃描存儲過程的緩衝區,以尋找匹配的執行計劃。在匹配執行計劃時,可能採用全字符匹配,也可能採用簡單參數化的方法。如果用戶將數據庫的Parameterization選項設置成Forced的話,執行計劃的源查詢語句會被全面參數化,即所有常數會被替換成佔位符,這個佔位符可以和任何其他常數匹配,這樣就會導致更多的查詢語句被匹配到該查詢計劃。

 

如果數據庫服務器是多CPU的,數據庫引擎還會產生並行執行計劃。一類特殊的操作:Exchange負責處理併發的相關操作,成員包括Distribute Streams, Repartition Streams以及Gather Streams。查詢計劃的併發度受查詢語句本身和查詢對象屬性的影響。通常來說系統閒散線程越多,CPU越多,查詢對象行數越多,併發可能性就越大,併發度也越高。另外我們可以配置併發查詢的起始值和併發查詢的最大併發度。有時候併發度太高就意味着佔用更多的資源,着在多用戶環境中並不總是友好的。

 

如果查詢的數據分佈在不同的數據庫服務器上,我們需要藉助於連接裝置進行訪問,最簡單的連接方式是Linked Server,如下所示:

SELECT Title, HireDate
   FROM DeptSQLSrvr.AdventureWorks.HumanResources.Employee

或者通過OpenRowSetOpenDataSource

SELECT *
FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
        'c:/MSOffice/Access/Samples/Northwind.mdb';'Admin';'';
        Employees)

前者適用於較經常的訪問,後者適用於不那麼頻繁的訪問。事實上,SQL Server查理查詢的兩個關鍵組件是查詢解析和存儲管理,她們之間使用OLE  DB來連接。當訪問遠程Server的數據時,本地的解析器將查詢分解成具體的操作,通過OLE DB調用遠端server的存儲管理組件,後者處理這些操作並返回結果。

發佈了36 篇原創文章 · 獲贊 0 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章