程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 數據庫知識 >> SqlServer數據庫 >> 關於SqlServer >> SQLServer內核架構剖析

SQLServer內核架構剖析

編輯:關於SqlServer

  我們做管理軟件的,主要核心就在數據存儲管理上。所以數據庫設計是我們的重中之重。為了讓我們的管理軟件能夠穩定、可擴展、性能優秀、可跟蹤排錯、可升級部署、可插件運行,我們往往研發自己的管理軟件開發平台。我們總是希望去學習別人的開發平台(如用友或金蝶或SAP),但我們卻總是感歎管理軟件業務處理細節繁多,而數據庫管理軟件卻簡單的SELECT、INSERT、DELETE、UPDATE四個命令就搞定。我們多希望有一天能做出一個架構,也可以這麼簡單就搞定管理軟件。我們往往研究別人的架構,卻忘記了我們身邊我們最熟悉的數據庫的架構。所以,今天,我想帶領大家一起剖析一下數據庫的架構,來探索數據庫的架構思想。而我本人呢,只熟悉SQLSERVER這一種數據庫產品,所以我就拿SQLSERVER來分析。

  在講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多個並發的用戶,發來各種各樣的數據處理請求,你怎麼能預測到哪些數據頁和索引頁要處理呢?這就需要一個統一的調度。而且這個統一的調度也影響著緩沖區管理器。你不能請求一個大數據,緩沖區管理器這才火燒屁股才擴大緩沖區,然後裝載數據,那樣流水線就停下了。緩沖區管理器必須預先知道將在不久要有一個大數據,所以在並行運算的時候就有獨立線程來擴展了緩沖區。因為擴大緩沖區還和操作系統有關。你要擴大緩沖區,正好遇到Windows頁面失效,就涉及到你的虛擬文件的變化。而頁面失效又會影響CPU和I/O。所以頁面失效是一個性能影響很大的問題。而提高命中率是我們性能優化一直努力的重點。如果數據長時間不用,緩沖區管理器就要讓這塊內存數據過期,可以被新的數據覆蓋。否則緩沖區老加載不卸載也不行。再說,有些數據已經被更新了,你數據老化了,不重新讀入,你的數據就引起讀錯誤了。

  我們知道,數據頁包含數據行。索引頁包含索引行。數據行就由行管理器來控制。而索引行,由索引管理器來負責。

  而單行上的檢索、修改、執行,又被事務管理器和鎖管理器影響著。事務,有顯性事務和隱性事務兩種。而鎖,又有共享鎖、排它鎖、更新鎖、意向鎖。而鎖,還分為行鎖、頁鎖、表鎖、數據庫鎖。而鎖,又有死鎖的可能性。鎖的不同,加上事務的影響,這個行是否能讀、能修改,能怎樣的讀(讀一致還是髒讀),是等待事務和鎖,還是可以進行,就受了很多影響。因為一張數據頁上放的行是有限的,尤其還有填充度的影響(如填充度為80%,就這個數據頁面只能填充80%就必須分頁,以防以後有數據插入的時候,就非常影響數據插頁,這也是性能影響比較大,尤其在插入數據比較多的情況下)。SQLSERVER的一張數據頁默認是64K,除去填充度和數據頭,也沒有多少可存儲的數據了。這就是為了關系型數據庫都勸阻大家要小表大數據。也就是說,列要少,列要短,頻繁訪問的列要在前。數據可以海量。如果行長了,你想要檢索和更新多少數據頁,這需要多少頁面調度,面臨著頁面失效和鎖機制的影響。而且,大文本和可變行,都是指針存儲,需要跳轉查找,更浪費了不少時間。

  而索引管理器,最主要在維護著索引B樹。沒有索引頁,我們就要做全表掃描了,那需要載入多少數據頁,而且還要逐行掃描,如果遇上事務和更新鎖,就更有問題。所以,索引是非常重要的。而一個表,可以建立很多索引。索引,能直接找到所需要的行,而無須全表掃描。但是,你的索引如果僅僅是男女,或者你的索引涉及到可變行,都對索引不利。索引,不宜建立多。否則維護索引頁的成本和消耗也非常多。索引頁更要涉及到插頁、拆頁,頻繁改動涉及到索引的字段,會讓索引頁劇烈變動,尤其數據量越大影響越大。我就不在這裡講解如何利用索引優化SQL了,否則一本書也講不完。



  數據不斷存取,數據不斷被維護,載入內存或從內存中寫入硬盤。其實都是惰性寫入器在照顧。惰性寫入器來定期掃描老化數據,讓硬盤和內存中的數據是一致的。有這個惰性寫入器,就有了內存和硬盤的差異時間窗。就有可能出現異常。一旦服務器突然斷電,沒有來得及寫會磁盤的怎麼辦。也也涉及到另一個模塊:日志管理器。日志管理器利用檢查點的機制維護著日志文件。在服務器重新啟動的時候,重寫載入日志來把數據恢復到一致性。寫日志,當然要比寫數據要容易的多,快的多。因為寫數據要操控內存和硬盤,還要注意權限、鎖、事務,所以突然斷電,你還沒反應就來不及了。所以日志這種輕量級的方法,就可以在恢復一致性上有很好的幫助(當然,也丟失數據。日志頁也沒來得及寫入硬盤)。

  講到這裡,就剩下事務管理器、鎖管理器。這兩個管理器和顯性事務、隱性事務、顯性鎖、隱性鎖、事務隔離級別、鎖級別、行管理器、索引管理器都有很多關系。微軟有Windows優勢,又有Jim Gray這樣的巨師坐鎮(Jim Gray是圖靈獎獲得者,就是此爺提出了數據庫事務這一概念。蓋茨為了讓此爺為微軟工作,而此爺不喜歡雷德蒙天天下雨的天氣,於是在加州陽光中給此爺單獨建了一座研究院)。所以,在性能上,我個人認為SQLSERVER的性能是非常優秀的(你想想,一個數據庫產品的性能受什麼方面的影響)。至於業界老稱SQLSERVER無法管理海量數據,性能不佳,我個人感覺都是業界在以訛傳訛。而尤其中國內地IT業界,大部分都是入門級在跟帖嘈雜,尤其還有一批更不懂技術的媒體記者或寫手。

  如果真要去說SQLSERVER不行,大型海量數據管理必須用某某數據庫產品,我建議從內部原理、內部架構、內部實現三個層次諸多方面來剖析到底在不在理。


  最後就是I/O管理器了。我一直不認同SQLSERVER內核中有I/O管理器。因為SQLSERVER使用的是和WINDOWS同樣的頁面調度和頁面分配方法。何必要自己另創一套呢。就如同SQLSERVER把頁面、硬盤、內存、線程、CPU交給了WINDOWS一樣。SQLSERVER作為WINDOWS上的一個應用軟件,應該和WINDOWS上的其他軟件一樣被Windows管理。SQLSERVER又不跨平台,無須自己管理。

  除了SQLSERVER這些內核涉及精妙以外,SQLSERVER的外圍工具也設計的相當好。如SQLSERVER的用戶安全性管理方法、對象分類(表、列、約束、默認、索引、觸發器、存儲過程、視圖、主鍵)、對象權限方法、元數據自管理方法、SQL語言、SQL查詢分析器、SQL跟蹤器、SQL性能分析器、SQL數據庫(mastermsdbtempdbmodel)。

  想一想,你的管理軟件平台有這些架構思想嗎?

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved