摘要:本文介紹了在客戶機上處理 Microsoft SQL Server 查詢的方式,各種客戶機與 SQL Server 的交互方式,以及 SQL Server 在處理客戶機程序的請求時需要完成的工作。(打印共 26 頁)
簡介 Microsoft(R) SQL Server(TM) 內部機制和結構是一個非常大的主題,因此本文僅限于程序開發人員感興趣的問題,集中研究其他源中沒有徹底討論的問題。在討論 SQL Server 的結構時,我們主要觀察客戶機的處理過程,研究不同的客戶機程序與 SQL Server 的交互方式,以及 SQL Server 如何處理客戶機的請求。還有一些討論 SQL Server 其他方面的信息源,特別是 Microsoft PRess 出版的 Inside SQL Server 7.0,作者是 Ron Soukup 和 Kalen Delaney,這本書非常詳細地討論了 SQL Server 存儲引擎的內部機制和處理方法,不過對查詢處理器的討論不夠深入。本文正填補了這個空白。
SQL Server 是一種客戶機/服務器系統 多年來,SQL Server 一直被認為是一種客戶機/服務器系統。事實上,Sybase DataServer(以此為基礎開發了原始的 SQL Server)正是第一個作為客戶機/服務器系統開發的商用關系數據庫系統。那這又說明了什么呢?這不只意味著 SQL Server 是一個雙層系統。從傳統上看,雙層系統意味著客戶機應用程序運行在一臺機器上,向另一臺計算機上的服務器發送請求。而對于 SQL Server,客戶機/服務器意味著 SQL Server 的組成部分,即客戶機 API 部分,駐留在處理結構中的遠端,與服務器組件本身是分開的。
三層系統也采用了同樣的模型。多年以來,SQL Server 一直用在事務處理監視系統中,例如 BEA 的 Tuxedo 以及 Compaq 的 ACMSxp,這些系統早在二、三十年前就采用了典型的三層模型。三層模型在今天基于 Web 的應用系統中占據了支配地位,這類系統以 Microsoft 的 MTS 以及新的 COM+ 1.0 為代表。從 SQL Server 的角度看,三層解決方案中的客戶機程序是放在中間層的。中間層直接與數據庫交互。實際的桌面,或瘦客戶機(Thin Client),使用其他機制并通常直接與中間層交互,而不是直接與數據庫系統交互。圖 1 描述了這種結構。
圖 1. 三層系統模型
客戶機結構 從結構的角度看,SQL Server 關系服務器組件本身并不真正關心客戶機程序運行的位置。事實上,就 SQL Server 而言,即使在運行 SQL Server 的同一臺機器上運行應用程序,仍然還是客戶機/服務器模型。服務器運行一個單獨的多線程進程,為來自客戶機的請求提供服務,不管客戶機的位置在哪里。客戶機程序代碼本身是單獨的運行在客戶機應用程序內部的 DLL,與 SQL Server 的實際接口是在客戶機和服務器之間對話的“表格數據流”(Tabular Data Stream, TDS) 協議。
一個常見的問題是“什么是 SQL Server 的本機接口呢?”很長時間以來,很多開發人員一直都不愿意使用 ODBC 這樣的接口,因為他們認為由 Sybase 開發的客戶機 API,也就是 DB-Library,是 SQL Server 的本機接口。實際上,SQL Server 關系服務器本身并沒有本機 API,它的接口就是在客戶機和服務器之間的通信流協議 TDS。TDS 把客戶機發送給服務器的 SQL 語句封裝起來,也把服務器返回給客戶機的處理結果封裝起來。任何直接處理 TDS 的 API 都是 SQL Server 的本機接口。
讓我們來看一下客戶機的組件,如圖 2 所示。客戶機結構中的某些部分就不在這里討論了,因為它們不屬于 SQL Server 的范疇。但如果您在編寫應用程序的話,就必須了解這些部分。大家知道得最多的應該是各種對象模型,如果您正在編寫 asp 或 Microsoft Visual Basic(R) 應用程序,就需要通過 ADO 與數據庫系統交互,而不是直接調用底層的 API,例如 ODBC 或 OLE-DB。ADO 映射到 OLE-DB,而 RDO 映射到 ODBC。因此,作為這種最常用的編程模型的對象模型,并不是 SQL Server 客戶機結構中的嚴格意義上的組件。此外,還有另外一些組件可以插接到 SQL Server 基礎結構上面的這一層。OLE-DB 的“會話池服務提供程序 (session Pooling Service Provider)”就是這種組件的一個例子。
圖 2. 客戶機結構
客戶機接口 SQL Server 有兩個接口可以認為是 SQL Server 7.0 的本機接口,即 OLE-DB 和 ODBC。DB-Library 接口也是本機的,它與 TDS 通信,但是 DB-Library 使用的是 TDS 較老的版本,需要在服務器上進行一些轉換?,F有的 DB-Library 應用程序仍然可以繼續與 SQL Server 7.0 協同使用,但是很多新的功能和性能提高等好處只能通過 ODBC 和 OLE DB 才能利用。更新 DB-Library 使其支持 SQL Server 7.0 的新能力,將會導致與現有應用程序的很多不兼容性,因此需要修改應用程序。ODBC 在五年之前就替代了 DB-Library,是新的 SQL Server 應用程序更理想的 API,因此引入不兼容的 DB-Library 新版本并不明智。
從圖 2 可以看到,所有這些客戶機 API 都有三個部分。最上面的部分實現 API 的細節,例如行集和游標應該是什么樣等等。TDS 格式化程序負責處理實際請求,例如 SQL 語句,并將其封裝成 TDS 消息包,發送給 SQL Server,獲得返回的結果,然后再把結果反饋到接口實現。
除了我們在前面已經提到的局限性,即 DB-Library 仍然只能使用 SQL Server 6.5 版,TDS 協議對于所有 API 都是相同的。ODBC 和 OLE-DB 在與 SQL Server 7.0 通信時使用 SQL Server 7.0 版,但也能夠與 6.5 或 6.0 服務器通信。另一個是 Net-Library,這是一個抽象層,客戶機和服務器都在此層上同網絡抽象接口通信,不必為 ipX 還是 TCP/IP 困擾。在這里我們將不討論 Net-Library 的工作細節;只要知道它們的工作基本上是將來自的網絡通信底層的細節隱藏起來不讓軟件的其他部分看到就可以了。
從客戶機的角度看服務器 前面已經提到過,客戶機與 SQL Server 通信的主要方法就是通過使用 TDS 消息。TDS 是一種簡單協議。當 SQL Server 接收到一條消息時,可以認為是發生了一個事件。首先,客戶機在一個連接上發送登錄消息(或事件),并得到返回的成功或失敗的響應。當您希望發送 SQL 語句時,客戶機可以把 SQL 語言消息打包發送給 SQL Server。另外,當您希望調用存儲過程、系統過程或虛擬系統存儲過程(我們后面還要詳細討論)時,客戶機可以發送 RPC 消息,這種消息相當于 SQL Server 上的一個 RPC 事件。對于上面的后兩種情況,服務器會以數據令牌流的形式送回結果。Microsoft 沒有把實際的 TDS 消息寫入文檔中,因為這被認為是 SQL Server 組件之間的私用契約。
目錄存儲過程是另一類關鍵的客戶機/服務器的交互部分。這些存儲過程首先在 ODBC 的 SQL Server 6.0 中出現, 包括諸如 sp_tables 和 sp_columns 等存儲過程。ODBC 和 OLE-DB API 定義了描述有關數據庫對象的元數據的標準方法,這些標準需要適用于所有類型的 RDBMS 服務器,而不必調整為 SQL Server 自己的系統表。不是客戶機向服務器發送對系統表的多個查詢,并在客戶機端建立標準的元數據視圖,而是創建一組存儲在服務器上的系統存儲過程,并對 API 返回適當格式的信息。這種方法使得通過一次通信就可以完成很多重要的元數據請求。
為 ODBC 編寫的過程已經寫入文檔,通常適合需要從系統表中獲取信息但其他機制沒有提供這種方法的情況。這使得 Transact-SQL 過程和 DB-Library 應用程序可以訪問元數據,而不需要編寫對 SQL Server 系統表的復雜查詢,并且使應用程序不受今后 Microsoft 修改系統表的影響。
OLE DB 定義了一組架構行集,它們類似于 ODBC 的元數據,但又和它不同。它創建了一組新的目錄存儲過程,以更有效地為這些架構行集植入數據。但是,這組新的存儲過程沒有寫入文檔,因為這些存儲過程重復了早先提供的功能。通過現有的若干種方法都可以得到元數據,因此 SQL Server 開發組決定不顯露這些并沒有為編程模型增加新內容的對象。
客戶機與服務器的交互還有第三個方面。它最初出現在 SQL Server 6.0 中,但是沒有得到普遍使用。這就是虛擬系統存儲過程的概念;在 SQL Server 7.0 中起很重要的作用。當第一次為 SQL Server 6.0 開發服務器端游標時,開發人員就需要選擇采取什么方法管理客戶機/服務器的交互。游標并不特別適合現有的 TDS 消息,因為這些消息允許逐行返回數據,不需要客戶機指定額外的 SQL 語句。開發人員本來可以向 TDS 協議添加更多的消息,但是需要修改太多的其他組件。SQL Server 6.0 中的 TDS 版本還需要向 Sybase 版本靠攏,以便確保兩者的可互操作性,于是開發人員選擇了另外的處理機制。他們開發了外表看起來像是系統存儲過程的新功能(服務器端游標),實際上是指向 SQL Server 代碼的入口存儲過程。它們被客戶機應用程序使用標準的 RPC TDS 消息來調用。它們被稱為虛擬系統存儲過程,因為在客戶機上,它們像其他存儲過程那樣被調用,和其他存儲過程不同的是,它們并不是由簡單的 SQL 語句組成。大多數虛擬系統存儲過程都是私用的,并且沒有寫入文檔。對于游標過程,所有 API 都顯露其自有的一組游標 API 模型和它們自己的游標操作函數,因此沒有必要為存儲過程本身編寫文檔。即使是在 Transact-SQL 語言中,也有顯露游標的語法,可以使用 DECLARE、OPEN、FETCH 等,所以完全沒有必要為虛擬系統存儲過程編寫文檔,例如 sp_cursor,因為這些過程只在內部使用。
ODBC 和 OLE DB 中出現了帶參數的查詢和準備/執行模型的概念。在 SQL Server 7.0 以前的版本中,這些概念是由客戶機 API 中的代碼實現的。在 SQL Server 7.0 中,Microsoft 為這些概念添加了對“關系服務器”的支持,并且通過新的虛擬系統存儲過程顯露了這種支持。本文后面還要介紹這些功能,以及服務器如何支持這些功能。通過 sp_executesql 過程對帶參數的查詢的支持,被認為對直接 Transact-SQL 和 DB-Library 的使用特別有用,所以將其寫入了文檔。準備/ 執行的過程,被 ODBC 驅動程序和 OLE DB 提供程序專用。
這樣,可以與 SQL Server 通信的所有客戶機程序,都建立在這三組功能之上:TDS 協議、目錄存儲過程和虛擬系統存儲過程。
服務器結構 SQL Server,或更確切一點地說,是“SQL Server 關系服務器”,經常被說成是由兩個主要部分組成,即關系引擎和存儲引擎。正如前面提到過的那樣,已經有很多文獻介紹存儲引擎的細節了,所以本文主要介紹關系引擎的功能。圖 3 給出了 SQL Server 關系引擎部分的主要組件。所給出的組件可以分為三組子系統。左邊的組件編譯查詢,包括查詢優化器。查詢優化器是所有關系數據庫引擎中的最神秘的部分之一,從性能的角度看也是最重要的部分。查詢優化器負責提取 SQL 語句中的非過程請求部分,并將其翻譯成一組磁盤 I/O、過濾以及其他能夠高效地滿足該請求的過程邏輯。圖中右側是執行基礎結構。這里實際上只有很少的功能。當編譯組件的工作完成之后,所產生的結果只需用很少幾個服務即可直接執行。
在這個具體的查詢語句中,我們從零件表中提取具有特定零件標識號的所有行。這是特定 SQL 的一個典型例子。在 SQL Server 7.0 以前的版本中,特定的 SQL 與存儲過程的一個顯著差別是,查詢優化器所生成的計劃從不緩存。查詢語句要被讀入、編譯、執行,然后再拋棄計劃。在 SQL Server 7.0 中,正如稍后還要討論的,實際上提供了可以緩存特定查詢語句的計劃的機制。
在這條語句被送往 SQL Server 之前,還必須要問幾個問題。所有客戶機程序都要提供某種游標說明,所以客戶機程序在內部必須詢問的一個問題是,程序員請求的是什么樣的結果集或什么樣的游標。最快的類型是在文檔中被稱為默認結果集的游標。這種游標由于歷史上的原因被稱為消防站游標,有時甚至根本不把它作為游標看待。當 SQL 請求被送到服務器之后,服務器開始把結果返回給客戶機,這個返回結果的過程持續進行,直到把全部數據集發送完畢為止。這就像一個將數據抽給客戶機的大型消防站。
一旦客戶機程序確定了這是默認結果集,則下一步就是確定是否有參數標記。使用這個 ODBC SQLExecDirect(以及 OLE-DB 中等價的調用)調用的選項之一是,不是在 WHERE 從句中給出像 7 這樣的具體值,而是可以用一個問號來傳遞參數標記,如下所示:
SQLExecDirect(hstmt, "SELECT * FROM parts where partid = ?", SQL_NTS)
請注意,您必須分別提供實際的參數值。
客戶機需要知道 SQL 語句中是否有參數標記,或者它是否為真正特定的非參數化 SQL。這將影響到客戶機將用這個語句在內部做什么,并確定將什么作為消息真正發送給 SQL Server。在沒有問號時,很明顯,客戶機只想將這個請求作為 SQL Language TDS 消息發送,然后客戶機將位于流水的末端,并將結果返回。然后客戶機能將結果返回給基于應用程序參數的應用程序??蛻魴C的內部處理選擇會模糊一點,這取決于您通過 ODBC 或 OLE DB API 請求什么。例如,應用程序不直接請求默認結果集。相反,在 ODBC 中,如果請求一個只讀的、只向前的且每次只給出一行的游標,那么對于客戶機內部運行來說,這就是在定義流水游標(默認結果集)。
流水游標有一個主要問題。除非客戶機已將所有的行全部接收完畢,客戶機不能將任何其他 SQL 語句向下發送給服務器。因為結果集可能有很多行,所以有些應用程序使用流水游標時不能順利運行。后面將要描述的只向前的快速游標,是 SQL Server 7.0 版的一個新特點,尤其適合于處理這種情況。
在 SQL Server 7.0 版之前,SQLExecDirect 調用在很大程度上是以相同方式處理的,而不管是否用參數標記來代替常數。如果您定義一個參數標記,客戶機將實際取您通過不同調用提供的值(本節的開始示例中的值“7”),并將它插入問號處。然后,使用代替值的新語句被向下發送,作為一個特定的 SQL 語句。在服務器上使用參數化的 SQL 沒有任何好處。
然而,在 SQL Server 7.0 版中,如果 SQLExecDirect 使用了參數標記,向下發送給 SQL Server 的 TDS 消息便不是 SQL 語言消息。相反,它被下發給使用 sp_executesql 過程的服務器,所以,就 TDS 協議來說,它是 RPC。在客戶機上,結果基本上相同??蛻魴C將取回數據流水。
讓我們看一個簡單的情況,即只返回一行給客戶機。至于默認的結果集,需要從客戶機到服務器往返發送一次消息。 SQL 消息(或 sp_executesql)向下發往服務器,然后結果返回來。在同一行(非流水)的游標情況下,您會看到傳統情況下能用 SQL Server 看見的東西。一個往返行程用于打開,一個往返行程用于取得數據,一個往返行程用于關閉。這個過程使用消息的次數是默認結果集使用的三倍。在 SQL Server 7.0 中,有一種所謂只向前的快速游標, 它使用同樣的游標結構。它與流水的表現不一樣,因為在發送任何附加 SQL 消息之前,它不需要您處理全部結果行。所以,如果您帶回 5 行,還有更多的數據,您仍能將更新向下發送給服務器。
一個只向前的快速游標在服務器上比常規游標更快,它讓您指定兩個附加選項。一個稱為自動取數,另一個稱為自動關閉。自動取數將返回第一個行集合,作為打開的響應消息的一部分。自動關閉在讀完最后一行后自動關閉游標。因為它是只向前的和只讀的,所以不能回滾。SQL Server 只傳回一個帶有說明游標已關閉的最后數據集的消息。如果您正在使用只向前的快速游標,則在行數少的消息里,您可向下與同一往返行程通信。如果您有很多行,則您至少還要對每一行塊支付附加開銷。如果您使用只向前的快速游標,那么游標處理會更加接近默認的結果集。
SQLPrepare(hstmt, "SELECT * FROM parts where partid = ?", SQL_NTS) SQLExecute(hstmt)
在 SQL Server 7.0 版本之前,準備/執行從來都不是 SQL Server 的本機模式。如今在 7.0 版本中,有兩個提供本機接口的虛擬系統存儲過程。對于準備調用,我們要再次研究游標的類型,然后調用 sp_prepare 或 sp_cursorprepare。這些過程會完成 SQL 或存儲過程的編譯,但不會實際執行計劃。相反,虛擬系統存儲過程只是返回該計劃的句柄?,F在,應用程序可以反復地執行 SQL 了,例如傳入不同的參數值,而不需要重新編譯。
在 SQL Server 6.5 中,由于沒有本機接口,需要模擬準備和執行兩個階段。可以通過下面的兩種方法做到這一點。在第一種方法中,不會真正出現準備階段。只有執行部分返回元數據(有一些選項可以做到這一點),所以 SQL Server 可以把結果的格式描述返回給應用程序。在第二種方法中,SQL Server 實際上創建一個特定存儲過程,這個過程是單個用戶私用的,不能共享計劃。這第二種方法可能會占滿 tempdb 數據庫的空間,因此大多數應用程序開發人員都通過 ODBC 配置對話框中的復選框,關閉此選項,以使用第二種方法。
在 SQL Server 7.0 中,準備/執行方法是 SQL Server 的本機功能。準備好 SQL 語句之后,才會執行它。至于默認的結果集,應用程序只需要調用 sp_execute,提供準備操作生成的句柄,語句就會被執行。對于游標,與其他游標處理過程看起來很相似,事實上,它也具有相同的特性,包括如果游標是快速只前向型,還可以使用 autofetch 和 toclose。