一、基础知识
1) 使用事务级别ReadUnCommited 会产生脏读现像,意味着读取到的为UnCommited(未提交)的数据。怎么理解呢?在使用该隔离级别的事务开始后。更新了数据库某一行的数据,但是事务的工作量比较大,后续还有一大堆代码还没执行完呢。不巧的是有个哥们过来读数据了,这个时候读到的就是未提交的值,如果后继工作一切正常,也没什么影响。一旦后面的代码执行中出错,就会产生不一致的错误,适用于对事务极度自信的情况下,特点为可读不可改。关于不可改需解释一下,MS SQL中一条语句为最基本的执行单元了,如果一个事务中,对同条数据的更新语句未Commited的情况下,其它事务是需要等待的。
2) 使用事务级别ReadCommited 会消除脏读现像,意味着读取到的为Commited(已提交)的数据。在使用该隔离级别的事务开始后,除了该事务其它的查询均会一直等待直到该事务提交。特点为不可读不可改,
3) 使用事务级别Repeatable Read,可重复读。它不会像ReadCommited一样阻止Select查询,但它会阻止Update语句。使用该隔离级别的事务开始后,会阻止其它事务更改查询。但依然可以新增数据。
4) 使用事务级别Serializable 最严的一种了,与Repeatable Read相比就是在这种级别下不能新增数据。
5) 下表为简单说明
其中增删除改查为其它事务中对已在事务中的数据行进行增删改查的可能行性,其中删,改,查操作多个事务中的同一条数据。√指该隔离级别下操作可未被阻塞可立即响应。
隔离级别 |
增(其他事务) |
删(其他事务) |
改(其他事务) |
查(其他事务) |
脏读 |
读提交 |
可重复读 |
幻影读 |
数据过期 |
ReadUnCommited |
√ |
× |
× |
√ |
√ |
× |
× |
√ |
√ |
ReadCommited |
√ |
× |
× |
× |
× |
√ |
× |
√ |
× |
Repeatable Read |
√ |
× |
× |
√ |
× |
√ |
√ |
√ |
√ |
Serializable |
× |
× |
× |
√ |
× |
√ |
√ |
× |
√ |
二、C#中的事务
1) ADO.NET中的事务与EF中的事务,它们是一类事物。可以说是同一个东西,均为封装了 SQL语句中的 Begin Tran Commit等。
2) 分布式事务TranscationCope需引用System.data.Transcations.dll才能使用
3) 分布式事务与事务二者区别,主要区别在于前者用于一个数据连接中控制数据一致性,后者在多个数据库连接中控制数据一致性,如果不能理解,可以记忆为一个数据库使用事务用前者,多个数据库中使用事务用后者,如果在一个数据连接中使用TranscationCope,可以把它为理解为简化版的事务,因为它只有Complete方法。
4) 注意TranscationCope进行多事务协调时需安装与设置MSDTC组件。
5) 并行事务:指在同一个DBConnection中启用二个事务,这个不被ADO.NET与EF支持(目前),如果程序运行过程中出现了 Connection不支持并行事务的时候,检查一下是否在一个数据库连接中使用了二个事务。
三、事务的应用与验证。
1) 下面依次验证(末完成)
验证时注意事项:连接字符串中设置 Pooling = False 既关掉数据连接池,特别是多次测试之后会有点小问题干扰结论。;如果需要验证多个事务进行引发的异常还是阻塞进程,需要把超时时间设置为更多一些超过程序预计运行时间。EF建议大家使用EF6.0,在低版本的 EF 中不能指定SaveChanges时使用的事务。
验证时使用3个线程向同一表中插入10条数据,如果顺序插入,表明事务是顺序执行,下面上代码
class Program { static string connectionString = "server=127.0.0.1;database=xxxxxx;user=sa;password=xxxxxx;Pooling=False"; static System.Data.IsolationLevel tranLevel = System.Data.IsolationLevel.ReadUncommitted; static string commandText = "update [Locker] set [id] = 100" ; static void Main(string[] args) { ClearTestData(); tranLevel = System.Data.IsolationLevel.ReadUncommitted; commandText = "update [Locker] set [id] = 100"; Thread th1 = new Thread(new ThreadStart(() => { test(); })); Thread th2 = new Thread(new ThreadStart(() => { test(); })); Thread th3 = new Thread(new ThreadStart(() => { test(); })); th1.Name = "线程1"; th2.Name = "线程2"; th3.Name = "线程3"; th2.Start(); th3.Start(); th1.Start(); Console.ReadLine(); } static void test() { using (var db = new SupplierPortalEntities()) { var connection = db.Database.Connection; if (connection.State != ConnectionState.Open) { connection.Open(); } var tran = connection.BeginTransaction(tranLevel); try { db.Database.UseTransaction(tran); var command = commandText; db.Database.ExecuteSqlCommand(command); for (int i = 0; i < 20; i++) { Random r = new Random(); var t = new TranLocker(); t.Content = Thread.CurrentThread.Name; db.TranLocker.Add(t); Console.WriteLine("线程{0}加入数据 - lockid: {1}", Thread.CurrentThread.Name, t.LockID); Thread.Sleep(50); } db.SaveChanges(); //if (Thread.CurrentThread.Name.EndsWith("3")) //引发一个异常 . //{ // throw new Exception("3为故障码"); //} tran.Commit(); Console.WriteLine("线程{0}:事务已提交", Thread.CurrentThread.Name); } catch (Exception er) { tran.Rollback(); Console.WriteLine("线程{0}:发生错误,事务已回滚:{1}", Thread.CurrentThread.Name, er.Message); } } } static void ClearTestData() { //清除数据 var connClearInsertDatas = new SqlConnection(connectionString); connClearInsertDatas.Open(); var cmd = connClearInsertDatas.CreateCommand(); cmd.CommandText = "delete from TranLocker"; cmd.ExecuteNonQuery(); connClearInsertDatas.Close(); } }