事务:
事务是将一组任务封闭在一个执行单元中。当在这个执行单元中的所有任务都得以成功执行时,每个事务都以一个特定的任务开始和结束。如果这个执行单元中的任意一个任务执行失败时,这个事务就执行失败。因此,事务的执行结果只有两种:要么成功,要么失败。不完整的执行步骤也将倒致事务的执行失败。
用户可以使用下面的语句将两个或更多的T-SQL语句封装到一个事务中:
- Begin Transaction (可简写为:Begin Tran)
- Rollback Transaction (可简写为:Rollback Tran)
- Commit Transaction (可简写为:Commit Tran)
如果事务中的任何一个任务发生了任意一种错误,那么事务中所有的更改都应当中止。在SQL Server术语中把撤销更改操作的过程称作“回滚”(rollback)。如果一个事务中的所有语句都执行正常,那么在事务中完成的所有更改操作都将一起记录到数据库中。在SQL Server术语中,我们称为:这些更改操作被提交到数据库中(在数据库中得以永久保存)
下面就是一个事务的例子:
-----------------------------------------------------
USE pubs
DECLARE @intErrorCode INT
BEGIN TRAN
UPDATE Authors
SET Phone = '415 354-9866'
WHERE au_id = '724-80-9391'
SELECT @intErrorCode = @@ERROR
IF (@intErrorCode <> 0) GOTO PROBLEM
UPDATE Publishers
SET city = 'Calcutta', country = 'India'
WHERE pub_id = '9999'
SELECT @intErrorCode = @@ERROR
IF (@intErrorCode <> 0) GOTO PROBLEM
COMMIT TRAN
PROBLEM:
IF (@intErrorCode <> 0) BEGIN
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
END
-----------------------------------------------------
在真正的处理流程开始之前,BEGIN TRAN语句告知SQL Server把下面的所有操作视为一个事务。这里BEGIN TRAN语句后跟有两个UPDATE语句。如果更新操作中未发生任何错误,那么当SQL Server执行COMMIT TRAN语句后,所有的更改操作都将提交给相应的数据库,并且最终结束这个存储存储过程。如果更新操作中发生了某个错误(这个错误是通过if语句检测到的),那么执行流程将转向PROBLEM标记的T-SQL语句块。这个语句块的作用是:给用户提示一个错误信息,然后SQL Server回滚在处理流程中发生的所有更改操作。
注意:确保有COMMIT或ROLLBACK与BEGIN TRAN进行配对出现。
嵌套事务:
SQL Server支持嵌套事务。这个特性主要意味着:一个新的事务在其前一个事务尚未完成之前就可开始。Transact-SQL允许你通过发出嵌套BEGIN TARN命令进行事务的嵌套操作。我们可以通过检索@@TRANCOUNT自动变量从而获知嵌套的层数:0表示没有嵌套事务,1表示嵌套了一层事务。依此类推。
------现在时间:22:53 2011-4-5。先去吃个饭,下文待续
------现在时间:09:33 2011-4-8。有事担搁了,下面继续完成工作
除了最外层的COMMIT语句,与其他嵌套事务配套的COMMIT语句的执行都不会真正提交任何更改操作到磁盘中,这些COMMIT语句的执行中只是使@@TRANCOUNT自动变量的值减1。然而,ROLLBACK语句不管它是与第几层嵌套事务匹配的,只要它被执行则不论当前有多少层嵌套事务,所有的事务都将被回滚。虽然这些个东东理解起来不是那么简单明了,但是这个机制的存在是有它的现实意义的:如果一个嵌套的COMMIT语句的执行将其匹配的嵌套事务中所做的更改操作永久性的写入了磁盘中(只要写入磁盘就变成永久保存了,这是相对于磁盘是一个永久性存储设备而言的),那么,当一个外层的ROLLBACK语句被执行时,这些更改操作就不能回滚了,因为这些更改操作已经被永久性的写入磁盘了。
当你显示的开始一个事务后,“自动变量”@@TRANCOUNT的值就会从0增加到1;当你执行COMMIT语句后,其值会减1;当你执行ROLLBACK语句后,其值就会被重置为0。到此为止,你已经看出来了:COMMIT和ROLLBACK这两个语句的执行是不对称的:当你嵌套一个事务时,如图1所示、COMMIT语句的执行总是将当前事务嵌套的层数减1。然后,ROLLBACK语句的执行,如图2所示、将会回滚所有的事务。COMMIT和ROLLBACK的不对称性恰恰正是处理嵌套事务出错的关键所在。
图1:一个COMMIT语句的执行总是使当前事务数减1以平衡BEGIN TRANSACTION语句
图2:一个ROLLBACK语句总是回滚全部的事务
当如图1和图2所示,你能嵌套事务并且使用@@TRANCOUNT的值检测当前事务嵌套的层数。到目前为此你也了解了COMMIT和ROLLBACK是具有不对称性的,这种不对称性表现在:COMMIT语句的执行只是使@@TRANCOUNT的值减1,而ROLLBACK语句的执行使@@TRANCOUNT的值被置为0。这也就意味着一个事务的真正提交只发生在最后一个COMMIT语句执行之后,不管你嵌套了多少层事务,只有最后的COMMIT语句的执行会产生真正的作用。
下面是一个嵌套事务的例子:
-----------------------------------------------------
USE pubs
SELECT 'Before BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 0
BEGIN TRAN
SELECT 'After BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 1
DELETE sales
BEGIN TRAN nested
SELECT 'After BEGIN TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 2
DELETE titleauthor
COMMIT TRAN nested
-- Does nothing except decrement the value of @@TRANCOUNT
SELECT 'After COMMIT TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 1
ROLLBACK TRAN
SELECT 'After ROLLBACK TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 0
-- because ROLLBACK TRAN always rolls back all transactions and sets
-- @@TRANCOUNT to 0.
SELECT TOP 5 au_id FROM titleauthor
-----------------------------------------------------
在上面这个例子中我们可以看到即使执行了嵌套的COMMIT TRAN语句,但是外层的ROLLBACK语句的执行依然会将DELETE titleauthor命令所产生的更改操作撤消。
下面是另一个与上例相类似的嵌套事务示例:
-----------------------------------------------------
USE pubs
SELECT 'Before BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 0
BEGIN TRAN
SELECT 'After BEGIN TRAN', @@TRANCOUNT -- The value of @@TRANCOUNT is 1
DELETE sales
BEGIN TRAN nested
SELECT 'After BEGIN TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 2
DELETE titleauthor
ROLLBACK TRAN
SELECT 'After COMMIT TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 0 because
-- ROLLBACK TRAN always rolls back all transactions and sets @@TRANCOUNT
-- to 0.
IF (@@TRANCOUNT > 0) BEGIN
COMMIT TRAN -- Never makes it here cause of the ROLLBACK
SELECT 'After COMMIT TRAN', @@TRANCOUNT
END
SELECT TOP 5 au_id FROM titleauthor
-----------------------------------------------------
保存点提供一种机制可以只回滚事务的一部分操作。用户可以在一个事务中设置保存点或标记点。保存点定义了一个在事务的部分操作在某个条件下被取消时,事务可以回滚到的位置。SQL Server允许你通过SAVE TRAN语句设定保存点,使用SAVE TRAN不会影响@@TRANCOUNT的值。同样的,回滚保存点(而不是回滚事务)的操作也不会影响@@TRANCOUNT的值。然而,这个回滚操作必须显示的指定这个保存点:使用不带指定保存点名称的ROLLBACK TRAN语句总是回滚整个事务。
在本例中,执行流程永远都不会到达外层的COMMIT TRAN。因为ROLLBACK TRAN回滚了当前进程中的所有事务,并且置@@TRANCOUNT的值为0。除非ROOLBACK TRAN语句在被调用时带有一个“保存点”(save point),否则ROLLBACK TRAN不管是在哪里被调用,它总是回滚所有的事务操作并置@@TRANCOUNT为0。
保存事务和保存点:
下面的脚本演示了保存点的使用方法:
-----------------------------------------------------
USE pubs
SELECT 'Before BEGIN TRAN main', @@TRANCOUNT
-- The value of @@TRANCOUNT is 0
BEGIN TRAN main
SELECT 'After BEGIN TRAN main', @@TRANCOUNT
-- The value of @@TRANCOUNT is 1
DELETE sales
SAVE TRAN sales -- Mark a save point
SELECT 'After SAVE TRAN sales', @@TRANCOUNT
-- The value of @@TRANCOUNT is still 1
BEGIN TRAN nested
SELECT 'After BEGIN TRAN nested', @@TRANCOUNT
-- The value of @@TRANCOUNT is 2
DELETE titleauthor
SAVE TRAN titleauthor -- Mark a save point
SELECT 'After SAVE TRAN titleauthor', @@TRANCOUNT
-- The value of @@TRANCOUNT is still 2
ROLLBACK TRAN sales
SELECT 'After ROLLBACK TRAN sales', @@TRANCOUNT
-- The value of @@TRANCOUNT is still 2
SELECT TOP 5 au_id FROM titleauthor
IF (@@TRANCOUNT > 0) BEGIN
ROLLBACK TRAN
SELECT 'AFTER ROLLBACK TRAN', @@TRANCOUNT
-- The value of @@TRANCOUNT is 0 because
-- ROLLBACK TRAN always rolls back all transactions and sets @@TRANCOUNT
-- to 0.
END
SELECT TOP 5 au_id FROM titleauthor
-----------------------------------------------------
错误处理:
这里的示例针对的是存储过程,存储过程是和数据库进行交互的理想解决方案。当存储过程中发生某个错误时,最好的处理方法是:中止代码的顺序执行而转向执行流程中的另一个代码块或返回至调用应用程序。自动变量@@ERROR在错误处理代码中被用来检测当前发生错误的个数。自动变量@@ERROR包含了错误标识号,这个错误标识号是在用户连接期间的最后一条SQL语句执行后所产生的。当某个语句执行成功后,@@ERROR包括0。我们可以通过在待检测语句执行后立即使用IF语句检测@@ERROR的值来确定这个语句是否执行成功。注意:在待检测语句执行后,请立即使用IF语句检测@@ERROR的值。因为当下一语句执行成功后@@ERROR的值会被置0。当一个可截获的错误发生时,@@ERROR的值就会大于0。但是你必须立即捕获@@ERROR的值,因为SQL Server会在任何命令执行成功后将@@ERROR的值置0。大多数情况下,人们都会在任何INSERT,UPDATE或DELETE语句执行后立即检测@@ERROR的值的改变。
-----------------------------------------------------
CREATE PROCEDURE addTitle(@title_id VARCHAR(6), @au_id VARCHAR(11), @title VARCHAR(20), @title_type CHAR(12))
AS
BEGIN TRAN
INSERT titles(title_id, title, type)
VALUES (@title_id, @title, @title_type)
IF (@@ERROR <> 0) BEGIN
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
RETURN 1
END
INSERT titleauthor(au_id, title_id)
VALUES (@au_id, @title_id)
IF (@@ERROR <> 0) BEGIN
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
RETURN 1
END
COMMIT TRAN
RETURN 0
-----------------------------------------------------
上述这种错误处理解决方案存在大量的冗余代码,尤其当你的业务逻辑需要多于两个Transant-SQL语句时这种冗余现象来体现出来。一个更好的解决方案是:把错误处理代码封装到一个通用的处理过程中:
-----------------------------------------------------
CREATE PROCEDURE addTitle(@title_id VARCHAR(6), @au_id VARCHAR(11),
@title VARCHAR(20), @title_type CHAR(12))
AS
BEGIN TRAN
INSERT titles(title_id, title, type)
VALUES (@title_id, @title, @title_type)
IF (@@ERROR <> 0) GOTO ERR_HANDLER
INSERT titleauthor(au_id, title_id)
VALUES (@au_id, @title_id)
IF (@@ERROR <> 0) GOTO ERR_HANDLER
COMMIT TRAN
RETURN 0
ERR_HANDLER:
PRINT 'Unexpected error occurred!'
ROLLBACK TRAN
RETURN 1
-----------------------------------------------------