一、关于TransactionScope
1.在使用事务操作是,当程序中存在多个EF上下文,很多时候都是使用TransactionScope
2.TransactionScope使用简单,同一个事务中多个数据库上下文不会出现程序死锁
实验证明:
1.多线程中不同的事务锁定了相同的表会抛出异常
2.抛出异常的线程将自动结束
3.如果出现数据库表死锁,本程序的其他线程中如果也有关于锁定表的操作同样的会暂停等待一段时间
4.使用TransactionScope,锁定表的表同样会影响其他进程或者线程的查询操作,其他进程的查询样会挂起等待
二、关于使用TransactionScope多线程问题解决方案
解决方案1(推荐)
1.所有的事务使用同一个线程锁,也就是说多个线程中事务操作开启同一个时间只有一个
2.这种处理方式简单,不会抛出异常,但会稍微有点影响性能,因为一个事务开启的话,其他的事务操作会挂起等待
3.特别说明,如果已知某些事务操作会执行比较长的时间,这种方式就不太合适了
代码实例:
使用封装过的TransactionScope,此处使用公共的排它锁,控制事务实例的创建和释放之间的代码,同一时间只有一个在运行。
也就是说只有一个事务释放之后,才会创建另一个事务。
/// <summary>
/// 自定义事务处理,
/// 此版本,数据库上下文会出现多个,所以事务使用 TransactionScope
/// 使用排它锁,确保事务的单线程执行
/// </summary>
public class EFTransaction : IDisposable
{
private readonly static object _MyLock = new object();
/// <summary>
/// 当前事务对象
/// </summary>
private TransactionScope tran = null;
public EFTransaction()
{
Monitor.Enter(_MyLock);//获取排它锁
this.tran = new TransactionScope();
}
/// <summary>
/// 提交
/// </summary>
public void Commit()
{
tran.Complete();
}
/// <summary>
/// 混滚操作,在Dispose(),中自动调用回滚
/// </summary>
public void Rollback()
{
//提前执行释放,回滚
if (tran != null)
tran.Dispose();
}
public void Dispose()
{
if (tran != null)
tran.Dispose();
Monitor.Exit(_MyLock);//释放排它锁
}
}
解决方案2
1.写代码的时候特别注意,不同事务之间不能操作相同的表,也就是多个事务中锁定的表不能有重合,否则就会抛出死锁异常
2.这种方式对编码有要求,并且控制上相对比较难
3.这种方式性能比较高,因为在逻辑上排除了数据库表死锁
解决方案3(推荐)
1.在所有的事务中都退死锁异常进行处理,也就是使用try/catch 处理回滚,本次操作失败,下次在重新执行此业务逻辑
2.这种方式比较折中,而且还可以在此一并处理其他的异常
3.如果使用这种方式的话,开启事务参数,最好指定等待时间
代码实例:
此方案,一个事务操作一个锁,如果抛出死锁异常,本次操作失败,依赖下一次操作。
//对于锁推荐使用静态私有静态变量
private readonly static object _MyLock = new object();
/// <summary>
/// 事务, 多表修改
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
public bool UpdateName(string name)
{
lock (_MyLock)
{
using (var tran = new TransactionScope())
{
try
{
ModuleOperate _module = new ModuleOperate();
//1.修改模块名称
_module.UpdateFirstName("模块:" + name);
System.Threading.Thread.Sleep(100);
//2.修改菜单
this.UpdateFirstName("菜单:" + name);
//提交事务
tran.Complete();
}
catch (Exception ex)
{
//使用TransactionScope,如果没有提交自动处理
//执行回滚操作处理
Console.WriteLine("当前操作Menu");
Console.WriteLine(ex.InnerException == null ? ex.Message : ex.InnerException.Message);
}
}
}
return true;
}
更多参考: