利用事务维持数据库的一致性

本文英文原版:
http://aspnet.4guysfromrolla.com/articles/072705-1.aspx

利用事务维持数据库的一致性

导言:

虽然数据库可以存储大量的数据供我们查询,但如果这些数据是错误的、无意义的那么这些数据和查询功能都变的毫无意义.不过数据库有很多的技术来确保数据的完整性和一致性:primary key约束和unique约束用于确保实体完整性;foreign key约束确保关系完整性,而事务transactions则确保数据库的数据维持一致性.

虽然INSERT, UPDATE,和DELETE statement是对数据库最常见的操作,但在有些时候我们想将多个INSERT, UPDATE, 和/或DELETE statements当作一个原子操作(atomic operation)来对待.也就是说,在某些情况下,我们不想把每个INSERT, UPDATE,和DELETE statement单独对待,而是将一系列的这些statement作为不可分割的整体的进行处理.当发出这些statement时,我们希望它们要么都执行成功,要么都执行失败——不应该有一些成功一些失败的情况发生.

比较典型的事务案例是将money从一个帐户转到另一个帐户上.银行处理money帐户转移要处理2个步骤;如果我们从经常帐户上转$500到储蓄存款帐户上,我们应处理下面的2个步骤:

.首先,必须从经常帐户上扣除$500;
.然后,将这$500增加到储蓄存款帐户上;


Transaction示例

在考察建立一个事务所必需的.NET代码之前,我们先讨论需要用到事务的比较常见的场合.前面我们提到了使用事务的典型场景——现金转帐.如果你要对一个或多个table执行多个修改命令——INSERT, UPDATE,或DELETE——且这些行为需要作为一个整体作为原子单元的时候,那么你就要使用到事务了.

比较常见的例子是在一个数据库里有2个或更多的有父/子关系的表的情况.当从父表删除一条记录时,你需要删除相关的子记录(或为这些子记录重新分配父关系).因此,当你要删除一个父记录时,你应该包含2个SQL statements,比如:

-- DELETE child records
DELETE FROM ChildTable
WHERE ParentID = IDofParentBeingDeleted

-- DELETE parent record
DELETE FROM ParentTable
WHERE ParentID = IDofParentBeingDeleted

另一个比较常见的例子是,你有2个逻辑相关的表,当添加或更新一个表时,也需要同时添加或更新另一个表.比如,假设有一个在线汽车保险网站,你除了要提交于保险相关的信息——日期、年龄、婚姻关系等之外,你可能还要提供你知晓该网站的途径——广播、电视、朋友等等.当点击提交后,网站会做2个添加记录动作——一个是与保险相关的,另一个是登陆者找到该网站的途径信息.


事务不仅可以保护免受流程步骤中断而带来的意想不到的灾难事故影响,也可以保护免受意想不到的与SQL相关的错误.比如,假想你有5个UPDATE指令,你想把它作为一个逻辑上一组的、单一的原子操作.但是不管是什么原因,第5个UPDATE指令包含了一个非法的值,并导致错误.如果没有事务的话,第5条指令不会更新数据库,但前面4条记录会,这样一来,在逻辑上数据库就陷入一种逻辑不一致的状态.


事务的通常步骤

使用事务时,一般来说你要用到下面的步骤:

1.明确指出你想开启一个事务.所有的指令从此时起在逻辑上都是原子操作的一部分
2.发出指令——那些包含在事务里的INSERT, UPDATE,和DELETEs
3.如果出错,回滚事务.回滚的作用在于前面执行的指令都无效.
4.如果全部正常,则提交事务.通过事务对数据库更新.

因为事务是"原子的",所有如果有任何的突然事故——比如断电、数据库服务器发生碰撞等——事务就会回滚,确保系统的一致性.

当通过.NET来处理事务时,你将开启事务,再通过事务对象来发出一系列的指令.当执行SQL statements出错时用Try ... Catch来捕获异常,此时你可以回滚事务。如果没有出错则提交事务.

用SqlTransaction Class类处理事务

如果你用的是Microsoft SQL Server,那么你就可以使用System.Data.SqlClient.SqlTransaction class类来开启一个事务.首先通过SqlConnection class类来打开一个到数据库的连接,然后调用SqlConnection class类的BeginTransaction()方法来创建一个事务实例,如下:

'Create a connection
Dim myConnection As New SqlConnection(myConnString)
myConnection.Open()

'Start the transaction
Dim myTrans As SqlTransaction = myConnection.BeginTransaction

接下来,你要创建一个用来发出指令的SQL statements的SqlCommand对象.当创建该对象后,你需要指定它使用ID为myTrans的SqlTransaction对象.你可以通过通过构造器来指派,或者通过SqlCommand的Transaction属性来指派.

... Continued from above ...

Try
'Specify the first statement to run...
Dim sql as String = "INSERT INTO ..."

'Create the SqlCommand object, specifying the transaction through
'the constructor (along with the SQL string and SqlConnection)
'Alternatively, could set properties of myCommand
'to specify the Connection, CommandText, and Transaction...
Dim myCommand as New SqlCommand(sql, myConnection, myTrans)

注意,我们已经有了SqlCommand对象,并且在Try ... Catch模块里发出对数据库的指令,此时,你就可以继续发出对数据库的指令了:

... Continued from above ...

myCommand.ExecuteNonQuery()

'Issue another INSERT
myCommand.CommandText = "INSERT INTO ..."
myCommand.ExecuteNonQuery()

... Lather, rinse, repeat as needed! ...

'If we reach here, all command succeeded, so commit the transaction
myTrans.Commit

Catch ex as Exception
'Something went wrong, so rollback the transaction
myTrans.Rollback()

Throw 'Bubble up the exception
Finally
myConnection.Close()'Finally, close the connection
End Try

要做的就这些了!你可以向数据库发出剩下的那些相关的SQL statements.注意,如果发生任何的错误将回滚事务,如果一切无误的话则提交事务.不过管是否引发异常都将执行Finally模块里的代码,关闭数据库连接.

Maintaining Transactions in T-SQL
你也可以直接用T-SQL语法来管理事务.而不用在你的代码里使用SqlTransaction class类,你可以将transaction syntax转移到一个存储过程里(也就是用多个statement来改动数据).具体信息请参考文章《Managing Transactions in SQL Server Stored Procedures》

结语:

在本文我们考察了数据库事务的概念,以及如何在.NET里将SQL statement封装到一个事务里.在创建ASP.NET数据驱动应用程序时维护你的数据的一致性是很重要的.当多个INSERT, UPDATE,或DELETE statements构成一个逻辑上的原子操作时,我们很有必要将这些statement封装到一个事务里.

祝编程快乐!

你可能感兴趣的:(sql,编程,.net,SQL Server,asp.net)