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

本文探讨了数据库事务的概念及其在.NET中实现的方法。事务能够保证多个数据库操作作为一个整体成功或失败,确保数据一致性。通过示例介绍了如何使用SqlTransaction类创建事务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文英文原版:
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封装到一个事务里.

祝编程快乐! 

### 数据库事务一致性(Consistency)的概念 数据库事务的一致性是指,在事务执行前后,数据库的状态都必须满足所有的完整性约束条件。这意味着任何事务都不能破坏数据库中的数据完整性规则[^3]。如果某个操作违反了这些规则,则整个事务会被视为失败并进行回滚。 #### 一致性的核心目标 一致性确保了数据库从一个合法状态转换到另一个合法状态。这种合法性由数据库的预定义约束决定,比如外键约束、唯一性约束以及用户自定义的业务规则等[^4]。只有当事务完成所有操作且未违反任何约束时,才会被提交;否则,它会通过回滚恢复到初始状态。 --- ### 实现一致性的主要机制 为了实现一致性数据库管理系统通常依赖以下几个方面: 1. **事务管理器 (Transaction Manager)** 事务管理器负责监督每个事务的行为,并验证其是否遵循 ACID 特性之一——即一致性。具体来说,它会在事务提交前检查是否存在违反约束的情况。如果有违规行为发生,则阻止该事务继续运行或将其撤销。 2. **锁机制 (Locking Mechanism)** 锁用于防止多个并发事务之间相互干扰而导致不一致的结果。例如,为了避免脏读现象的发生,可以采用共享锁和排他锁来控制访问权限[^5]。这样能够保证在一个事务尚未结束之前其他事务无法篡改相关联的数据项。 3. **多版本并发控制 (MVCC, Multi-Version Concurrency Control)** MVCC 是一种高级技术手段,允许不同时间点上的视图共存于同一时刻下供查询使用而不互相影响。这种方法特别适用于高并发场景下的 OLTP 应用程序中维护强一致性的同时提高性能效率[^2]。 4. **日志记录与崩溃恢复 (Logging and Recovery)** 日志文件保存着关于每笔交易的所有更改历史信息以便日后必要时候用来重建丢失或者损坏的信息副本从而达到持久化的目的同时也间接支持了一致性的达成因为即使系统突然中断也能依据先前写入磁盘的日志重新构建正确的最终结果集。 --- ### 示例代码展示如何手动处理 MySQL的一致性问题 下面是一个简单的例子展示了如何利用编程语言配合 SQL 来保持银行转账过程中账户余额总金额不变这一基本商业逻辑要求: ```sql START TRANSACTION; -- 假设我们有两个表 accounts 和 transactions UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 账户 A 减少 100 元 IF ROW_COUNT() != 1 THEN ROLLBACK; ELSE UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 账户 B 增加 100 元 IF ROW_COUNT() != 1 THEN ROLLBACK; END IF; COMMIT; ``` 上述脚本演示了一个典型跨两个独立实体之间的资金转移过程,其中包含了必要的错误检测步骤以确保存款总额不会因异常情况而改变。 --- ### 总结 综上所述,数据库事务的一致性不仅关乎单个交易内部各步动作间的协调统一,还涉及更广泛的层面如与其他现有数据间的关系维持等方面的工作。借助现代 DBMS 提供的各种工具和技术方案我们可以有效地管理和解决这些问题进而构建更加健壮可靠的应用环境^. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值