ADO.NET事宜拾遗
ADO.NET事务拾遗
代码段1:
说明:insertString1、insertString2和selectString操作的是同一张表
1.运行这段代码,到第21行(sda.Fill(ds))会出现异常:“如果分配给命令的连接位于本地挂起事务中,ExecuteReader 要求命令拥有事务。命令的 Transaction 属性尚未初始化。”
原因:SqlDataAdapter在填充数据时使用SqlDataReader读取数据,事务是基于数据库连接的,cmd1和cmd2使用的是同一连接,执行完cmd1后,事务处于挂起状态,还未提交。为了保持事务的完整性(其实这个地方是原子性的体现),要求新的cmd2必须也使用同一事务。
代码段2:将代码段1稍作修改,使cmd2使用新的连接conn2
在 sda.Fill(ds)时会抛出异常:“ 超时时间已到。在操作完成之前超时时间已过或服务器未响应”
原因:
在SQL Server 2005中运行下面的脚本,可以获得锁的信息:
会发现有两个会话(SQl Server中的两个进程)发生了冲突,会话1请求了一个排它锁,而会话2一直在等待同一资源的共享锁,因此会话2被阻塞,形成了死锁的情况,过了程序设置的超时时间,SQLServer2005会自动终止操作,在C#中就会捕捉到超时的异常。(虽然会话1中事务的隔离级别已手动设置为未提交读ReadUncommitted,但是回话2中默认隔离级别为已提交读ReadCommited,所以会话2依然会等待会话释放排它锁)
总结:以上例子仅供说明之用,在使用事务时,要根据业务设计好数据库操作顺序,并且恰当的设置隔离级别,避免此种情况的出现。
代码段1:
using (SqlConnection conn1 = new SqlConnection(connString)) { if (conn1.State == ConnectionState.Closed) conn1.Open(); SqlTransaction trans = conn1.BeginTransaction(IsolationLevel.ReadUncommitted); try { SqlCommand cmd1 = new SqlCommand(); cmd1.Connection = conn1; cmd1.CommandText = insertString1;//插入语句1 cmd1.Transaction = trans; cmd1.ExecuteNonQuery(); cmd1.CommandText = insertString2;//插入语句2 cmd1.Transaction = trans; cmd1.ExecuteNonQuery(); SqlCommand cmd2 = new SqlCommand(selectString, conn1);//查询语句 SqlDataAdapter sda = new SqlDataAdapter(cmd2); DataSet ds =new DataSet(); sda.Fill(ds); trans.Commit(); } catch(SqlException ex){ trans.Rollback(); Console.WriteLine(ex.Message); } }
说明:insertString1、insertString2和selectString操作的是同一张表
1.运行这段代码,到第21行(sda.Fill(ds))会出现异常:“如果分配给命令的连接位于本地挂起事务中,ExecuteReader 要求命令拥有事务。命令的 Transaction 属性尚未初始化。”
原因:SqlDataAdapter在填充数据时使用SqlDataReader读取数据,事务是基于数据库连接的,cmd1和cmd2使用的是同一连接,执行完cmd1后,事务处于挂起状态,还未提交。为了保持事务的完整性(其实这个地方是原子性的体现),要求新的cmd2必须也使用同一事务。
代码段2:将代码段1稍作修改,使cmd2使用新的连接conn2
using (SqlConnection conn1 = new SqlConnection(connString)) { if (conn1.State == ConnectionState.Closed) conn1.Open(); SqlTransaction trans = conn1.BeginTransaction(IsolationLevel.ReadUncommitted); try { SqlCommand cmd1 = new SqlCommand(); cmd1.Connection = conn1; cmd1.CommandText = insertString1;//插入语句1 cmd1.Transaction = trans; cmd1.ExecuteNonQuery(); cmd1.CommandText = insertString2;//插入语句2 cmd1.Transaction = trans; cmd1.ExecuteNonQuery(); //下面使cmd2使用新的连接conn2 using (SqlConnection conn2 = new SqlConnection(connString)) { SqlCommand cmd2 = new SqlCommand(selectString, conn2); SqlDataAdapter sda = new SqlDataAdapter(cmd2); DataSet ds = new DataSet(); sda.Fill(ds); } trans.Commit(); } catch(SqlException ex){ trans.Rollback(); Console.WriteLine(ex.Message); } }
在 sda.Fill(ds)时会抛出异常:“ 超时时间已到。在操作完成之前超时时间已过或服务器未响应”
原因:
在SQL Server 2005中运行下面的脚本,可以获得锁的信息:
select request_session_id, resource_type, resource_database_id, resource_description , resource_associated_entity_id, request_mode, request_status from sys.dm_tran_locks
会发现有两个会话(SQl Server中的两个进程)发生了冲突,会话1请求了一个排它锁,而会话2一直在等待同一资源的共享锁,因此会话2被阻塞,形成了死锁的情况,过了程序设置的超时时间,SQLServer2005会自动终止操作,在C#中就会捕捉到超时的异常。(虽然会话1中事务的隔离级别已手动设置为未提交读ReadUncommitted,但是回话2中默认隔离级别为已提交读ReadCommited,所以会话2依然会等待会话释放排它锁)
总结:以上例子仅供说明之用,在使用事务时,要根据业务设计好数据库操作顺序,并且恰当的设置隔离级别,避免此种情况的出现。
1 楼
风过有声
2011-04-21
好