SQL Server 下一版本 — 代号“Yukon”的 Beta 1 版引入了许多对 T-SQL 的功能增强和新增功能,可以提高您的表达能力、错误管理水平和性能。本文中,我将讨论一些重要的功能增强和新功能,包括错误处理、递归查询和隔离。我将把注意力主要放在错误管理和相关的改进,并简要描述其他方面的功能增强。
除了这里叙述的功能以外,T-SQL 还有许多其他重要功能增强我没有深入探讨,因为其中一些从概念上讲并不新,而另一些又需要单独讨论。这些方面包括消息处理和服务代理平台,分区和 XML 的功能增强。(有关 Yukon 中 XML 功能增强的更多信息,请参阅本期中 Bob Beauchemin 撰写的“XML in Yukon: New Version Showcases Native XML Type and Advanced Data Handling”一文。)
错误处理SQL Server Yukon Beta 1 为在 T-SQL 中进行错误处理引入了新的 TRY/CATCH 构造。此构造可以用于捕获事务中止错误,甚至是在 SQL Server 以前的版本中会引起批处理中止的错误(转换错误、死锁等)。新的构造无法处理的错误类型是那些会导致会话中止的错误(通常是严重度为 21 和更高的错误,如硬件错误)。通常,您的错误处理代码如图 1 中所示。
XACT_ABORT 设置打开了,这样 SQL Server 可以将任何错误当作事务中止错误,从而使其能够被捕获和处理。在 TRY 块内,任何在显式事务内出现的错误会使控制权传递给紧跟在 TRY 块之后的 CATCH 块。如果没有错误出现,则跳过 CATCH 块。如果想研究所发生错误的类型并相应地做出反应,必须将 @@error 的返回值保存到位于 CATCH 块开始处的一个变量中,然后再开始研究。否则 @@error 返回的值可能不正确,因为除了 DECLARE 之外的任何语句都能够更改它。
当事务中止错误发生在位于 TRY 块里的事务内且控制权传递给 CATCH 块时,事务就进入了注定失败的状态。在您显式地发出一个 ROLLBACK 命令之前,锁是不会释放的,已经持续存储的工作也无法逆转。在发出 ROLLBACK 之前,不允许启动任何需要打开隐式或者显式事务的操作。您可以检查导致了错误的事务中已经更改的资源的内容,这样可以看到什么发生了更改,但是必须发出一个 ROLLBACK,以采取需要发生事务的补救措施。请注意,为了捕获 CATCH 块内出现的错误,必须在嵌套 TRY/CATCH 构造内编写代码。为了看一个更详细的示例,我们首先创建一个 ErrorLog 表(其中错误处理代码要对注释进行审核),然后创建 T1 和 T2 表,对它们发出查询,如我用图 2 的代码所完成的功能那样。
接下来,在新的连接(称为连接 1)中运行图 3 中的脚本(称为脚本 1)。脚本 1 将锁的超时设定设置为 30 秒并将死锁优先级设置为低,从那么它在一个死锁情况中自愿成为一个按正常优先级运行的进程发生死锁的牺牲品。TRY 块中的代码更新了 T1,等待 10 秒,然后从 T2 选择。如果事务无错误地完成,将在 ErrorLog 表中插入一行,其中有一个注释表明它成功完成。
CATCH 块设计成捕获主键冲突错误、锁的超时设定到期和重试逻辑的死锁错误。您可以通过更改赋给位于代码开始处的变量 @retry 的值,重新设置所需的重试次数,现在这个值被设置为 2。
在第一次运行图 3 中的代码之后,查看 ErrorLog 的内容。请注意事务成功完成了。要测试是否发生主键冲突错误,打开一个新的连接(称为连接 2)并运行以下代码:
INSERT INTO T1 VALUES(3)
回到连接 1 并再次运行脚本 1。如果查看 ErrorLog 的内容,应该可以看到其中记录了一个主键冲突错误。转到连接 2 并通过运行以下命令删除刚插入的行:
DELETE FROM T1 WHERE col1 = 3
要测试锁的超时设定是否到期,在连接 2 中运行以下代码:
BEGIN TRAN UPDATE T1 SET col1 = 1
回到连接 1 并再次运行脚本 1。在大约 30 秒后,应该出现一个错误。查看 ErrorLog 的内容,可以发现记录了一条锁的超时设定到期。转到连接 2 并发出一条 ROLLBACK 命令以回滚事务。
为了测试是否存在死锁,到连接 2 并粘贴以下代码,但是暂时不运行:
DECLARE @i AS INT BEGIN TRAN SET @i = 1 WHILE @i <= 2 BEGIN UPDATE T2 SET col1 = 2 WAITFOR DELAY '00:00:10' SELECT * FROM T1 WAITFOR DELAY '00:00:05' SET @i = @i 1 END ROLLBACK
转到连接 1,运行脚本 1 中的代码,然后立即运行连接 2 中的代码。大约一分钟之后,您将看到连接 1 中出现错误。查看 ErrorLog 的内容可以注意到在死锁错误之后进行了两次重试尝试,第三次尝试成功了,没有发生错误。查询 ErrorLog 表并查看其内容。
最后,如果想要在 TRY 块内引发您自己的事务中止错误,您可以使用 TRAN_ABORT 选项调用 RAISERROR 命令。
SNAPSHOT 隔离Yukon 引入了一种新的隔离级别,称为 SNAPSHOT,它允许您使用以下这种模式:写入程序不会阻碍读取程序,而且为读取程序提供了它们所请求数据的已提交版本。SQL Server Yukon 在 tempdb 中维护着一个链接列表,负责跟踪行的更改并为读取程序构造一个较旧的已提交的数据版本。这种隔离对于开放式锁定而言是有用的,在开放式锁定中 UPDATE 冲突并不常见。如果进程 1 检索数据,稍后又试图对它进行修改,如果进程 2 在进程 1 检索和修改之间也修改了同一数据,那么 SQL Server 就会因为出现冲突,在进程 1 试图进行修改时生成一个错误。然后,进程 1 可以尝试重新发出事务。这种模式在更新冲突不太常见的情况下会非常高效。
为了能够工作在 SNAPSHOT 隔离级别模式下,必须打开数据库选项 ALLOW_SNAPSHOT_ISOLATION,稍后您就会看到这一点。为了模拟一个写入程序不阻碍阅读程序的情景,则创建一个 testdb 数据库,打开相应的数据库选项,并通过运行以下代码,并创建一个在 datacol 列中具有值为“Version1”的 T1 表:
CREATE DATABASE testdb GO USE testdb ALTER DATABASE testdb SET ALLOW_SNAPSHOT_ISOLATION ON CREATE TABLE T1 ( keycol INT NOT NULL PRIMARY KEY, datacol VARCHAR(10) NOT NULL ) INSERT INTO T1 VALUES(1, 'Version1')
从连接 1 发出以下代码,它将打开一个事务并将 datacol 中的值更改为“Version2”:
USE testdb BEGIN TRAN UPDATE T1 SET datacol = 'Version2' WHERE keycol = 1 SELECT * FROM T1
转到连接 2 并运行以下代码,它将把会话的隔离级别设置为 SNAPSHOT,并检索 T1 的内容:
USE testdb SET TRANSACTION ISOLATION LEVEL SNAPSHOT SELECT * FROM T1
请注意即使连接 2 将值改为“Version2”(但是还没有提交更改),检索回的还是“Version1”。
现在转到连接 1 并提交事务,然后用一个 COMMIT 命令关闭所有连接。为了尝试开放式锁定,打开两个新的连接,转到连接 1 并运行以下代码,它将把会话的隔离级别设置为 SNAPSHOT,打开一个事务,并从 T1 检索数据:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT BEGIN TRAN SELECT * FROM T1
转到连接 2 并发出一条 UPDATE 命令:
UPDATE T1 SET datacol = 'Version3' WHERE keycol = 1
回到连接 1 并尝试更新前面检索到的同一数据。它已经被连接 2 修改了:
UPDATE T1 SET datacol = 'Version4' WHERE keycol = 1
将会获得错误,通知您 SQL Server 不能使用快照隔离访问数据库 testdb 中的表 T1,而且您应该重试事务。
WAITFOR 命令Yukon 中 WAITFOR 命令在许多方面进行了增强。除了等待指定的持续时间或者等待到某个 datetime 值,现在您还可以请求等待一条至少影响一行的 T-SQL 语句。可以指定命令等待以下语句之一:SELECT、INSERT、UPDATE、DELETE 或者 RECEIVE。前面的四个无需解释了;RECEIVE 指的是从队列中接收一条消息。如果希望在指定的毫秒数之后停止等待,可以选择性地指定一个超时设定值。WAITFOR 命令的语法如下:
WAITFOR() [,TIMEOUT ]
Yukon 中另一个对 T-SQL 的功能增强允许您从数据操作语言 (DML) 的语句而不是 SELECT (INSER