SQL Server 分布式事务与本地事务
背景:之前有项目中出现大量死锁,进行排查后最终发现很多死锁都是由于序列化隔离级别
导致,开发针对业务和SQL进行优化后,死锁减少,但是没进行后续研究。最近又有很多项目出现死锁及超时,特别是工作流和待办这块,同样发现都是存在序列化,于是针对这一点进行相关资料查阅及解答。
一. 为什么会出现serializable(序列化)
如果我们程序中定义事务类调用了分布式事务
,那么事务的隔离级别默认就是serializable
,数据库中即会出现序列化级别的会话。
而如果调用了本地事务
,那么隔离级别默认为Read committed
。
二. 分布式事务与本地事务的区别
本地事务
:System.Data.Common.DbTransaction
,只能进行本地事务操作,无法建立多个数据库连接跨实例操作。
分布式事务
:System.Transactions.TransactionScope
,同一个事务中,可以建立多个数据库连接,进行跨库或跨实例操作,需要开启msdtc服务。
分布式事务的常见使用场景
有如下几种:
- TransactionScope中有多个SqlConnection(多个数据库连接)连接同一个SqlServer实例的
不同数据库
- TransactionScope中的多个SqlConnection(多个数据库连接)连接
不同的SqlServer实例
- TransactionScope中有SqlConnection(数据库连接)执行的Sql语句使用到了
LinkedServer(链接服务器)
参看上面分布式事务的使用场景,我们应用中除了个性化会使用到这些场景,其他大部分事务其实都不需要调用分布式事务,所以一般情况下我们的数据库中不会出现序列化隔离级别。
三. 2种事务隔离级别区别
Read committed
与 Serializable
分别是本地事务与分布式事务的默认隔离级别,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