网站建设工作总结报告,Wordpress公网,上海网站 工作室室,嘉定注册公司一. 相关概念 前面系列中的章节的#xff1a; 第二十二节: 以SQLServer为例介绍数据库自有的锁机制(共享锁、更新锁、排它锁等#xff09;和事务隔离级别 介绍了各种锁以及事务的隔离级别#xff0c;是从数据库的角度进行介绍的#xff0c;本章节是通过EF Core为载体 第二十二节: 以SQLServer为例介绍数据库自有的锁机制(共享锁、更新锁、排它锁等和事务隔离级别 介绍了各种锁以及事务的隔离级别是从数据库的角度进行介绍的本章节是通过EF Core为载体介绍事务隔离级别和相关问题与上述章节有些许重复的内容。 1. 什么是事务 事务Transaction是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元。 2. 事务的特征 事务具有 4 个基本特征分别是原子性Atomicity、一致性Consistency、隔离性Isolation、持久性Duration简称ACID。 (1).原子性指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中只允许出现两种状态之一。 • 全部执行成功 • 全部执行失败任何一项操作都会导致整个事务的失败同时其它已经被执行的操作都将被撤销并回滚只有所有的操作全部成功整个事务才算是成功完成. (2).一致性事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性一个事务在执行之前和执行之后数据库都必须处以一致性状态。比如如果从 A 账户转账到 B 账户不可能因为 A 账户扣了钱而 B 账户没有加钱无论 A 和 B 怎么转账系统中总额是固定的不可能因为 A 和 B 转账导致系统总额缺斤少两。 (3).隔离性指在并发环境中并发的事务是互相隔离的一个事务的执行不能被其它事务干扰。也就是说不同的事务并发操作相同的数据时每个事务都有各自完整的数据空间。一个事务内部的操作及使用的数据对其它并发事务是隔离的并发执行的各个事务是不能互相干扰的。(详见下面隔离级别) (4).持久性事务的持久性是指事务一旦提交后数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动那么一定能够将其恢复到事务成功结束后的状态。 二. 事务隔离级别以及引发的问题 1. 隔离级别 (1).读未提交READ_UNCOMMITTED读未提交该隔离级别允许脏读取其隔离级别是最低的。换句话说如果一个事务正在处理某一数据并对其进行了更新但同时尚未完成事务 因此还没有提交事务而以此同时允许另一个事务也能够访问该数据。 【引发的问题脏读】 (2).读已提交READ_COMMITTED 事务执行的时候只能获取到其它事务已经提交的数据获取不到未提交的数据。 【解决了“脏读”但是解决不了“不可重复读”】 (3).可重复读REPEATABLE_READ保证在事务处理过程中多次读取同一个数据时该数据的值和事务开始时刻是一致的。 【解决了“脏读”和“不可重复度”但是解决不了“幻读”】 (4).顺序读SERIALIZABLE最严格的事务隔离级别。它要求所有的事务排队顺序执行即事务只能一个接一个地处理不能并发。 【解决上述所有情况】 注4 种事务隔离级别从上往下级别越高并发性越差安全性就越来越高。一般数据默认级别是读已提交或可重复读。 PS常见数据库的默认级别 ①MySQL 数据库的默认隔离级别是 Repeatable read 级别。 ②Oracle数据库中只支持 Seralizable 和 Read committed级别默认的是 Read committed 级别。 ③SQL Server 数据库中默认的是 Read committed(读已提交) 级别。 2.引发的问题 (1).脏读Dirty Read第一个事务读取第二个事务正在更新的数据如果第二个事务还没有更新完成那么第一个事务读取的数据将是一半为更新过的一半还没更新过的数据。 (2).不可重复读Unrepeatable Read如果一个用户在一个事务中多次读取一条数据而另外一个用户则同时更新啦这条数据造成第一个用户多次读取数据不一致。 (3).幻读Phantom Read指同样的事务操作在前后两个时间段内执行对同一个数据项的读取可能出现不一致的结果集。 3. 案例测试 前提初始值userAge均为1000的且id为01 和 02 两条数据 (1).脏读测试事务1两条数据分别-500正常事务提交后这两条数据的userAge的值应该均为500将事务2设置成读未提交(IsolationLevel.ReadUncommitted 即允许脏读) 查出来的结果是500,1000即脏读数据。 代码分享 1 {2 //1.事先准备删除所有数据,插入两条指定数据3 using (EFDB01Context db new EFDB01Context())4 {5 db.Database.ExecuteSqlCommand(truncate table T_UserInfor);6 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(01,ypf1,男,1000,2019-08-08));7 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(02,ypf2,男,1000,2019-08-08));8 }9 //事务1
10 Task.Run(()
11 {
12 using (var db new EFDB01Context())
13 {
14 using (var transaction db.Database.BeginTransaction())
15 {
16 try
17 {
18 var data1 db.T_UserInfor.Find(01);
19 data1.userAge - 500;
20 db.SaveChanges();
21
22 Task.Delay(TimeSpan.FromSeconds(10)).Wait();
23
24 var data2 db.T_UserInfor.Find(02);
25 data2.userAge - 500;
26 db.SaveChanges();
27
28 transaction.Commit();
29
30 }
31 catch (Exception ex)
32 {
33
34 Console.WriteLine(ex.Message);
35 }
36 }
37 }
38 });
39 //事务2
40 Task.Run(()
41 {
42 using (var db new EFDB01Context())
43 {
44 //设置成“读未提交”
45 using (var transaction db.Database.BeginTransaction(IsolationLevel.ReadUncommitted))
46 {
47 try
48 {
49 Task.Delay(TimeSpan.FromSeconds(5)).Wait();
50 var data1 db.T_UserInfor.Find(01);
51 var data2 db.T_UserInfor.Find(02);
52
53 Console.WriteLine($01 userAge is {data1.userAge});
54 Console.WriteLine($02 userAge is {data2.userAge});
55
56 }
57 catch (Exception ex)
58 {
59
60 Console.WriteLine(ex.Message);
61 }
62 }
63 }
64 });
65 } View Code 避免脏读将事务2设置成读已提交(IsolationLevel.ReadCommitted 或者不设置SQLServer默认就是读已提交)则事务2需要等待事务1执行完才能读取读出来的两条数据的均为500即避免了脏读。 代码分享 1 {2 //1.事先准备删除所有数据,插入两条指定数据3 using (EFDB01Context db new EFDB01Context())4 {5 db.Database.ExecuteSqlCommand(truncate table T_UserInfor);6 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(01,ypf1,男,1000,2019-08-08));7 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(02,ypf2,男,1000,2019-08-08));8 }9 //事务1
10 Task.Run(()
11 {
12 using (var db new EFDB01Context())
13 {
14 using (var transaction db.Database.BeginTransaction())
15 {
16 try
17 {
18 var data1 db.T_UserInfor.Find(01);
19 data1.userAge - 500;
20 db.SaveChanges();
21
22 Task.Delay(TimeSpan.FromSeconds(10)).Wait();
23
24 var data2 db.T_UserInfor.Find(02);
25 data2.userAge - 500;
26 db.SaveChanges();
27
28 transaction.Commit();
29
30 }
31 catch (Exception ex)
32 {
33
34 Console.WriteLine(ex.Message);
35 }
36 }
37 }
38 });
39 //事务2
40 Task.Run(()
41 {
42 using (var db new EFDB01Context())
43 {
44 //设置成“读已提交”或者不设置SQLServer默认就是读已提交
45 using (var transaction db.Database.BeginTransaction(IsolationLevel.ReadCommitted))
46 {
47 try
48 {
49 Task.Delay(TimeSpan.FromSeconds(5)).Wait();
50 var data1 db.T_UserInfor.Find(01);
51 var data2 db.T_UserInfor.Find(02);
52
53 Console.WriteLine($01 userAge is {data1.userAge});
54 Console.WriteLine($02 userAge is {data2.userAge});
55
56 }
57 catch (Exception ex)
58 {
59
60 Console.WriteLine(ex.Message);
61 }
62 }
63 }
64 });
65 } View Code (2).不可重复读测试事务1中5s后将01数据的userAge的值由1000改为500事务2中在“读已提交”的情况下两次读取的01的数据分别是1000,500即为不可重复读。 代码分享 1 {2 {3 //1.事先准备删除所有数据,插入两条指定数据4 using (EFDB01Context db new EFDB01Context())5 {6 db.Database.ExecuteSqlCommand(truncate table T_UserInfor);7 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(01,ypf1,男,1000,2019-08-08));8 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(02,ypf2,男,1000,2019-08-08));9 }
10 //事务1
11 Task.Run(()
12 {
13 using (var db new EFDB01Context())
14 {
15 using (var transaction db.Database.BeginTransaction())
16 {
17 try
18 {
19 Task.Delay(TimeSpan.FromSeconds(5)).Wait();
20
21 var data1 db.T_UserInfor.Find(01);
22 data1.userAge - 500;
23 db.SaveChanges();
24
25 transaction.Commit();
26
27 }
28 catch (Exception ex)
29 {
30
31 Console.WriteLine(ex.Message);
32 }
33 }
34 }
35 });
36 //事务2
37 Task.Run(()
38 {
39 using (var db new EFDB01Context())
40 {
41 //设置成“读已提交”或者不设置SQLServer默认就是读已提交
42 using (var transaction db.Database.BeginTransaction(IsolationLevel.ReadCommitted))
43 {
44 try
45 {
46 //一定要加上这句否则下面的第二个Find不读取数据库
47 db.ChangeTracker.QueryTrackingBehavior QueryTrackingBehavior.NoTracking;
48
49 var data1 db.T_UserInfor.Find(01);
50 Console.WriteLine($01 userAge is {data1.userAge});
51
52 Task.Delay(TimeSpan.FromSeconds(6)).Wait();
53
54 var data2 db.T_UserInfor.Find(01);
55 Console.WriteLine($01 userAge is {data2.userAge});
56
57 }
58 catch (Exception ex)
59 {
60
61 Console.WriteLine(ex.Message);
62 }
63 }
64 }
65 });
66 }
67 } View Code 避免不可重复读将事务2设置成“可重复读”(IsolationLevel.RepeatableRead)事务2两次读取的数据均为1000避免了不可重复读。但第二次数据和数据库已经不一样了数据库中是500 代码分享 1 {2 {3 //1.事先准备删除所有数据,插入两条指定数据4 using (EFDB01Context db new EFDB01Context())5 {6 db.Database.ExecuteSqlCommand(truncate table T_UserInfor);7 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(01,ypf1,男,1000,2019-08-08));8 db.Database.ExecuteSqlCommand(insert into T_UserInfor values(02,ypf2,男,1000,2019-08-08));9 }
10 //事务1
11 Task.Run(()
12 {
13 using (var db new EFDB01Context())
14 {
15 using (var transaction db.Database.BeginTransaction())
16 {
17 try
18 {
19 Task.Delay(TimeSpan.FromSeconds(5)).Wait();
20
21 var data1 db.T_UserInfor.Find(01);
22 data1.userAge - 500;
23 db.SaveChanges();
24
25 transaction.Commit();
26
27 }
28 catch (Exception ex)
29 {
30
31 Console.WriteLine(ex.Message);
32 }
33 }
34 }
35 });
36 //事务2
37 Task.Run(()
38 {
39 using (var db new EFDB01Context())
40 {
41 //设置成“可重复读”
42 using (var transaction db.Database.BeginTransaction(IsolationLevel.RepeatableRead))
43 {
44 try
45 {
46 //一定要加上这句否则下面的第二个Find不读取数据库
47 db.ChangeTracker.QueryTrackingBehavior QueryTrackingBehavior.NoTracking;
48
49 var data1 db.T_UserInfor.Find(01);
50 Console.WriteLine($01 userAge is {data1.userAge});
51
52 Task.Delay(TimeSpan.FromSeconds(6)).Wait();
53
54 var data2 db.T_UserInfor.Find(01);
55 Console.WriteLine($01 userAge is {data2.userAge});
56
57 }
58 catch (Exception ex)
59 {
60
61 Console.WriteLine(ex.Message);
62 }
63 }
64 }
65 });
66 }
67 } View Code (3).幻读测试 有点问题需要在什么场景下测试 三. 死锁 详见开头之前的章节此处不再重复介绍了 ! 作 者 : Yaopengfei(姚鹏飞)博客地址 : http://www.cnblogs.com/yaopengfei/声 明1 : 本人才疏学浅用郭德纲的话说“我是一个小学生”如有错误欢迎讨论请勿谩骂^_^。声 明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址否则保留追究法律责任的权利。 转载于:https://www.cnblogs.com/yaopengfei/p/11394728.html