本文内容
释义
分布式事务跨越两个或多个称为资源管理器的服务器。称为事务管理器的服务器组件必须在资源管理器之间协调事务管理。如果分布式事务由 Microsoft 分布式事务处理协调器 (MS DTC) 之类的事务管理器或其他支持 X/Open XA 分布式事务处理规范的事务管理器来协调,则每个 SQL Server Database Engine 实例都可以作为资源管理器来运行。有关详细信息,请参阅 MS DTC 文档。
跨越两个或多个数据库的单个数据库引擎 实例中的事务实际上是分布式事务。该实例对分布式事务进行内部管理;对于用户而言,其操作就像本地事务一样。
对于应用程序而言,管理分布式事务很像管理本地事务。当事务结束时,应用程序会请求提交或回滚事务。不同的是,分布式提交必须由事务管理器管理,以尽量避免出现因网络故障而导致事务由某些资源管理器成功提交,但由另一些资源管理器回滚的情况。通过分两个阶段(准备阶段和提交阶段)管理提交进程可避免这种情况,这称为两阶段提交 (2PC)。
准备阶段:
当事务管理器收到提交请求时,它会向该事务涉及的所有资源管理器发送准备命令。然后,每个资源管理器将尽力使该事务持久,并且所有保存该事务日志映象的缓冲区将被刷新到磁盘中。当每个资源管理器完成准备阶段时,它会向事务管理器返回准备成功或准备失败的消息。
提交阶段:
如果事务管理器从所有资源管理器收到准备成功的消息,它将向每个资源管理器发送一个提交命令。然后,资源管理器就可以完成提交。如果所有资源管理器都报告提交成功,那么事务管理器就会向应用程序发送一个成功通知。如果任一资源管理器报告准备失败,那么事务管理器将向每个资源管理器发送一个回滚命令,并向应用程序表明提交失败。
数据库引擎 应用程序可以通过 Transact-SQL 或数据库 API 来管理分布式事务。
测试数据库
数据库名称:test
数据库表名称:Users
表结构:
列名 |
数据类型 |
允许为空 |
注释 |
UsersID |
int |
false |
主键,自增标识 |
UserName |
varchar(50) |
false |
用户名,唯一约束 |
Password |
varchar(50) |
false |
在文章开始之前,我们首先要明确:事务是单个的工作单元。如果某一事务成功,则在该事务中进行的所有数据修改均会提交,成为数据库中的永久组成部分。如果事务遇到错误且必须取消或回滚,则所有数据修改均被清除。也就是说,当我们使用 SQL Server 的查询分析器中执行了了一行代码 "insert into t values(x, y, z)",数据成功地添加到了数据库中,但这并不是说这里没有使用事务处理,而是系统帮我们将常用的操作自动地做了一些处理(我猜想应该是代理模式)。
SQL Server 数据库为我们提供了以下几种事务模式:
自动提交事务 每条单独的语句都是一个事务。
显式事务 每个事务均以 BEGIN TRANSACTION 语句显式开始,以 COMMIT 或 ROLLBACK 语句显式结束。
隐式事务 在前一个事务完成时新事务隐式启动,但每个事务仍以 COMMIT 或 ROLLBACK 语句显式完成。
批处理级事务 只能应用于多个活动结果集 (MARS),在 MARS 会话中启动的 Transact-SQL 显式或隐式事务变为批处理级事务。当批处理完成时没有提交或回滚的批处理级事务自动由 SQL Server 进行回滚。
第一节 T-SQL 中的事务处理
下面的代码演示了如何使用 T-SQL 语句创建一个显式事务:
- --开启一个事务begin transaction--使用
- try…
- catch结构捕获异常begin
- try--插入两条数据(相同的用户名)INSERT INTO[test]. [dbo]. [Users]([UserName], [Password]) VALUES('张三', '123') INSERT INTO[test]. [dbo]. [Users]([UserName], [Password]) VALUES('张三', '456') commit transaction--提交事务end
- try--如果出现了异常,进入
- catch代码段begin
- catch rollback transaction--回滚事务select ERROR_MESSAGE() as[Message]--输出错误信息end
- catch
以上代码执行后会失败,错误信息为:"违反了 UNIQUE KEY 约束'UQ_Users_UserName'。不能在对象'dbo.Users'中插入重复键"。
需要注意的是,在 SQL Server 中,如果没有显式声明事务,那么系统将分配隐性事务。当数据库引擎 实例首次执行下列任何语句时,都会自动启动一个事务:
ALTER TABLE |
INSERT |
CREATE |
OPEN |
DELETE |
REVOKE |
DROP |
SELECT |
FETCH |
TRUNCATE TABLE |
GRANT |
UPDATE |
第二节 ADO.NET 中的事务处理
以下代码演示了如何使用 SqlClient 组件来创建一个显式事务:
- using(SqlConnection connection = new SqlConnection(connectionString)) {
- connection.Open();
- SqlCommand command = connection.CreateCommand();
- SqlTransaction transaction;
- // 启动一个本地事务
- transaction = connection.BeginTransaction();
- // 在启动一个本地事务之前,需要为Command对象指派Connection对象与Transaction对象
- command.Connection = connection;
- command.Transaction = transaction;
- try {
- command.CommandText = "INSERT INTO [test].[dbo].[Users] ([UserName] ,[Password]) VALUES ('张三','123')";
- command.ExecuteNonQuery();
- command.CommandText = "INSERT INTO [test].[dbo].[Users] ([UserName] ,[Password]) VALUES ('张三','123')";
- command.ExecuteNonQuery();
- // 尝试提交事务
- transaction.Commit();
- Console.WriteLine("所有的记录已经提交至数据库");
- } catch(Exception ex) {
- Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
- Console.WriteLine("异常信息: {0}", ex.Message);
- // 尝试回滚事务
- try {
- transaction.Rollback();
- } catch(Exception ex2) {
- // 此catch块将处理任何可能发生在服务器上的,导致回滚失败的错误。如连接已关闭。
- Console.WriteLine("Rollback Exception Type: {0}", ex2.GetType());
- Console.WriteLine(" Message: {0}", ex2.Message);
- }
- }
- }
以上代码执行后会失败,错误信息为:"违反了 UNIQUE KEY 约束'UQ_Users_UserName'。不能在对象'dbo.Users'中插入重复键"。
第三节 LINQ to SQL 中的隐式事务处理
下面的代码演示了如何使用 LINQ to SQL 技术进行事务处理:
- using(TestDataContext db = new TestDataContext()) {
- // 在控制台中输出T-SQL语句
- db.Log = Console.Out;
- //创建两个Users对象(注意,用户名是相同的)
- Users u1 = new Users() {
- UserName = "张三",
- Password = "123"
- };
- Users u2 = new Users() {
- UserName = "张三",
- Password = "456"
- };
- try {
- db.Users.InsertAllOnSubmit(new Users[] {
- u1,
- u2
- });
- // 尝试提交事务
- db.SubmitChanges();
- Console.WriteLine("所有的记录已经提交至数据库");
- } catch(Exception ex) {
- Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
- Console.WriteLine("异常信息: {0}", ex.Message);
- }
- }
以上代码执行后会失败,错误信息为:"违反了 UNIQUE KEY 约束'UQ_Users_UserName'。不能在对象'dbo.Users'中插入重复键"。
在代码中,当调用 SubmitChanges 时,LINQ to SQL 会检查此调用是否在 Transaction 的作用域内或者 Transaction 属性 (IDbTransaction) 是否设置为由用户启动的本地事务。如果这两个事务它均未找到,则 LINQ to SQL 启动本地事务 (IDbTransaction),并使用此事务执行所生成的 SQL 命令。当所有 SQL 命令均已成功执行完毕时,LINQ to SQL 提交本地事务并返回。这就是 LINQ to SQL 的 "隐式事务"。
第四节 分布式事务处理
本文进行到这里,将使用一个问题引入标题内容 "分布式事务处理" 是怎样的一种操作:如何将两个 DataContext 对象的提交过程当做一个事务来处理?
可能你对这个问题不是太明白,下面的代码演示了这个问题是一个怎样的过程:
- //创建两个TestDataContext对象
- TestDataContext db1 = new TestDataContext();
- TestDataContext db2 = new TestDataContext();
- //创建两个Users对象(注意,用户名是相同的)
- Users u1 = new Users() {
- UserName = "张三",
- Password = "123"
- };
- Users u2 = new Users() {
- UserName = "张三",
- Password = "456"
- };
- try {
- //将两个Users对象分别加入到不同的TestDataContext对象中
- db1.Users.InsertOnSubmit(u1);
- db2.Users.InsertOnSubmit(u2);
- // 尝试提交事务
- db1.SubmitChanges();
- db2.SubmitChanges();
- Console.WriteLine("所有的记录已经提交至数据库");
- } catch(Exception ex) {
- Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
- Console.WriteLine("异常信息: {0}", ex.Message);
- }
以上代码执行后同样会失败,错误信息为:"违反了 UNIQUE KEY 约束'UQ_Users_UserName'。不能在对象'dbo.Users'中插入重复键"。但是明显的,数据库中将成功添加一条数据——db1.SubmitChanges() 执行时提交的数据。
那么,如何才能让这个过程符合我们的要求呢?以下代码演示了这一点:
- //创建两个TestDataContext对象
- TestDataContext db1 = new TestDataContext();
- TestDataContext db2 = new TestDataContext();
- //创建两个Users对象(注意,用户名是相同的)
- Users u1 = new Users() {
- UserName = "张三",
- Password = "123"
- };
- Users u2 = new Users() {
- UserName = "张三",
- Password = "456"
- };
- //将两个Users对象分别加入到不同的TestDataContext对象中
- db1.Users.InsertOnSubmit(u1);
- db2.Users.InsertOnSubmit(u2);
- // 使用TransactionScope对象,使代码块成为事务性代码。
- using(TransactionScope rs = new TransactionScope()) {
- try {
- // 尝试提交事务
- db1.SubmitChanges();
- db2.SubmitChanges();
- rs.Complete();
- Console.WriteLine("所有的记录已经提交至数据库");
- } catch(Exception ex) {
- Console.WriteLine("引发的异常类型为: {0}", ex.GetType());
- Console.WriteLine("异常信息: {0}", ex.Message);
- }
- }
当决定使用 SQL Server 分布式事务处理模式时,必须启动名称为 Distributed Transaction Coordinator 的 Windows 服务项,你可以在服务管理器中启动它(开始菜单 - 运行 - services.msc),也可以在命令提示符中直接启动它(net start msdtc)。
这时,包含在 TransactionScope 对象代码块中的两个 DataContext 对象,其执行 SubmitChanges 方法时将处于事务之中,如果此时出现了异常,那么之前的所有操作将会回滚。也就是说如果在事务范围中(即从初始化 TransactionScope 对象到调用其 Dispose 方法之间)发生异常了异常,事务都将结束,并回滚至初始状态。这意味着,即使执行了 Complete 方法,在这之后发生的异常也将导致事务失败。所以,尽量不要在执行 Complete 方法与 Dispose 方法之间,加入可能会导致事务失败的代码。
如果 TransactionScope 对象成功执行了 Complete 方法,那么所有数据将提交至数据库中。需要注意的是,如果在 TransactionScope 对象代码块中未执行 TransactionScope 对象的 Complete 方法,那么事务也将终止并回滚至初始状态。
同样的,TransactionScope 对象代码块也适用于其他代码级数据库访问,例如将多个 SqlConnection 对象的提交过程放在同一个 TransactionScope 对象代码块中,就可以方便地控制事务的执行方式。
转自
来源: http://lib.csdn.net/article/dotnet/41962