欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ADO.NET事务拾遗

程序员文章站 2024-01-12 16:34:10
...
[b]代码段1:[/b]
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);
}
}

[b]说明:insertString1、insertString2和selectString操作的是同一张表[/b]
1.运行这段代码,到第21行(sda.Fill(ds))会出现异常:“如果分配给命令的连接位于本地挂起事务中,ExecuteReader 要求命令拥有事务。命令的 Transaction 属性尚未初始化。”
原因:SqlDataAdapter在填充数据时使用SqlDataReader读取数据,事务是基于数据库连接的,cmd1和cmd2使用的是同一连接,执行完cmd1后,事务处于挂起状态,还未提交。为了保持事务的完整性(其实这个地方是原子性的体现),要求新的cmd2必须也使用同一事务。
[b]代码段2:将代码段1稍作修改,使cmd2使用新的连接conn2[/b]
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依然会等待会话释放排它锁)

总结:以上例子仅供说明之用,在使用事务时,要根据业务设计好数据库操作顺序,并且恰当的设置隔离级别,避免此种情况的出现。