在講SQLSERVER內部原理的之前,我覺得非常有必要向大家介紹一下SQLSERVER的歷史。
讓我們站在1999年,看看計算機數據庫業界到底處於什麼狀態。
1999年,Oracle已經於1998年9月發布了Oracle 8i(可能中文版在1999年才來到中國)。Oracle 8i支持用JAVA編寫存儲過程,支持XML,支持Linux。
1999年1月,SQLSERVER7正式發布。SQLSERVER7重構了整個數據庫引擎(相當於重寫了SQLSERVER)。SQLSERVER第一次完整性的支持了行鎖(有沒有搞錯,過去人是怎麼使用數據庫產品的。1988年,Oracle6就支持行鎖。另外1988年,Oracle就開始研發ERP產品。誰說Oracle是ERP門外漢,可以參考這個)。
看看他們倆的前一個版本。如果你入行比較晚(2000年以後),可能對以下文字更感到驚訝。
1992年,Oracle7發布。有了存儲過程、觸發器、引用完整性校驗、分布式事務處理。(天哪,Oracle7才有了這些東西)。
1995年,SQLSERVER6發布。SQLSERVER6是微軟真正意義上的第一個數據庫產品(真是爆料,大家沒想到SQLSERVER6才是微軟第一個數據庫產品,那版本6之前的5、4、3、2、1是怎麼度過的)。因為1994年,微軟和Sybase掰了(Sybase是第一個運行於PC上的C/S數據庫產品)。微軟為了進入數據庫產品領域,自己又沒有經驗,於是和Sybase一起合作(當時微軟是全世界第一大軟件公司,微軟1986年上市。Sybase有產品,缺錢。微軟缺產品,有錢。於是一拍即合)。直到1994年,微軟也不需要Sybase了(已經學會了數據庫技術),Sybase也感覺微軟太狼子野心,於是合作分裂。微軟開始自己做自己的數據庫。
歷史說完。我們言歸正傳。
很多入門級做管理軟件的,SQL語句玩的熟練,從子查詢到Having到交叉表統計SQL都能做出來,甚至存儲過程能寫2000多行,游標、自定義函數、觸發器、約束用的眼花缭亂。再入點門,在SQL查詢器中可以使用SQL分析優化索引,用SQL Profile可以跟蹤SQL,甚至在性能查看器中監測SQLSERVER內存、CPU、線程、I/O的運行狀態,甚至為自己會使用DBCC而沾沾自喜。
你是如此熟悉SQLSERVER,又是對SQLSERVER如此陌生。
我今天就用架構的角度來給大家分析一下SQLSERVER架構和原理。短短一篇博文肯定只能面上的多一些,深一層的可能需要連載數篇文章甚至一塊大磚頭書才能講完整。不過,我希望我的博文能夠拋磚引玉,使大家能從一個過去沒有想過的角度去看SQLSERVER。
SQLSERVER,作為一個數據庫產品,我個人認為,最重要的就是兩大塊:存儲引擎和查詢引擎。
其他的日志、事務、鎖、索引等等都是圍繞他們來工作的。
SQLSERVER是C/S產品,所以一條SQL語句要讓SQLSERVER執行,必須要傳輸到SQLSERVER服務器端。傳輸,我們當然知道需要NetBEUI、TCP/IP等等網絡傳輸協議。但是光有這些還不行。客戶端如何發,服務器端如何收,如何確認發的和收的正確完整,如何確實發的和收的已經結束,如何發和收能跨越各種網絡協議(如UNIX和WINDOWS和NOVELL通訊),如何保證數據安全校驗,如何保證數據收發是同步還是異步,就需要在網絡傳輸協議之上再構造一層協議。SQLSERVER既支持IPC機制,也支持RPC機制。你想想你的管理軟件開發平台是否有這一層。當然,現在的消息服務器已經專業的提供了這一機理,可靠的、安全的、高效的、異步的、消息壓縮、消息拆分、智能路由、集群,跨越不同的操作系統、不同的編程語言、不同的通訊協議、不同的硬件平台的消息數據傳輸。可能你過去不了解消息中間件,通過這一案例可以知道消息中間件的用途。
SQL語句被可靠無誤的發送到了服務器端,SQLSERVER引擎中第一個模塊就來接待這個SQL數據。這個模塊的名字叫:Open Data Services。它監聽新的連接;清除失敗連接;將結果集、消息和狀態返回給客戶端。
SQLSERVER客戶端和服務器端之間傳輸數據,數據包是有格式的。在SQLSERVER中被稱為tabular data stream。這個數據流是令牌控制客戶端和服務器端對話(否則,客戶端說了N句話,服務器端返回N句話,沒有令牌就混在一起了,不知道哪個回答是對應哪個請求的)。我們往往不能直接和Open Data Services打交道,把數據放進來。而是我們必須通過ODBC、ADO或DB-Library來發送tabular data stream。而SQLSERVER返回的數據結果,也是通過這些ODBC之類發回tabular data stream。你看看SQLSERVER設計的多巧妙,一個通用數據訪問接口屏蔽了你和SQLSERVER之間,就如同WINDOWS API屏蔽了內核讓你無法訪問,就如同DirectX屏蔽了UI和外設的操控。
SQL語句-ODBC-編碼成tabular data stream-IPC或RPC-網絡協議-IPC或RPC-解碼tabular data stream-ODBC-Open Data Services。
Open Data Services監測客戶端連接。如果並發太多,它會創建連接,如果服務完,它會自己維護連接歸入池中。在池中保留一段生命期,它會自己釋放連接。如果有的客戶端連接中途突然斷掉(如客戶端重啟了),它在偵聽後無回應,它也會自己整理自己的連接的。我們在SQLSERVER線程中看到的連接,就是Open Data Services創建的。
Open Data Services有了連接(可能是創建的可能是從池裡拿出來的,池化、創建、銷毀都是非常講究技能的。池化多少,上下文資源如何保留,池化多長時間,什麼時候該銷毀,調度不當就會嚴重消耗資源),就把SQL接住。這時,是接到了Open Data Services的讀緩沖區裡面。這個緩沖區為高性能處理數據的SQLSERVER帶來一絲喘息機會,而就這一絲喘息機會,讓SQLSERVER可以游刃有余(你的設計有嗎?)。而Open Data Services有一個寫緩沖區。SQLSERVER把檢索到的數據,檢索出來就立即放進寫緩沖區,寫緩沖區一滿就立即被Open Data Service發走。當我過去研究SQLSERVER原理的時候,我常常贊歎,一個小小的SQLSERVER外圍模塊都設計如此精妙,實在讓人佩服。我們經常在追求海量數據存儲和Cache架構,我們卻無視我們手邊的SQLSERVER。
SQL語句放到讀緩沖區,SQLSERVER的關系引擎就開始工作了。它總是在偵聽這個讀緩沖區。
SQL語句遇到的關系引擎的第一個模塊就是命令分析器。我們在SQL查詢分析器中看到的查詢分析結果就是它的輸出傑作。它來構造查詢樹。首先是將你的SQL語句規范化(你想想你寫的軟件代碼,輸入數據來了什麼都不管就直接處理,連輸入數據校驗都沒有,怎能穩定),否則以後的步驟將不好操作,如果你的SQL語句有語法錯誤,這個查詢樹的構造就無法完成,於是中斷。而要規范一個SQL語句,首先要從SQL語法庫中抽取SQLSERVER現有支持的各種語法和函數。
一旦構造成功,關系引擎的第二個模塊就是命令優化器,來裁剪這棵樹。一個SQL語句可以生成多種執行和優化的方案(如果你使用過那種SQL優化工具的話,你就能理解),SQLSERVER會選擇最節省內存、CPU利用率、I/O次數(I/O是性能優化最要命的地方,往往性能就瓶頸在I/O上)的那一種方案。優化器會根據每張表的數據統計(有時候你為了性能優化,必須定時期同步更新一下統計,否則優化就會有誤差)。而且優化器也會根據查詢樹去選擇合適的索引(如果使用索引代價大,它會自動選擇全表掃描),優化器也會根據查詢樹知道先取哪些表的數據,然後再內存中如何合並數據,以得到你想要的結果(有時候想想優化器真偉大,你一個SQL過去,它需要在極短的時間內做多少事啊,為了能在極短時間內確定一個相對優化的方案,它也不可能窮舉所有可能的方案,所以我們做海量數據優化的時候,往往評估多種方案,然後修改自己的SQL語句以符合產生最優的方案)。
規范化、優化完SQL語句,就要產生執行計劃了。SQL管理器負責執行計劃的產生。因為你發過來的SQL語句可能是一個SELECT,也可能是一個INSERT或UPDATE。即使SELECT,也面臨著用戶權限的限制(你如果設置過某一個SQLSERVER用戶的對象權限和列權限,你就會明白)。而INSERT之類更新語句,又會涉及到權限、默認值、約束、表達式、主外鍵、觸發器。一個優化完的SQL,具體要真正讓SQLSERVER從內存或硬盤上把數據找出來或者更新回去,需要很多細節的步驟。
查詢執行器來負責SQL的執行。因為SQL的執行要涉及到事務、鎖、等待、CPU調度,內存頁失效影響、I/O存取影響,所以查詢執行器會協調很多其他模塊,但各個模塊來負責處理,而查詢執行器並不真正全部包辦,否則讓事務管理器、鎖管理器、索引管理器、頁面文件管理器、緩沖管理器、行管理器、日志管理器干嗎去。
查詢執行器是查詢引擎的最後一個模塊,接下來的模塊都屬於存儲引擎的范疇。所以,從上看,查詢引擎最主要是構造SQL查詢樹、優化裁剪SQL查詢樹,根據查詢樹產生執行計劃,然後協調執行查詢樹,把結果返回去。
而真正要把數據取出來或存進去,就需要存儲引擎來工作了。
首先根據執行計劃,要存取哪些數據頁和索引頁。這就是訪問方法管理器(access methods manager)要做的事情。但其實真要打開這些頁,還不是訪問方法管理器自己要親手干的。
親手干這個活的是一個叫“緩沖區管理器”的模塊。因為在硬盤上的數據是不可能計算處理的,必須要在內存中才能讓CPU來計算。所以要存取那些數據頁和索引頁,就通知讓緩沖區管理器來做。如果數據沒有在內存中,就讓緩沖區管理器來讀入,如果數據已經在內存中了,緩沖區管理器只有返回即可。這個過程是被緩沖區管理器來屏蔽的,對於訪問方法管理器是透明的。大家可不要以為訪問方法管理器啥事不做,只是一個發布調度命令的。這可錯怪了它。因為SQLSERVER要保證高速處理,必須預先預測好哪些數據頁和索引頁要處理。不能人家緩沖管理器已經處理完,你訪問方法管理器才計算下一步將要處理的頁面。要知道,這些管理器可是不分哪個用戶來處理的。如果接受來自100多個並發的用戶,發來各種各樣的數據處理請求,你怎麼能預測到哪些數據頁和索引頁要處理呢?這就需要一