SQL Server 分布式事务与本地事务

背景:之前有项目中出现大量死锁,进行排查后最终发现很多死锁都是由于序列化隔离级别导致,开发针对业务和SQL进行优化后,死锁减少,但是没进行后续研究。最近又有很多项目出现死锁及超时,特别是工作流和待办这块,同样发现都是存在序列化,于是针对这一点进行相关资料查阅及解答。

一. 为什么会出现serializable(序列化)

如果我们程序中定义事务类调用了分布式事务,那么事务的隔离级别默认就是serializable,数据库中即会出现序列化级别的会话。

而如果调用了本地事务,那么隔离级别默认为Read committed

二. 分布式事务与本地事务的区别

本地事务System.Data.Common.DbTransaction,只能进行本地事务操作,无法建立多个数据库连接跨实例操作。

分布式事务System.Transactions.TransactionScope,同一个事务中,可以建立多个数据库连接,进行跨库或跨实例操作,需要开启msdtc服务。

分布式事务的常见使用场景有如下几种:

  1. TransactionScope中有多个SqlConnection(多个数据库连接)连接同一个SqlServer实例的不同数据库
  2. TransactionScope中的多个SqlConnection(多个数据库连接)连接不同的SqlServer实例
  3. TransactionScope中有SqlConnection(数据库连接)执行的Sql语句使用到了LinkedServer(链接服务器)

参看上面分布式事务的使用场景,我们应用中除了个性化会使用到这些场景,其他大部分事务其实都不需要调用分布式事务,所以一般情况下我们的数据库中不会出现序列化隔离级别。

三. 2种事务隔离级别区别

Read committedSerializable 分别是本地事务与分布式事务的默认隔离级别,2种隔离级别的区别在于,SQL执行过程中锁的范围不同。

通俗来说,Serializable级别下,锁的范围更大,所以会导致阻塞变多,进而死锁发生概率变高。但是Serializable级别下数据一致性最高,也就是说可以避免不可重复读、幻读等。

对于我们应用来说,不可重复读或者幻读的影响几乎没有,所以不用刻意去规避这些问题,使用默认的Read committed 来防止脏读就可以,所以除非场景符合分布式事务,一般使用本地事务即可。

四. 总结建议

对于使用分布式事务,可能之前开发并没有注意到它的使用场景及事务隔离级别,许多事务并不需要分布式事务,仅仅本地事务就可以满足,而那些需要分布式的场景,在使用的时候也可以修改其默认的隔离级别因为序列化隔离级别的优势我们并不需要,相反其并发性差的弱势却是我们需要避免的

修改分布式事务隔离级别代码:

TransactionOptions transactionOption = new TransactionOptions(); 
transactionOption.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted; //设置隔离级别
using (TransactionScope scope = new TransactionScope(transactionOption)) {
}  //然后这儿加上transactionOption参数

查看数据库中是否存在序列化会话

select * from sys.dm_exec_sessions where transaction_isolation_level = 4