本文給出了類型化和非類型化XML列各自的優勢、將SQL Server 2000數據庫遷移到SQL Server2005,新的異常處理如何工作;如果表的架構發生更改,我如何執行操作等,供大家參考!
SQL Server 2005 包括對Transact-SQL (T-SQL) 語言的幾項重要改進。新增功能之一是一種新的觸發器,它在數據定義語言 (DDL) 語句運行時激發。在跟蹤或保護數據庫對象的創建和修改,或對數據庫服務器進行更改時,DDL 觸發器非常有用。另一個新功能涉及異常處理,該功能因包含TRY/CATCH 塊而在異常處理方面向前飛躍了一大步。另一組新功能則以新的XML數據類型為中心,該數據類型在與SQL Server交互的企業應用程序中管理XML數據方面前進了一大部。現在,XML 文檔或片段可以存儲於 XML 列、綁定到架構、還可以利用 XQuery 語法查詢。本文匯總各類問題來做出詳細解答。
問:類型化和非類型化 XML 列各自的優勢是什麼?
答:非類型化 XML 列可以存儲所有格式規范的 XML 片段或文檔,而類型化 XML 列可以綁定到 XML 架構。如果不確定 XML 將遵循哪個架構,則非類型化 XML 很有用。例如,如果您必須使用另一個應用程序的某些 XML,但無法確定數據將遵循哪個 XML 架構,則可以使用非類型化 XML 列來執行這項工作。當然,非類型化 XML 也會帶來其他問題。例如,不能針對某個架構編程,因此很難有效地使用 XML。但有時無法避免非類型化 XML。創建非類型化 XML 列非常簡單,如下所示:
CREATE TABLE Foo(FooID INT, someXml XML)
架構不綁定到 XML 列的另一個結果是,SQL Server® 2005 會將非類型化 XML 作為一個字符串存儲。這是件好事還是壞事?實際上,既是好事也是壞事。將 XML 作為字符串存儲使您可以更靈活地存儲任何 XML 片段或文檔。根據應用程序的業務規則,您可能需要這種方式。另一方面,將 XML 作為字符串存儲意味著,與類型化 XML 相比,既不能有效地存儲也不能有效地搜索 XML 數據。並且既不會告訴 SQL Server XML 將包含哪些內容,也不會告訴它層次結構和 XML 節點的數據類型。但請記住,僅僅因為這些是非類型化 XML 列並不意味著它們可以接受您設計出的任何格式。類型化和非類型化 XML 列仍然只接受格式規范的 XML。
如果您要存儲架構已知的 XML 文檔,類型化 XML 列很理想。架構可以定義元素、屬性、它們的數據類型、需要哪些字段以及數據的整個層次結構。由於這種詳細信息有關 XML 列的數據,因此 SQL Server 2005 可以在內部更加有效地存儲 XML 數據。如果您嘗試存儲與架構不匹配的 XML 數據,則 SQL Server 會檢測到這一點並阻止您。
創建類型化 XML 列就像在括號中添加架構名稱一樣簡單,如下所示:
CREATE TABLE Foo(FooID INT, someXml XML(CONTENT FooSchema))
該語句指出 someXml 列必須遵循名為 FooSchema 的 XML 架構集合。通過分別包含相應的關鍵字 DOCUMENT 或 CONTENT,XML 可指定為必須是一個文檔或者可以包含一個片段。如果省略,則默認值為 CONTENT。
可以使用 T-SQL 命令將 XML 架構集合添加到數據庫中,如下所示:
CREATE XML SCHEMA COLLECTION [FooSchema] AS N 'put your schema here'
也可以使用 SQL Server Management Studio (SSMS) 創建 XML 架構集合模板。從 VIEw 菜單打開 Template Explorer,然後導航到 XML Schema Collections 節點並展開它。然後,您可以雙擊 CREATE 模板打開一個模板,該模板為您創建 XML 架構集合提供了一個良好的語法開端(參見圖 1)。
圖1:XML架構集合模板是SSMS
SQL Server 2005 隨附的 AdventureWorks 數據庫有一個名為 HumanResources.JobCandidate 的表。該表包含一個名為 Resume 的列,該列是一個綁定到架構集合 HumanResources.HRResumeScheMacollection 的 XML 列。該列中存儲的所有 XML 數據都必須遵循該架構。
圖2:Resume架構
類型化和非類型化 XML 列都可以進行索引,但索引時,綁定到架構的 XML 列比非類型化 XML 列具有更多優點。將 XML 索引應用於非類型化 XML 列時,必須分析大部分 XML 結構來定位匹配的節點。但是,將 XML 索引應用於類型化 XML 列時,特定節點是可識別的並可根據架構定位。因此,索引可以在類型化 XML 中更有效地工作,因為它知道在哪裡查找。此外,如果需要搜索一個數值范圍,則索引將用非類型化 XML 執行一個數據類型轉換(因為數據類型是未知的)。類型化 XML 定義自己的數據類型,從而避免了轉換開銷。
問:我剛剛將 SQL Server 2000 數據庫遷移到 SQL Server 2005。我聽說 SQL Server 2005 在 T-SQL 中引入了 TRY/CATCH 異常處理。與現有的 IF @@ERROR <> 0 技術相比,新的異常處理如何工作呢?
答:通常對使用 T-SQL 編寫代碼的抱怨是,T-SQL 始終缺少一個健壯的異常處理結構。通過 SQL Server 2000,您可以編寫 T-SQL 批處理代碼,以檢查是否存在錯誤甚至何時引發錯誤(如果需要)。但與 TRY/CATCH 技術相比,執行此操作的技術是最基本的。首先,我將使用 @@ERROR 函數定義與異常處理相關聯的問題,然後展示新的 TRY/CATCH 技術與之相比是如何執行的。
SQL Server 公開內置的 @@ERROR 函數,該函數返回所執行的上一條 T-SQL 命令的錯誤號。該函數的問題在於,它始終返回從剛剛執行的上一個語句返回的錯誤。這意味著,如果您執行一個導致錯誤的 INSERT 語句,然後執行另一個不會導致錯誤的任意 SQL 語句,之後再查看 @@ERROR 的返回值,則該函數將返回 0,原因是上一個語句沒有返回錯誤。您必須十分小心,以確保在執行每個單獨語句之後檢查 @@ERROR 的值。
BEGIN TRANSACTION
DELETE [Order Details] WHERE OrderID IN
(SELECT OrderID FROM Orders WHERE CustomerID = 'ALFKI')
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
DELETE Orders WHERE CustomerID = 'ALFKI'
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
DELETE Customers WHERE CustomerID = 'ALFKI'
IF @@ERROR <> 0
BEGIN
ROLLBACK TRANSACTION
RETURN
END
PRINT 'I got here'
-- Normally do a COMMIT TRANSACTION here.
-- But I do a ROLLBACK so I don't truly delete my test data.
ROLLBACK TRANSACTION
展示一些示例代碼,這些代碼將在事務內部運行,依次刪除客戶的定單詳情、定單以及客戶本身。在每個 DELETE 語句之後,代碼都會檢查 @@ERROR 函數的值,以查看 DELETE 語句是否導致了錯誤。如果是,則回滾事務,停止執行代碼,然後從批處理操作返回。
這是標准的事務管理,即如果發生錯誤,則中止事務,然後將數據的狀態返回至其初始狀態。請注意,如果一切順利並且沒有發生錯誤,我會打印一個成功消息,然後回滾事務。通常,我不會成功地進行回滾,因為這是一個測試事務,我實際上並不希望刪除我的數據。
請注意,我必須遵循帶有 @@ERROR 函數的每個語句,然後再使用 ROLLBACK 和 RETURN。該方法十分糟糕。假設編寫一個包含許多查詢(這些查詢都需要檢查)的事務。該過程可以通過 GOTO 語句來稍加簡化,當然我並不是 GOTO 語句的提倡者,因為它們依舊未解決需要在查詢操作之後立即檢查 @@ERROR 函數的問題。
SQL Server 2005 仍然支持 @@ERROR 函數,但它還包括了廣為人知的 TRY/CATCH 模式。TRY/CATCH 結構與 C# try/catch 結構的類似之處在於,在 try 塊中捕獲錯誤,然後轉移到 catch 塊執行。(T-SQL 版本的 TRY/CATCH 沒有 FINALLY 子句選項,但 C# 版本有。您可以通過捕獲異常、不執行返回、然後使用適當的清除代碼執行 catch 塊來進行模擬。)因此,在某些情況下需要注意這一點,例如,在指定了 CURSOR 並在 T-SQL TRY 塊中引發錯誤時打開該游標的情況。在這種情況下,應該在 CATCH 塊中檢查游標以查看它是否已打開;如果已打開,則應將其關閉並取消指定。
BEGIN TRY
BEGIN TRANSACTION
DELETE [Order Details] WHERE OrderID IN
(SELECT OrderID FROM Orders WHERE CustomerID = 'ALFKI')
DELETE Orders WHERE CustomerID = 'ALFKI'
DELETE Customers WHERE CustomerID = 'ALFKI'
PRINT 'committing deletes'
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
RETURN
END CATCH
看到上面代碼中的 TRY/CATCH 結構後,您可能會震驚於它與 @@ERROR 技術相比是多麼簡潔。請注意,對於 TRY/CATCH 塊,不需要重復檢查是否有錯誤,因而減少了代碼行數同時也減少了出現編碼錯誤的地方。
上面代碼中的代碼試圖開始一個事務,執行一系列 DELETE 語句,然後提交該事務。(另請注意,在我的示例中,我沒有提交事務而是回滾該事務,因此實際上並沒有刪除數據。實際上,這應該是一個 COMMIT TRAN 語句。)
可以在 TRY/CATCH 結構中訪問多個內置函數,以幫助您確定導致代碼進入 CATCH 塊的原因。例如,可以在 CATCH 塊內部添加以下語句,返回有關該錯誤的信息:
SELECT
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage,
ERROR_NUMBER() AS ErrorNumber,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState
這些函數(如果適用)將返回發生錯誤的行號、錯誤消息、錯誤號、存儲過程或發生錯誤的觸發器、錯誤的嚴重級別以及錯誤的狀態級別。
TRY/CATCH 結構捕獲嚴重度為 11 到 19(包括 11 和 19)的錯誤。低於 11 的嚴重級別是警告,不視為錯誤。嚴重級別為 20 及以上的錯誤視為嚴重錯誤。然而,如果這些嚴重級別為 20 及以上的錯誤沒有導致數據庫引擎停止,則它們將由 TRY/CATCH 捕獲。總之,TRY/CATCH 語句更易於讀取、更易於維護,且更不容易導致復制和粘貼錯誤。此外,@@ERROR 技術也不總是很可靠。例如,某些錯誤會中止計劃或整個批處理。
問:我知道可以創建觸發器來審核對數據的更改,但是如果表的架構發生更改,我如何執行操作呢?
答:我在簡介中提到過,現在有了一個新的觸發器:SQL Server 2005 中的 DDL 觸發器。數據操作語言 (DML) 觸發器基於數據操作激發,而 DDL 觸發器在對數據庫架構或服務器進行更改時激發。創建的 DDL 觸發器只能在發生觸發事件之後激發,而 DML 觸發器則不同,它可以在事件之後或代替事件激發。以下是創建 DDL 觸發器的語法,在 SQL Server 2005 文檔中進行了概述:
CREATE TRIGGER trigger_name
ON { ALL SERVER | DATABASE }
[ WITH ENCRYPTION ]
{ FOR | AFTER } { event_type [ ,...n ] | DDL_DATABASE_LEVEL_EVENTS }
[ WITH APPEND ]
[ NOT FOR REPLICATION ]
{ AS
{ sql_statement [ ...n ] | EXTERNAL NAME < method specifIEr > }
}
< method_specifIEr > ::= assembly_name:class_name[::method_name]
請注意,您可以創建觸發器,以便它在對數據庫的架構或數據庫服務器進行更改時激發。您還可以通過指定事件類型(這些類型都列在 SQL Server 2005 文檔中)來定義導致觸發器激發的事件。或者,也可以通過指定 DDL_DATABASE_LEVEL_EVENTS 標識符讓 DDL 觸發器在所有事件之後激發。
通過 DDL 觸發器,您可以記錄對數據庫的架構所作的更改。可以使用 DDL 觸發器審核更改,而不是防止更改。或者,也可以通過 DDL 觸發器記錄嘗試操作,然後將其回滾。您可能還希望在創建對象時對其強制命名約定。例如,您可能希望所有存儲過程都以 pr 為前綴。通過 DDL 觸發器,您可以強制該命名約定。
DDL 觸發器與 DML 觸發器的類似之處在於,它們都可以回滾事務。然而,DDL 觸發器沒有插入或刪除的表。DDL 觸發器能夠訪問一個名為 eventdata 的內置函數,該函數在 XML 數據類型中返回 XML(包含有關激發觸發器的事件的信息)。每個事件在事件數據中顯示的信息都略有不同。但是,eventdata 函數始終為所有事件返回以下信息:
◆事件的時間
◆事件的類型
◆導致觸發器激發的連接 SPID
◆發生事件的用戶上下文的登錄名和用戶名
CREATE TRIGGER ddlTrigger_ProcNamingConvention
ON DATABASE
AFTER CREATE_PROCEDURE, ALTER_PROCEDURE
AS
DECLARE @eventDataXml XML
SET @eventDataXML = EVENTDATA()
IF SUBSTRING(@eventDataXML.value('(//ObjectName)[1]',
'VARCHAR(200)'), 1, 2) = 'pr'
PRINT 'starts with pr'
ELSE
BEGIN
PRINT 'does not start with pr'
ROLLBACK TRANSACTION
END
GO
請注意,這段代碼中的 DDL 觸發器定義為,在當前數據庫上創建或更改存儲過程時激發。當觸發器激發後,事件的數據會被捕獲並存儲在本地 XML 變量中。盡管這不是必要的,但如果您打算多次訪問 eventdata 函數,這有助於使觸發器更高效。然後,觸發器會檢查對象的名稱是否以 pr 開頭。如果是,則允許事務完成;如果不是,則回滾事務,並且存儲過程將保持不變。在本例中,我知道 ObjectName 元素將包含存儲過程的名稱,因為僅在創建或更改存儲過程時才會調用觸發器。
<EVENT_INSTANCE>
<EventType>CREATE_PROCEDURE</EventType>
<PostTime>2005-10-20T00:52:16.160</PostTime>
<SPID>51</SPID>
<ServerName>MYDBSERVER</ServerName>
<LoginName>CAMELOTjpapa</LoginName>
<UserName>dbo</UserName>
<DatabaseName>AdventureWorks</DatabaseName>
<SchemaName>dbo</SchemaName>
<ObjectName>test1</ObjectName>
<ObjectType>PROCEDURE</ObjectType>
<TSQLCommand>
<SetOptions ANSI_NULLS="ON" ANSI_NULL_DEFAULT="ON" ANSI_PADDING="ON"
QUOTED_IDENTIFIER="ON" ENCRYPTED="FALSE" />
<CommandText>create proc test1 as select getdate()</CommandText>
</TSQLCommand>
</EVENT_INSTANCE>
顯示執行以下命令後該觸發器的事件數據:
create proc test1 as select getdate
您還可以捕獲事件數據,並將其完整存儲到審核表的 XML 列中。另一個選擇是抽出特定節點的內容,並將其單獨存儲。顯然,實現視具體情況而定,但 DDL 觸發器為您提供的選擇有很多。使用 DDL 觸發器時需要記住的一件事是,它們是同步操作的。由於是同步執行,觸發器應該盡可能不執行費時的操作。否則,它們會對數據庫服務器的性能造成負面影響。與 DML 觸發器一樣,應該將 DDL 觸發器定義為僅執行所需的操作並快速完成。如果需要異步執行,可以使用 Service Broker 和 Event Notification 模型。