概述
任何有實際價值的關系數據庫應用程序都離不開一大堆的查詢表。如果您是開發圖形用戶界面的專家,那麼您知道這些查詢表將用於加工下拉列表框中的列表。我將查詢表分成兩種:只讀表和可改寫只讀表。二者的區別在於什麼會導致表的改變。我認為如果需要召開員工會議或者用戶會議才可以修改表的內容,那麼表就是只讀的。一個好的例子就是公司的產品類別表。表的內容將不會改變直到公司研發並向市場投放了新產品,或者公司進行了重組。可改寫的只讀表是內容相對固定的列表,但可以被最終用戶修改,通常使用組合框而不用下拉列表框來展現。可改寫只讀表的一個例子就是稱謂術語表。應用程序設計人員能夠考慮到最常用的那些稱謂,如Ms., Mr., Mrs.以及Dr.,但總有某個用戶的頭銜是您從未考慮過的而且希望把它添加進來。舉個例子看看這種情況有多麼常見,我最後工作的一個中型產品有一個設計良好符合第三范式的關系數據庫,它包含了350—400張表,我估計了一下大約有250張表是只讀的或可改寫只讀表。
傳統的Web應用程序(三層結構應用程序的經典范例)希望盡可能地緩存這種類型的表。這樣不僅可以減少往返數據庫的次數,還可以降低數據庫服務器的查詢負載、提高某些應用場景的響應能力,例如接受了新訂單。只讀表很容易緩存;可以始終將表放在緩存中,偶爾需要重新加載表時,則由數據庫管理員(DBA)通過某種方式重新加載緩存。我希望您的企業中很少召開對數據庫基本結構和內容作修改的會議。重新刷新中間層緩存中可改寫只讀表則有一點點麻煩。如果緩存刷新的頻率太低就無法獲得您期望的效果;用戶也無法立刻看到其它用戶對數據的修改。支持人員可以使用另一個應用程序添加新項,然後給打算使用該項的同伴發送一條即時消息,但同伴的選擇列表框中卻並不包含新添加的項。更糟糕的是,如果第二個用戶此時試圖重新添加這條“缺失的列表項”,就會收到一條數據庫錯誤告知該項已經存在了。由於類似問題的存在,如果可改寫只讀表允許多點更新,那麼通常不進行緩存。
過去,程序員不得不自己手工開發解決方案,使用消息隊列、進行文件輸出的觸發器、或者out-of-band協議來通知緩存,如果應用程序外部的某些用戶更新了可改寫只讀表。這種“通知”解決方案只是通知緩存有行被添加或修改了,指示必須刷新緩存。如何通知緩存有哪些行改變了或者有哪些行被添加了,這一直都是個難題,是分布式數據庫和分布式事務或者合並復制領域的問題。低成本的通知解決方案的做法是:只要程序收到“緩存無效“的消息就刷新整個緩存。
SqlDependency 提供了緩存解決方案
如果您正在使用SQL Server 2005和ADO.NET 2.0,那麼一種新的稱為查詢通知的通知解決方案已內置在SqlClient數據供應商和數據庫中。該問題終於有了內置且易於使用的解決方案!ASP.NET 2.0也內置了用於支持查詢通知的特性。ASP.NET的Cache對象可以注冊通知,甚至還可以將通知與ASP.NET的頁面緩存或頁面片段緩存結合在一起使用。
實現該功能的架構中包含了SQL Server 2005查詢引擎、SQL Server Service Broker、sp_DispatcherProc系統存儲過程,ADO.NET的SqlNotification (System.Data.Sql.SqlNotificationRequest)和SqlDependency類(System.Data.SqlClient.SqlDependency),以及ASP.NET的Cache類(System.Web.Caching.Cache)簡而言之,它的工作方式如下:
1.每個ADO.NET SqlCommand都包含一個Notification屬性,表示對通知的請求。
當執行SqlCommand時,如果存在Notification屬性,那麼就會在網絡請求中附加一個表示通知請求的網絡協議包(TDS)。
2.SQL Server使用查詢通知架構注冊所請求通知的訂閱,然後執行命令。
3.SQL Server“監視”任何可能導致最初返回的結果集發生變化的SQL DML語句。當發生變生時,就向Service Broker服務發送消息。
4.消息可以:
a.引發通知並將通知傳回注冊的客戶端。
b.駐留在Service Broker's的服務隊列中,高級客戶端可以實現自己的處理機制。
圖1. 通知服務概覽
ASP.NET的SqlCacheDependency類(System.Web.Caching.SqlCacheDependency)和OutputCache指示詞會使用SqlDependency實現自動的通知功能。如果ADO.NET客戶端需要對查詢通知擁有更多的控制權,那麼可以使用SqlNotificationRequest並且手工處理Service Broker隊列,實現自己喜歡的處理邏輯。深入解釋Service Broker已經超出了本文的范圍,A First Look at SQL Server 2005 for Developers的示例章節以及Roger Wolter的文章A First Look at SQL Server 2005 Service Broker將對您有所幫助。
在繼續下面的內容之前,明確這一點是十分重要的:當結果集改變時,每個SqlNotificationRequest或者SqlDependency只收到一條通知消息。不管數據改變是由於執行INSERT語句在數據庫上進行了插入、使用DELETE語句刪除了一行或多行,還是使用UPDATE更新了一行或多行,通知消息都是完全一樣的,通知中也不包含任何有關改變的行數或者哪些行被改變的信息。當緩存對象或者用戶應用程序收到這條關於數據改變的消息時只有一種選擇,刷新整個結果集或者重新注冊通知。您不會接收到多條消息,當該條消息引發後,數據庫中的用戶訂閱也就不存在了。此外查詢通知框架的工作前提是:為多種不同事件發送通知總比根本不通知要好。因此不僅僅在結果集改變時會發送通知,刪除或者修改了結果集引用的表、回收數據庫、以及其他一些原因也會發送通知。緩存或程序對通知的響應方式通常是一樣的,即刷新緩存數據並且重新注冊通知。
現在我們已經了解了所有相關的概念和語義,接下來我們將從三個角度深入研究查詢通知是如何工作的:
1.SQL Server的查詢通知是如何實現的?可選的分發器是如何工作的?
2.SqlClient的SqlDependency和SqlNotificationRequest是如何在客戶層/中間層工作的?
3.ASP.NET 2.0是如何支持SqlDependency的?
SQL Server 2005中的查詢通知
在服務器一級,SQL Server分批處理客戶端發送的查詢。每個查詢(可以將查詢看作是SqlCommand.CommandText屬性)只能包含一個批,盡管每個批可以包含多個T-SQL語句。SqlCommand還可以執行存儲過程或者用戶定義的函數,這些對象也可以包含多個T-SQL語句。在SQL Server 2005中,客戶端發送的查詢中還包含了其它三條信息:用於投遞通知的Service Broker的service、通知標識符(一個字符串)、以及通知的超時值。如果查詢請求中存在這三條信息並且查詢包含了SELECT或者EXECUTE語句,那麼SQL Server將“監視”該查詢產生的所有結果集是否因為其他SQL Server會話而改變。如果產生了多個結果集,例如執行了某個存儲過程,那麼SQL Server將“監視”所有的結果集。
那麼我所說的“監視”結果集是指什麼?SQL Server又是如何完成該任務的呢?對結果集的變更檢測是SQL Server數據庫引擎的一部分,使用了SQL Server 2000中用於索引視圖同步的變更檢測機制。在SQL Server 2000中,Microsoft引入了索引視圖的概念。 SQL Server中的視圖包含了對單張表或者多張表的列的查詢。 每個視圖對象都有名稱,可以像使用表名那樣使用視圖名稱,例如:
CREATE VIEW WestCoastAuthors
AS
SELECT * FROM authors
WHERE state IN ('CA', 'WA', 'OR')
現在您就可以使用視圖了,就像在查詢中使用表一樣:
SELECT au_id, au_lname FROM WestCoastAuthors
WHERE au_lname LIKE 'S%'
大多數程序員都很熟悉視圖,但可能並不熟悉索引視圖。在一個非索引視圖中,視圖中的數據並不作為獨立副本而存儲在數據庫中;每次使用視圖時,視圖中包含的查詢將被執行。因此在上面的例子中,將執行獲取WestCoastAuthors結果集的查詢,然後再通過條件判定找出那些我們想要的WestCoastAuthors。索引視圖則存儲了數據副本,因此如果我們將WestCoastAuthors創建成一個索引視圖,我們就擁有兩份authors數據。您現在可以通過兩種方式更新數據,或者通過索引視圖,或者通過原始表。SQL Server因此需要對兩份物理數據存儲的變化進行檢測,從而將一處的變更應用到另一處。這種變更檢測機制和數據庫引擎處理查詢通知時使用的機制是一樣的。
由於變更檢測的這種實現方式,因此並非所有視圖都可以創建索引。那些有關索引視圖的限制條件對於查詢通知也同樣生效。例如:以上面的方式書寫的WestCoastAuthors 視圖就不能創建索引。要想在視圖上創建索引,視圖定義必須使用2部分命名法則,且必須顯式地列出結果集中所有的列名。因此為了創建索引我們來修改一下視圖的定義:
CREATE VIEW WestCoastAuthors
WITH SCHEMABINDING
AS
SELECT au_id, au_lname, au_fname, address, city, state, zip, phone
FROM dbo.authors
WHERE state in ('CA', 'WA', 'OR')
只有遵循了索引視圖規則的那些查詢才可以使用通知。注意:盡管使用了相同的機制來判定是否一個查詢結果集發生了改變,但查詢通知不會導致SQL Server創建數據副本,而索引視圖會。您可以在SQL Server 2005 Books Online中找到有關索引視圖的一系列規則。如果提交了一個附帶通知請求但是違背規則的查詢,SQL Server會立刻發出一個原因為“無效查詢”的通知。可是,通知發布到哪裡呢?
SQL Server 2005使用Service Broker傳遞通知。Service Broker是SQL Server中內置的異步消息隊列功能。查詢通知將使用Service Broker的SERVICE。此處的SERVICE是指異步消息的目的地;可以強制要求消息必須遵循被稱為合同的一組規則。每個Service Broker的SERVICE始終和某個隊列,也就是物理的消息目的地相關聯。查詢通知合同是內置在SQL Server中的,名稱為http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification.
注意:盡管合同在SQL Server中的對象名看似一個URL,但卻與該位置沒有任何聯系。它只是一個對象名,就像dbo.authors是個表名而已。
總而言之,查詢通知消息的目的地可以是任何SERVICE,只要該SERVICE支持相應的合同。使用SQL DDL定義service的語句如下所示:
CREATE QUEUE mynotificationqueue
CREATE SERVICE myservice ON QUEUE mynotificationqueue
([http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification])
GO
現在就可以使用myservice的SERVICE作為查詢通知請求的目的地了。SQL Server通過將消息發送到SERVICE的方式發布通知。可以使用您自己的SERVICE或者讓SQL Server選擇MSDB數據庫中的一個內置SERVICE。如果使用您自己的SERVICE,您必需自己編寫代碼讀取和處理消息。但如果使用MSDB中內置的SERVICE,則使用預先寫好的消息投遞的代碼。後面我還會再探討這一點。
由於查詢通知需要使用Service Broker,因此有一些其它要求:
1.必須在通知查詢運行的數據庫上開啟Service Broker。在SQL Server Beta 2中,默認AdventureWorks示例數據庫沒有開啟該功能,可以使用“ALTER DATABASE SET ENABLE_BROKER”的DDL語句開啟該功能。
2.提交查詢的用戶必須具有訂閱查詢通知的權限。權限授予是在每個數據庫級別上配置的;下面的DDL語句將授予用戶'bob'在當前數據庫中訂閱的權限:
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO bob
將通知發送給最終用戶或者緩存
到目前為止,我們已經提交了正確的附帶通知請求的查詢到SQL Server。SQL Server在行集上進行監視,如果任何用戶改變了行集,一條消息就被發送到我們選擇的SERVICE。現在該做些什麼呢?您可以自己編寫代碼負責在通知產生時讀取消息並執行自定義的處理邏輯;或者您也可以使用內置的分發器替您進行處理。那麼,讓我們來看看分發器。
除非指定了自定義的SERVICE,否則查詢通知將使用MSDB數據庫中內置的名為http://schemas.microsoft.com/SQL/Notifications/QueryNotificationService作為默認的SERVICE。當消息到達該SERVICE的隊列時,與隊列關聯的sp_DispatcherProc系統存儲過程自動對消息進行處理。有意思的一點是該存儲過程使用了.NET編寫的代碼,因此必須在SQL Server 2005實例上啟用“加載NET公共語言運行時(CLR)”,自動的查詢通知投遞才能工作。(可以在每個SQL Server實例上啟用或禁用“加載.NET CLR”)
當查詢通知消息到達時,sp_DispatcherProc (從現在起,我將它稱為“分發器”)檢查SqlDependency通知隊列中的查詢通知訂閱列表,然後將消息發送給每個訂閱者。注意:使用分發器的時候,是由服務器將數據改變的通知告知客戶端的。這樣做有兩個好處:客戶端無須對通知進行輪詢,客戶端無須建立和SQL Server的連接就可以接收通知。分發器使用HTTP協議或者TCP或者私有協議將通知發送給每個訂閱者。可以選擇是否對服務器-客戶端通信進行身份驗證。通知投遞到訂閱者後,就從活動的訂閱列表中刪除該訂閱。記住:每個客戶端的訂閱只能接收一條通知;這取決於客戶端是否重新提交查詢以及重新訂閱。
從數據庫客戶端使用查詢通知
現在我們已經了解了所有內部細節,讓我們編寫一個使用查詢通知的ADO.NET客戶端程序。為什麼在我們編寫相對簡單的客戶端代碼前要做那麼多的解釋呢?盡管編寫代碼是相當簡單的,但是必須記住要遵守規則。最常見的問題就是提交了一個無效的查詢以及忘記設置Service Broker和用戶權限,致使這個強大功能受挫,甚至給某些beta版測試人員的印象是該功能不能工作。一點點的准備和研究就會對您大有幫助。最後,先了解內部細節也是不錯的,因為後面我們將設置諸如Service Broker的SERVICEs以及分發器協議的屬性,現在您已經完全了解這些術語的含義了。
您可以在ADO.NET中寫一個查詢通知的客戶端,像我們一樣使用OLE DB,甚至使用新的HTTP Web服務客戶端,但需要記住的一點就是:查詢通知只能通過客戶端代碼來實現。該特性不允許直接通過T-SQL語句或者SQLCLR存儲過程來使用SqlServer數據供應商與SQL Server進行對話。
您可以使用System.Data.dll程序集中包含的兩個類:SqlDependency和SqlNotificationRequest。 如果希望使用分發器自動進行通知,那麼使用SqlDependency。如果希望自己處理通知消息,那麼使用SqlNotificationRequest。我們來看看每個類的一個例子。
使用SqlDependency
使用SqlDependency步驟很簡單。首先,創建一個包含您要接收查詢通知的SQL語句的SqlCommand;將SqlCommand和SqlDependency關聯起來。然後注冊SqlDependency對象OnChanged的事件處理程序。接下來執行SqlCommand。您可以處理DataReader,關閉DataReader,甚至關閉相關的SqlConnection。分發器會在您的結果集發生變更時通知您。SqlDependency對象的事件是在另一個線程上引發的;這意味著您必需有心理准備處理那些事件已經引發而您的代碼還在繼續運行的情形,甚至在事件引發時您可能仍然在處理查詢結果集。以下是代碼:
using System;
using System.Data;
using System.Data.SqlClient;
static void Main(string[] args)
{
string connstring = GetConnectionStringFromConfig();
using (SqlConnection conn = new SqlConnection(connstring))
using (SqlCommand cmd =
// 2-part table names, no "SELECT * FROM ..."
new SqlCommand("SELECT au_id, au_lname FROM dbo.authors", conn))
{
try
{
// create dependency associated with cmd
SqlDependency depend = new SqlDependency(cmd);
// register handler
depend.OnChanged += new OnChangedEventHandler(MyOnChanged);
conn.Open();
SqlDataReader rdr = cmd.ExecuteReader();
// process DataReader
while (rdr.Read())
Console.WriteLine(rdr[0]);
rdr.Close();
// Wait for invalidation to come through
Console.WriteLine("Press Enter to continue");
Console.ReadLine();
}
catch (Exception e)
{ Console.WriteLine(e.Message); }
}
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
Console.WriteLine("result has changed");
Console.WriteLine("Source " + e.Source);
Console.WriteLine("Type " + e.Type);
Console.WriteLine("Info " + e.Info);
}
同樣的代碼您也可以使用Visual Basic .NET編寫,使用熟悉的WithEvents關鍵字和SqlDependency. 注意這段程序只接收和處理一個OnChanged事件,而不管底層行集發生了多少次改變。收到通知時我們要做的工作就是重新提交附帶新的通知請求的命令,並使用該命令的執行結果刷新緩存以反映出新數據。如果將上面例子中Main() 的代碼轉移到一個命名的例程中,代碼看起來應如下所示:
static void Main(string[] args)
{
GetAndProcessData();
UpdateCache();
// wait for user to end program
Console.WriteLine("Press Enter to continue");
Console.ReadLine();
}
static void MyOnChanged(object caller, SqlNotificationEventArgs e)
{
GetAndProcessData();
UpdateCache();
}
通過幾小段文字我們就可以了解在ASP.NET 2.0中可以采用完全相同的方式,使用ASP.NET的Cache類作為數據緩存。
SqlDependency依靠內置在SQL Server 2005中的分發器建立和客戶端的連接以及發送通知消息。分發器使用的是out-of-band通信而不是使用SqlConnection。這也意味著您的客戶端必須可以“通過網絡到達”SQL Server;防火牆或者網絡地址轉換可能會妨礙這種通訊。未來的beta版將允許您對端口配置有更多的控制權,從而可以更友好地通過防火牆的限制。可以通過指定SqlDependency構造方法中的參數來完全配置服務器和客戶端的通信工作。示例如下:
SqlDependency depend = new SqlDependency(cmd,
null,
SqlNotificationAuthType.None,
SqlNotificationEncryptionType.None,
SqlNotificationTransports.Tcp,
10000);
SqlDependency的這個構造方法允許您選擇不同於默認值的行為。所有可改變行為中最有用的就是服務器用於連接客戶端所使用的傳輸協議。該例中使用的是SqlNotificationTransports.Tcp,服務器可以使用TCP或者HTTP。該參數的默認值是SqlNotificationTransports.Any;這樣讓服務器來“決定”使用哪個傳輸協議。如果指定Any,那麼當客戶端操作系統中包含了核心模式HTTP支持時,服務器將使用HTTP協議,否則使用TCP。Windows Server 2003和Windows XP with SP2都包含了核心HTTP支持。由於消息是通過網絡發送的,因此您可以指定使用何種類型的身份驗證。目前EncryptionType是一個參數,但在以後的beta版中該參數將被去除。目前兩個值默認都是None。
SqlNotificationAuthType也支持集
成的身份驗證。您可以顯式地指定訂閱超時值和SQL Server Service Broker的SERVICE名稱。SERVICE名稱通常設置為空,如例子中所示,但也可以顯式地將內置的SqlQueryNotificationService指定為SERVICE名稱。最有可能被改寫的參數是SqlNotificationTransport和timeout。注意這些參數只能應用於SqlDependency,因為參數指定的是服務器端分發器的行為。使用SqlNotificationRequest的時候並不會使用分發器。
使用SqlNotificationRequest
使用SqlNotificationRequest只是在配置方面要比SqlDependency復雜一點,但主要取決於您的程序如何處理消息。當您使用SqlDependency時,服務器上的通知被發送到MSDB數據庫的SqlQueryNotificationService,並由它替您處理通知。使用SqlNotificationRequest時您必須自己處理消息。這裡有一個簡單的示例,使用SqlNotificationRequest以及我們在本文前面定義的SERVICE。
using System;
using System.Data;
using System.Data.Sql;
using System.Data.SqlClient;
class Class1
{
string connstring = null;
SqlConnection conn = null;
SqlDataReader rdr = null;
static void Main(string[] args)
{
connstring = GetConnectionStringFromConfig();
conn = new SqlConnection(connstring));
Class1 c = new Class1();
c.DoWork();
}
void DoWork()
{
conn.Open();
rdr = GetJobs(2);
if (rdr != null)
{
rdr.Close();
WaitForChanges();
}
conn.Dispose();
}
public SqlDataReader GetJobs(int JobId)
{
using (SqlCommand cmd = new SqlCommand(
"Select job_id, job_desc from dbo. jobs where job_id = @id",
conn))
{
try
{
cmd.Parameters.AddWithValue("@id", JobId);
SqlNotificationRequest not = new SqlNotificationRequest();
not.Id = new Guid();
// this must be a service named MyService in the pubs database
// associated with a queue called notificationqueue (see below)
// service must go by QueryNotifications contract
not.Service = "myservice";
not.Timeout = 0;
// hook up the notification request
cmd.Notification = not;
rdr = cmd.ExecuteReader();
while (rdr.Read())
Console.WriteLine(rdr[0]);
rdr.Close();
}
catch (Exception ex)
{ Console.WriteLine(ex.Message); }
return rdr;
}
}
public void WaitForChanges()
{
// wait for notification to appear on the queue
// then read it yourself
using (SqlCommand cmd = new SqlCommand(
"WAITFOR (Receive convert(xml,message_body) from notificationqueue)",
conn))
{
object o = cmd.ExecuteScalar();
// process the notification message however you like
Console.WriteLine(o);
}
}
使用SqlNotificationRequest的強大之處(當然也包括額外的工作)就是由您自己等待和處理查詢通知。如果您使用SqlDependency,那麼在接收到查詢通知之前您甚至無需連接數據庫。事實上不需要為了接收SqlNotificationRequest的通知而進行等待;可以每隔一段時間輪詢一次消息隊列。SqlNotificationRequest另一個用處就是編寫一個特別的應用程序,即使通知已經激活了,該應用程序也不會運行。當應用程序啟動後,它將連接到消息隊列並決定緩存中(由於上次運行應用程序)哪些內容是無效的。
探討一下這樣的應用程序,它們為了等待一條通知需要花費好幾個小時或者幾天,這種應用程序向我們提出了問題: “如果數據不發生改變,那麼通知何時消失呢?” 唯一可以導致通知消失的情況(例如,清理了數據庫的訂閱表)就是通知被引發或者通知過期。數據庫管理員如果為那些始終留在數據庫中的訂閱(因為它們會使用SQL資源並且增加查詢和數據更新的成本)而惱火的話,可以在SQL Server中手動地清理通知。首先查詢SQL Server 2005動態視圖,找出那些不想要的通知訂閱,然後使用下面的命令刪除它們:
-- look at all subscriptions
SELECT * FROM sys.dm_qn_subscriptions
-- pick the ID of the subscription that you want, then
-- say its ID = 42
KILL QUERY NOTIFICATION SUBSCRIPTION 42
在ASP.NET中使用SqlCacheDependency
通知功能還被連接到ASP.NET的Cache類中。ASP.NET 2.0中CacheDependency類可以派生子類,其中SqlCacheDependency封裝了SqlDependency,其行為與ASP.NET中其它的CacheDependency一樣。 SqlCacheDependency超越了SqlDependency,因為它可以工作在SQL Server 2005或者SQL Server早期版本上。當然對於SQL Server 2005之前的版本,它的實現是完全不同的。
如果您使用的是SQL Server早期版本,那麼SqlCacheDependency通過建立在被“監視”表上的觸發器進行工作。這些觸發器將變更信息記錄到另一張SQL Server表中,然後輪詢那張表。可以對啟用相依性功能的表以及輪詢的時間間隔進行配置。SQL Server 2005以前版本的實現細節已經超出了本文討論的范圍,更多信息請參閱Improved Caching in ASP.NET 2.0。
使用SQL Server 2005時,SqlCacheDependency封裝了一個與前面ADO.NET示例中類似的SqlDependency實例。以下是使用SqlCacheDependency的一個簡短示例:
// called from Page.Load
CreateSqlCacheDependency(SqlCommand cmd)
{
SqlCacheDependency dep = new SqlCacheDepedency(cmd);
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60);
Response.Cache.SetCacheability(HttpCacheability.Public);
Response.Cache.SetValidUntilExpires(true);
Response.AddCacheDependency(dep);
}
一個很棒且易於使用的特性就是SqlCacheDependency可以和頁面緩存或者頁面片段緩存配合使用。您可以通過一個特殊的ASP.NET指示詞OutputCache聲明式地啟用所有SqlCommands的查詢通知功能。該方式為頁面中所有SqlCommand設置相同的SqlDependency。對於SQL Server 2005數據庫其聲明方式如下所示:
<%OutputCache SqlDependency="CommandNotification" ... %>
注意CommandNotification是關鍵字,其含義為“使用SQL Server 2005和SqlDependency”,如果使用早期的SQL Server版本,那麼該指示參數的語法是完全不同的。同樣,只有在特別的操作系統版本上運行ASP.NET 2.0時,才會啟用關鍵字CommandNotification設置的值。
熱心的通知
SQL Server查詢通知的設計策略是:頻繁通知用戶總比忘記通知用戶要好。盡管絕大多數情況您收到通知都是由於他人修改了表中的某一行致使緩存無效,但並非總是如此。例如,如果DBA回收了數據庫您會收到通知。如果查詢中引用的任何表被修改、刪除、或者清空,您也會收到通知。由於查詢通知回占用SQL Server資源,如果SQL Server受到資源不足的壓力,就可能從內部表中刪除查詢通知,這種情況下客戶端也會收到通知。由於每個通知請求都包含了一個超時值,如果訂閱超時也會通知您。
如果使用SqlDependency,那麼分發器會將所有信息封裝到一個SqlNotificationEventArgs實例中。該類包含了三個屬性:Info, Source和Type,這些屬性幫助您查明通知產生的原因。如果使用SqlNotificationRequest,那麼隊列消息的message_body字段包含的XML文檔中也含有相同的信息,但是您必須使用XPath或XQuery自己分析這些信息。此處是前面ADO.NET SqlNotificationRequest示例產生的XML文檔的例子:
<qn:QueryNotification
xmlns:qn="http://schemas.microsoft.com/SQL/Notifications/QueryNotification"
id="2" type="change" source="data" info="update"
database_id="6" user_id="1">
<qn:Message>{CFD53DDB-A633-4490-95A8-8E837D771707}</qn:Message>
</qn:QueryNotification>
注意:盡管通知的產生是由於將job_id = 5這一行的job_desc列值修改為“new job”,但在消息體中永遠無法看到這些信息。通知可以判斷出某個SQL語句修改了數據並因此導致您的結果集發生了變化,但是它還不夠聰明,無法分辨出UPDATE語句是否真正對數據進行了修改。例如,將某行的job_desc = "new job" 修改成 job_desc = "new job" 將導致通知的產生。同樣地,由於查詢通知是異步的並且是在您執行SQL命令或批時注冊的,因此可能您還沒讀取完結果集就收到了通知。如果您提交的查詢不符合規則,那麼也會立刻收到通知。這些規則就是前面我提到的關於索引視圖的規則。
何時不使用通知?一些勸誡
由於您現在已經了解了查詢通知的工作原理,因此可以很容易指出它們的用處:可改寫只讀表。每個通知都會占用SQL Server資源,因此在只讀表上使用通知將是浪費和不經濟的。此外也不要將通知用於即席查詢,因為那樣將導致大量不同的行集同時被“監視”。這裡有一個很有用的內部細節,即SQL Server會合並參數化查詢使用不同參數時占用的通知資源。始終使用參數化查詢(像上面SqlNotificationRequest示例中那樣)允許您利用這一特點並獲得增強的性能。如果您聽到這些勸言後有些擔心,那麼請記住性能問題並不意味著您無法收到恰當的通知。如果使用au_lname作為參數,user1監視au_lname為A-M的authors數據而user2監視au_lname為N-Z的數據,那麼每個用戶將只會接收到針對他們數據子集的“正確”的通知。
最後一點注意事項:當某些人談論通知應用程序時,他們想象著一間滿是股票經紀人的房間以及不斷變化的股票價格,每個顯示器都不停變化著。這樣使用查詢通知絕對是錯誤的,原因有兩個:
1.由於行集不斷變化,網絡將被大量的查詢通知和查詢刷新請求阻塞。
2.如果有相當多的用戶並且他們都“監視”相同的數據,那麼每個通知都將導致大量用戶同時重新查詢相同的行集。大量對相同數據集的查詢請求也可能導致SQL Server拒絕服務!
如果您覺得有些程序員會濫用該特性,那麼可以高興地告訴您Beta2以後版本中,SQL Server可以通過動態管理視圖為DBA提供更多監視該特性的信息。目前這些視圖只能顯示訂閱的信息。請記住這種可能性是始終存在的,即SQL Server 2005“判定”查詢通知占用了太多資源並開始清理它們。
結論
查詢通知是內置在SQL Server 2005中的一個強大新特性,可以直接在ADO.NET 2.0和ASP.NET 2.0中使用。盡管ADO.NET的特性(SqlNotificationRequest和SqlDependency)只能在SQL Server 2005數據庫上工作,但ASP.NET通過池的機制使得該特性“向後兼容”。請謹慎正確的使用該特性,牢記它的含義以及影響。對於該特性而言最好的“甜點”就是ASP.NET中那些可能被其他應用程序,當然也包括Web應用程序更新的可改寫只讀表。對於這種應用場景,查詢通知提供了程序員們盼望多年的解決方案。