.NET:“事务、并发、并发问题、事务隔离级别、锁”小议,重点介绍:“事务隔离级别"如何影响 “锁”?
备注
我们知道事务的重要性,我们同样知道系统会出现并发,而且,一直在准求高并发,但是多数新手(包括我自己)经常忽略并发问题(更新丢失、脏读、不可重复读、幻读),如何应对并发问题呢?和线程并发控制一样,我们采用锁(乐观锁和悲观锁),大多数场景我们不需要直接管理锁,而是使用有更高语义的事务隔离级别来控制并发问题。
关于事务、事务隔离级别如何应对并发问题的文章我之前有过介绍,可以参考如下文章:.NET:脏读、不可重复读和幻读测试。
本文重点说一下:事务隔离级别如何影响锁?
事务隔离级别如何影响锁?
这里大家只需知道两种锁:共享锁和排它锁,如果拿多线程相关的锁来类比的话,共享锁和排它锁共同构成了:ReadWriteLockSlim,共享锁是读取锁,排它锁是修改锁。一个资源只能拥有一个排他锁,但是可以拥有多个共享锁,而且共享锁和排它锁是互斥的,即:一个资源同时只能拥有一种锁。
事务隔离级别不会影响排它锁,修改资源的 SQL 会给资源加排它锁,直到事务提交排它锁才会释放,如果此时有其它事务尝试读取修改的资源,读取会被挂起,因为排它锁是互斥的(使用了读未提交隔离级别的事务除外)。事务隔离级别会影响共享锁,如:是否需要共享锁?拥有共享锁多长时间?锁定多大粒度的资源?下面我们看几个具体的例子。
更新丢失(最后一个覆盖前面的修改)
代码
private static void Test8()
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open();
var cmd = new SqlCommand("delete from Users", con);
cmd.ExecuteNonQuery(); cmd = new SqlCommand("insert into Users values (N'段光伟1')", con);
cmd.ExecuteNonQuery();
} for (var i = ; i < ; i++)
{
Task.Factory.StartNew((state) =>
{
using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open(); var index = (int)state + ;
var padding = new String(' ', (index - ) * ); Console.WriteLine(string.Format("{0}第{1}次:开始读取", padding, index));
var cmd = new SqlCommand("select top 1 * from Users", con);
cmd.ExecuteReader().Close(); Console.WriteLine(string.Format("{0}第{1}次:开始休眠", padding, index));
System.Threading.Thread.Sleep( * ((int)state + )); Console.WriteLine(string.Format("{0}第{1}次:开始修改", padding, index));
cmd = new SqlCommand("update Users set Name = N'段光宇" + state + "'", con);
cmd.ExecuteNonQuery();
Console.WriteLine(string.Format("{0}第{1}次:完成修改", padding, index));
} ts.Complete();
}
}, i).ContinueWith((t) =>
{
Console.WriteLine(t.Exception.InnerException.Message);
}, TaskContinuationOptions.OnlyOnFaulted);
} Console.ReadLine();
}
输出
说明
此时会出现这种问题是因为:事务中的读取虽然会使用共享锁,但是共享锁在读取完成之后立即释放,不会等到事务提交后才释放,我们可以使用 SQL(注意看下面的 select 语句的变化) 延长共享锁的持有时间,如下:
代码
private static void Test9()
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open();
var cmd = new SqlCommand("delete from Users", con);
cmd.ExecuteNonQuery(); cmd = new SqlCommand("insert into Users values (N'段光伟1')", con);
cmd.ExecuteNonQuery();
} for (var i = ; i < ; i++)
{
Task.Factory.StartNew((state) =>
{
using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open(); var index = (int)state + ;
var padding = new String(' ', (index - ) * ); Console.WriteLine(string.Format("{0}第{1}次:开始读取", padding, index));
var cmd = new SqlCommand("select top 1 * from Users(holdlock)", con);
cmd.ExecuteReader().Close(); Console.WriteLine(string.Format("{0}第{1}次:开始休眠", padding, index));
System.Threading.Thread.Sleep( * ((int)state + )); Console.WriteLine(string.Format("{0}第{1}次:开始修改", padding, index));
cmd = new SqlCommand("update Users set Name = N'段光宇" + state + "'", con);
cmd.ExecuteNonQuery();
Console.WriteLine(string.Format("{0}第{1}次:完成修改", padding, index));
} ts.Complete();
}
}, i).ContinueWith((t) =>
{
Console.WriteLine(t.Exception.InnerException.Message);
}, TaskContinuationOptions.OnlyOnFaulted);
} Console.ReadLine();
}
输出
说明
我们为 select 语句采用了 (holdlock) 后缀,这回导致共享锁直到事务提交才会释放,好无疑问,这回导致死锁,系统会选择一个胜利者,其它的都作为牺牲品。
使用可重复读隔离级别延长共享锁的持有时间
上面我们手工采用 SQL 来延长了共享锁的持有时间,这里演示另外一种方式。
代码
private static void Test10()
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open();
var cmd = new SqlCommand("delete from Users", con);
cmd.ExecuteNonQuery(); cmd = new SqlCommand("insert into Users values (N'段光伟1')", con);
cmd.ExecuteNonQuery();
} for (var i = ; i < ; i++)
{
Task.Factory.StartNew((state) =>
{
using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.RepeatableRead }))
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open(); var index = (int)state + ;
var padding = new String(' ', (index - ) * ); Console.WriteLine(string.Format("{0}第{1}次:开始读取", padding, index));
var cmd = new SqlCommand("select top 1 * from Users", con);
cmd.ExecuteReader().Close(); Console.WriteLine(string.Format("{0}第{1}次:开始休眠", padding, index));
System.Threading.Thread.Sleep( * ((int)state + )); Console.WriteLine(string.Format("{0}第{1}次:开始修改", padding, index));
cmd = new SqlCommand("update Users set Name = N'段光宇" + state + "'", con);
cmd.ExecuteNonQuery();
Console.WriteLine(string.Format("{0}第{1}次:完成修改", padding, index));
} ts.Complete();
}
}, i).ContinueWith((t) =>
{
Console.WriteLine(t.Exception.InnerException.Message);
}, TaskContinuationOptions.OnlyOnFaulted);
} Console.ReadLine();
}
输出
说明
使用事务隔离级别来控制共享锁的持有时间,会影响整个事务内的所有读取。
一种避免死锁的方式
上面大家看到了死锁的发生,我们可以采用乐观锁 + 重试来避免这种情况,当然也可以采用另外一种数据库锁:更新锁(注意 select 语句的变化)。
代码
private static void Test11()
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open();
var cmd = new SqlCommand("delete from Users", con);
cmd.ExecuteNonQuery(); cmd = new SqlCommand("insert into Users values (N'段光伟1')", con);
cmd.ExecuteNonQuery();
} for (var i = ; i < ; i++)
{
Task.Factory.StartNew((state) =>
{
using (var ts = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted }))
{
using (var con = new SqlConnection(CONNECTION_STRING))
{
con.Open(); var index = (int)state + ;
var padding = new String(' ', (index - ) * ); Console.WriteLine(string.Format("{0}第{1}次:开始读取", padding, index));
var cmd = new SqlCommand("select top 1 * from Users(updlock)", con);
cmd.ExecuteReader().Close(); Console.WriteLine(string.Format("{0}第{1}次:开始休眠", padding, index));
System.Threading.Thread.Sleep( * ((int)state + )); Console.WriteLine(string.Format("{0}第{1}次:开始修改", padding, index));
cmd = new SqlCommand("update Users set Name = N'段光宇" + state + "'", con);
cmd.ExecuteNonQuery();
Console.WriteLine(string.Format("{0}第{1}次:完成修改", padding, index));
} ts.Complete();
}
}, i).ContinueWith((t) =>
{
Console.WriteLine(t.Exception.InnerException.Message);
}, TaskContinuationOptions.OnlyOnFaulted);
} Console.ReadLine();
}
输出
说明
修改锁会阻塞其它更新锁的获取,因此所有任务都串行化了。
备注
最后补充一点,如果采用可串行化隔离级别,共享锁不只会延长锁定时间,锁对应的资源的粒度也会变大(锁表)。
从此不再迷茫,感觉入门了。
.NET:“事务、并发、并发问题、事务隔离级别、锁”小议,重点介绍:“事务隔离级别"如何影响 “锁”?的更多相关文章
- 网络协议 finally{ return问题 注入问题 jdbc注册驱动问题 PreparedStatement 连接池目的 1.2.1DBCP连接池 C3P0连接池 MYSQL两种方式进行实物管理 JDBC事务 DBUtils事务 ThreadLocal 事务特性 并发访问 隔离级别
1.1.1 API详解:注册驱动 DriverManager.registerDriver(new com.mysql.jdbc.Driver());不建议使用 原因有2个: >导致驱动被注册2 ...
- mysql中事务的并发问题与隔离级别
回归一下事务的四大特性ACID 1.原子性(Atomicity) 事务开始后所有操作,要么全部做完,要么全部不做.事务是一个不可分割的整体.事务在执行过程中出错,会回滚到事务开始之前的状态,以此来保证 ...
- 什么是事务?事务的四个特性(ACID)?并发事务带来哪些问题?事务隔离级别都有哪些?事务的传播特性
什么是事务? 事务是应用程序中一系列严密的操作,所有操作必须成功完成,否则在每个操作中所作的所有更改都会被撤消.也就是事务具有原子性,一个事务中的一系列的操作要么全部成功,要么一个都不做. 事物的四个 ...
- Microsoft SQL Server中的事务与并发详解
本篇索引: 1.事务 2.锁定和阻塞 3.隔离级别 4.死锁 一.事务 1.1 事务的概念 事务是作为单个工作单元而执行的一系列操作,比如查询和修改数据等. 事务是数据库并发控制的基本单位,一条或者一 ...
- Mysql事务,并发问题,锁机制
.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库正确地改变状态后,数据库的一致性约束 ...
- sql之事务和并发
1.Transaction(事务)是什么: 事务是作为单一工作单元而执行的一系列操作.包括增删查改. 2.事务的种类: 事务分为显示事务和隐式事务: 隐式事务:就是平常我们使用每一条sql 语句就是一 ...
- Hibernate事务与并发问题处理(乐观锁与悲观锁)
目录 一.数据库事务的定义 二.数据库事务并发可能带来的问题 三.数据库事务隔离级别 四.使用Hibernate设置数据库隔离级别 五.使用悲观锁解决事务并发问题 六.使用乐观锁解决事务并发问题 Hi ...
- SQL事务与并发
1.Transaction(事务)是什么: 事务是作为单一工作单元而执行的一系列操作.包括增删查改. 2.事务的种类: 事务分为显示事务和隐式事务: 隐式事务:就是平常我们使用每一条sql 语句就是一 ...
- Mysql事务,并发问题,锁机制-- 幻读、不可重复读(转)
1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库正确地改变状态后,数据库的一致性约 ...
随机推荐
- 20155309 《Java程序设计》实验三(Java面向对象程序设计)实验报告
一.实验内容及步骤 (一)编码标准 在IDEA中使用工具(Code->Reformate Code)把代码重新格式化. (二)在码云上把自己的学习搭档加入自己的项目中,确认搭档的项目加入自己后, ...
- WinForm界面开发之 启动界面
我们在开发桌面应用程序的时候,由于程序启动比较慢,往往为了提高用户的体验,增加一个闪屏,也就是SplashScreen,好处有:1.让用户看到加载的过程,提高程序的交互响应:2.可以简短展示或者介绍程 ...
- 一步一步学习IdentityServer3 (13) 令牌
IdentityServer3中客户端保护了授权资源,不难看出在IdentityServer3中,有这样一个设置 AllowedScopes = new List<string> { &q ...
- CCF CSP 201612-2 工资计算
CCF计算机职业资格认证考试题解系列文章为meelo原创,请务必以链接形式注明本文地址 CCF CSP 201612-2 工资计算 问题描述 小明的公司每个月给小明发工资,而小明拿到的工资为交完个人所 ...
- Eclipse+Tomcat+Axis2+ADT开发环境配置
一.安装Eclipse和Tomcat 1.安装Eclipse: 2.解压缩安装apache-tomcat-6.0.41 3.tomcat配置环境变量(4个) TOMCAT_HOME D:\An ...
- HBase(二)CentOS7.5搭建HBase1.2.6HA集群
一.安装前提 1.HBase 依赖于 HDFS 做底层的数据存储 2.HBase 依赖于 MapReduce 做数据计算 3.HBase 依赖于 ZooKeeper 做服务协调 4.HBase源码是j ...
- 【Java网络编程】基于 UDP 的聊天通信
使用 udp 协议,写一个基于命令行的聊天软件:客户端跟服务端分别在命令行启动之后,客户端和服务器端可以互相发送数据. 代码实现如下: 一.创建线程 sendThread 和 receiveThrea ...
- poj1703 Find them, Catch them(带权并查集)
题目链接 http://poj.org/problem?id=1703 题意 有两个帮派:龙帮和蛇帮,两个帮派共有n个人(编号1~n),输入m组数据,每组数据为D [a][b]或A [a][b],D[ ...
- C# Zip解压缩,规避 [content_types].xml 文件
使用 System.IO.Packaging.Package 进行压缩和解压时,会自动生成 [content_types].xml 文件. The Structure of the [Content_ ...
- Java异常处理中的恢复模型
异常处理理论上有两种基本模型.Java支持终止模型,在这种模型中,假设错误非常关键,以至于程序无法返回到异常发生的地方继续执行.一旦异常被抛出,就表明错误已无法挽回,也不能回来继续执行.长久以来,尽管 ...