using (var db = new BaseContext())
{
db.Configuration.ValidateOnSaveEnabled = false;
using (var ts = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.Serializable }))
{
OrdersModel model = db.OrdersModel.SingleOrDefault(m => m.OrderNo == OrderNo);//订单
Message = "";
if (model != null)//如果订单未查询出信息,做出提示
{
#region 修改订单信息
model.State = 1;
model.UpdateTime = DateTime.Now;
#endregion
var res = db.SaveChanges();//保存
ts.Complete();//提交事务(不提交是无法正常Update的)
db.Configuration.ValidateOnSaveEnabled = true;
if (res > 0)
{
result = true;
}
}
}
else
{
Message = "对不起,订单信息获取出错,请联系管理员。";
}
}
}
程序集 System.Transactions
只需要调整IsolationLevel = IsolationLevel.Serializable
namespace System.Transactions
{
//
// 摘要:
// 指定事务的隔离级别
public enum IsolationLevel
{
//
// 摘要:序列化
// 在事务期间,可以读取但不能修改和添加
Serializable = 0,
//
// 摘要:可重复读
// 在事务期间可以读取但不能修改易失性数据。可以在事务期间添加新数据。
RepeatableRead = 1,
//
// 摘要:提交读
// 事务期间无法读取易失性数据,但可以对其进行修改。(EF 默认)
ReadCommitted = 2,
//
// 摘要:未提交读-会产生脏读
// 在事务期间可以读取和修改易失性数据。
ReadUncommitted = 3,
//
// 摘要:快照隔离
// 可以读取易失性数据。在事务修改数据之前,它会验证另一个事务在最初读取数据后更改了数据。如果数据已更新,出现错误。这使得事务可以到达以前提交的数据值。
Snapshot = 4,
//
// 摘要:
// 无法覆盖来自高度隔离事务的挂起更改。
Chaos = 5,
//
// 摘要:
// 级别无法确定。如果设置了此值,则引发异常。
Unspecified = 6
}
}
SQL语句是SqlServer,都是以前做测试留下的记录。
未提交读:会产生脏读和幻读:
脏读
一个事务正在访问并修改数据库中的数据但是没有提交,但是另外一个事务可能读取到这些已作出修改但未提交的数据。这样可能导致的结果就是所有的操作都有可能回滚,比如第一个事务对数据做出的修改可能违背了数据表的某些约束,破坏了完整性,但是恰巧第二个事务却读取到了这些不正确的数据造成它自身操作也发生失败回滚。
Update.sql
第一步:执行以下代码
begin tran
update A Set HouseNum='123123' where HouseNum='HFB000006'
第六步:回滚数据
rollback
Select2.sql
第二步:执行以下查询
Select * from A --阻塞
Select * from A where HouseNum='HFB000006'--阻塞
--结果:一直处于阻塞,等待事务执行完
第三步:取消执行
第四步:执行以下查询
Select * from A (nolock)
Select * from A (nolock) where HouseNum='123123'
--结果:脏读,事务未提交完的数据已经读到
第五步:查询结果,把第一步执行的添加数据读到(脏读)
第七步:再查询
Select * from A (nolock)
Select * from A (nolock) where HouseNum='123123'
第二个查询已经没有结果
幻读
与不可重复读有点类似,都是两次读取,不同的是 A 事务第一次操作的比如说是全表的数据,此时 B 事务并不是只修改某一具体数据而是插入了一条新数据,而后 A 事务第二次读取这全表的时候就发现比上一次多了一条数据,发生幻觉了。
查询窗口1:Insert.sql
第一步:执行以下代码
begin tran
insert into A values (NEWID(),'123123')
第六步:回滚数据
rollback
查询窗口2:Select.sql
第二步:执行以下查询
Select * from A --阻塞
Select * from A where HouseNum='HFB000006'--阻塞
--结果:一直处于阻塞,等待事务执行完
第三步:取消执行
第四步:执行以下查询
Select * from A (nolock)
Select * from A (nolock) where HouseNum='123123'
--结果:幻读,事务未提交完的数据已经读到
第七步:再查询
Select * from A (nolock)
Select * from A (nolock) where HouseNum='123123'
第二个查询已经没有结果
提交读:可能产生不可重复读
A 事务两次读取同一数据,B事务也读取这同一数据,但是 A 事务在第二次读取前B事务已经更新了这一数据。所以对于A事务来说,它第一次和第二次读取到的这一数据可能就不一致了。
已提交读隔离级别能够避免脏读,但是仍然会遇到不可重复读取的问题。
可重复读
不能读取已由其它事务修改了但是未提交的行,其它任何事务也不能修改在当前事务完成之前由当前事务读取的数据。
但是对于其它事务插入的新行数据,当前事务第二次访问表行时会检索这一新行。因此,这一个隔离级别的设置解决了不可重复读取的问题,但是避免不了幻读。
也就是能解决update,不能解决insert。
SNAPSHOT (快照隔离)
可以解决幻读 Phantom Reads 的问题,当前事务中读取的数据在整个事务开始到事务提交结束之间,这个数据版本是一致的。其它的事务可能对这些数据做出修改,但是对于当前事务来说它是看不到这些变化。有点类似于当前事务拿到这个数据的时候是拿到这个数据的快照,因此在这个快照上做出的操作同一事务中前后几次操作都是基于同一数据版本。因此,这一个隔离级别的设置可以解决 Phantom Reads 幻读问题。但是要注意的是,其它事务是可以在当前事务完成之前修改由当前事务读取的数据
在使用 SNAPSHOT 之前要注意,默认情况下数据库不允许设置 SNAPSHOT 隔离级别,直接设置会出现类似于这样的错误:
DBCC execution completed. If DBCC printed error messages, contact your system administrator.
Msg 3952, Level 16, State 1, Line 8
Snapshot isolation transaction failed accessing database 'XXXXXX' because snapshot isolation is not allowed in this database. Use ALTER DATABASE to allow snapshot isolation.
ALTER DATABASE 数据库名
SET ALLOW_SNAPSHOT_ISOLATION ON
设置之后会让读取数据没有共享锁(S),就算有事务在Update,也一直读取上一个版本,不会触发共享锁。对强业务系统不要去设置。
这个会更改数据库设置,慎用。sys.databases上面可查到is_read_committed_snapshot_on
1 = READ_COMMITTED_SNAPSHOT 选项为 ON。 read-committed 隔离级别下的读操作基于快照扫描,没有获取锁。
0 = READ_COMMITTED_SNAPSHOT 选项为 OFF(默认)。 read-committed 隔离级别下的读操作使用共享锁。
或者直接修改,连接会全部断开。
SERIALIZABLE(序列化)
性能最低,隔离级别最高最严格,可以几乎上面提到的所有问题。比如不能读取其它已由其它事务修改但是没有提交的数据,不允许其它事务在当前事务完成修改之前修改由当前事务读取的数据,不允许其它事务在当前事务完成修改之前插入新的行。它的作用与在事务内所有 SELECT 语句中的所有表上设置 HOLDLOCK 相同,并发级别比较低但又对安全性要求比较高的时候可以考虑使用。如果并发级别很高,使用这个隔离级别,性能瓶颈将非常严重。
OrdersService ordersService = new OrdersService();//执行方法就是最上面那个事务
string message;
var task1 = Task.Run(() =>
{
ordersService.test("20210628111831088", "123123123", out message);//SERIALIZABLE
Console.WriteLine("Run1:" + message);
});
var task2 = Task.Run(() =>
{
ordersService.test("20210628111831088", "456456456", out message);//SERIALIZABLE
Console.WriteLine("Run2:" + message);
});
task1.Wait();
task2.Wait();
比如这种,第二次的执行,会直接被锁死了抛异常了。