仅此一文让你明白事务隔离级别、脏读、不可重复读、幻读
网络上关于这方面的博文有些偏理论,有些通篇代码,都不能深入浅出。本文用图文并茂的方式,配上行云流水般的代码,非要摆清楚这个问题。相关代码已提交至码云(点击这里下载)。
事务是现代关系型数据库的核心之一。在多个事务并发操作数据库(多线程、网络并发等)的时候,如果没有有效的避免机制,就会出现以下几种问题:
第一类丢失更新(Lost Update)
在完全未隔离事务的情况下,两个事务更新同一条数据资源,某一事务完成,另一事务异常终止,回滚造成第一个完成的更新也同时丢失 。这个问题现代关系型数据库已经不会发生,就不在这里占用篇幅,有兴趣的可以自行百度。
脏读(Dirty Read)
A事务执行过程中,B事务读取了A事务的修改。但是由于某些原因,A事务可能没有完成提交,发生RollBack了操作,则B事务所读取的数据就会是不正确的。这个未提交数据就是脏读(Dirty Read)。脏读产生的流程如下:
可以用EF Core模拟此过程:
class TestReadUncommitted :TestBase { private AutoResetEvent _autoResetEvent; [Test] public void ReadUncommitted() { using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { var user = context.Users.SingleOrDefault(u => u.Account == "admin"); Console.WriteLine($"初始用户状态:【{user.Status}】"); } _autoResetEvent = new AutoResetEvent(false); ThreadPool.QueueUserWorkItem(data =>{ Write(); //启动线程写 }); ThreadPool.QueueUserWorkItem(data =>{ Read(); //启动线程读 }); Thread.Sleep(5000); using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { var user = context.Users.SingleOrDefault(u => u.Account == "admin"); Console.WriteLine($"最终用户状态:【{user.Status}】"); } } private void Read() { _autoResetEvent.WaitOne(); var options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadUncommitted }; using (var scope = new TransactionScope(TransactionScopeOption.Required, options)) { using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { var user = context.Users.SingleOrDefault(u => u.Account == "admin"); Console.WriteLine($"事务B:脏读到的用户状态:【{user.Status}】--{DateTime.Now.ToString("HH:mm:ss fff")}"); //如果这时执行下面的判断 if (user.Status == 1) { Console.WriteLine("事务B:非正常数据,会产生意想不到的BUG"); } } } } private void Write() { using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions {IsolationLevel = IsolationLevel.ReadCommitted})) { Console.WriteLine($"事务A:修改--{DateTime.Now.ToString("HH:mm:ss fff")}"); using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { var user = context.Users.SingleOrDefault(u => u.Account == "admin"); user.Status = 1-user.Status; //模拟修改 context.SaveChanges(); } _autoResetEvent.Set(); //模拟多线程切换,这时切换到Read线程,复现脏读 Thread.Sleep(2000); //模拟长事务 Console.WriteLine($"事务A:改完,但没提交--{DateTime.Now.ToString("HH:mm:ss fff")}"); } } }
对应的执行结果:
不可重复读(Nonrepeatable Read)
B事务读取了两次数据,在这两次的读取过程中A事务修改了数据,B事务的这两次读取出来的数据不一样。B事务这种读取的结果,即为不可重复读(Nonrepeatable Read)。不可重复读的产生的流程如下:
模拟代码如下:
public class TestReadCommitted : TestBase { private AutoResetEvent _toWriteEvent = new AutoResetEvent(false); private AutoResetEvent _toReadEvent = new AutoResetEvent(false); [Test] public void ReadCommitted() { ThreadPool.QueueUserWorkItem(data => { Read(); //启动线程读 }); ThreadPool.QueueUserWorkItem(data => { Write(); //启动线程写 }); Thread.Sleep(5000); using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { var user = context.Users.SingleOrDefault(u => u.Account == "admin"); Console.WriteLine($"最终用户状态:【{user.Status}】--{DateTime.Now.ToString("HH:mm:ss fff")}"); } } private void Read() { using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })) { using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { var user = context.Users.SingleOrDefault(u => u.Account == "admin"); Console.WriteLine($"事务B:第一次读取:【{user.Status}】--{DateTime.Now.ToString("HH:mm:ss fff")}"); } _toWriteEvent.Set(); //模拟多线程切换,这时切换到写线程,复现不可重复读 _toReadEvent.WaitOne(); using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { var user = context.Users.SingleOrDefault(u => u.Account == "admin"); Console.WriteLine($"事务B:第二次读取:【{user.Status}】--{DateTime.Now.ToString("HH:mm:ss fff")}"); } } } private void Write() { _toWriteEvent.WaitOne(); using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })) { User user = null; using (var context = _autofacServiceProvider.GetService<OpenAuthDBContext>()) { user = context.Users.SingleOrDefault(u => u.Account == "admin"); Console.WriteLine($"事务A:读取为【{user?.Status}】--{DateTime.Now.ToString("HH:mm:ss fff")}"); user.Status = 1 - user.Status; context.SaveChanges(); } scope.Complete(); Console.WriteLine($"事务A:已被更改为【{user.Status}】--{DateTime.Now.ToString("HH:mm:ss fff")}"); _toReadEvent.Set(); } } }