一起谈.NET技术,.NET中锁6大处理方法 悲观乐观自己掌握

  本文介绍了处理.NET中锁的6种方法,首先我们讨论一下并发性问题,然后讨论处理乐观锁的3种方法,乐观锁不能从根源上解决并发问题,因此后面我们介绍了悲观锁,最后介绍隔离级别如何帮助我们实现悲观锁,每个隔离级别都列举了示例进行说明,使得概念更加清晰。

  我们为什么需要锁?

  在多用户环境中,在同一时间可能会有多个用户更新相同的记录,这就会产生冲突,这个就是著名的并发性问题。

  图 1 并行性问题漫画

  如何解决并发性问题?

  借助正确的锁定策略可以解决并发性问题,资源被锁定后,其它进程想要访问它就会被阻止。

  并发会造成什么样的冲突?

  并发主要会导致四种常见的问题,详细情况请看下表。

问题 简要描述 解释
脏读取 当一个事务读取其它完成一半事务的记录时,就会发生脏读取
  • 用户A和用户B看到的值都是5
  •  用户B将值修改为2
  •  用户A看到的值仍然是5,这时就发生了脏读取
不可重复读取 在每次读数据时,如果你获得的值都不一样,那表明你遇到了不可重复读取问题
  • 用户A看到的值是5
  • 用户B将值改为2
  • 用户A刷新后看到的值仍然是5,这时就发生了不可重复读取
虚幻行 如果update和delete SQL语句未对数据造成影响,很可能遇到了虚幻行问题
  • 用户A将所有值从5修改为2
  • 用户B使用值2插入一个新记录
  • 用户A查询所有值为2的记录,但却找不到,这时就发生了虚幻行
更新丢失 一个事务的更新覆盖了其它事务的更新结果,就是所谓的更新丢失
  • 用户A将所有值从5更新为2
  • 用户B将所有值从2更新到5
  •  用户A丢失了他的更新

  如何解决上述冲突?

  答案是使用乐观锁或悲观锁,下面将进一步进行阐述。

  图 2 乐观锁和悲观锁  

  什么是乐观锁?

  顾名思义,乐观锁假设多个事务相互不会影响对方,换句话说就是,在乐观锁模式下,没有锁操作会得到执行,事务只是验证是否有其它事务修改数据,如果有则进行事务回滚,否则就提交。

 图 3 乐观锁

  乐观锁是如何工作的?

  实现乐观锁的方法有多种,但基本原则都一样,总是少不了下面五个步骤:

  •记录当前的时间戳

  •开始修改值

  •在更新前,检查是否有其他人更新了值(通过检查新旧时间戳实现)

  • 如果不相等就回滚,否则就提交

  图 4 乐观锁的工作原理

  实现乐观锁的解决方案

  在.NET中,实现乐观锁的方法主要有三种:

  •数据集(Dataset):数据集是实现乐观锁的默认方法,在更新前它会检查新旧值。

  • 时间戳数据类型(timestamp):在你的表中创建一个timestamp数据类型,在更新时,检查旧时间戳是否等于新时间戳。

  •直接检查新旧值:在更新时检查旧值和新值是否相等,如果不相等就回滚,否则就提交。

  解决方案1:数据集

  正如前面所说的,数据集是处理乐观锁的默认方法,下面是一个简单的快照,在Adapter的update函数上有一个调试点,当我移除断点运行update函数时,它抛出如下图所示的并行异常错误。

  图 5 Update函数执行时抛出的异常错误

  如果你运行后端分析器,你将会看到更新语句检查当前值和旧值是否相等: 


exec sp_executesql N'UPDATE [tbl_items] SET [AuthorName] = @p1
 WHERE (([Id] = @p2) AND ((@p3 = 1 AND [ItemName] IS NULL)
OR ([ItemName] = @p4)) AND ((@p5 = 1 AND [Type] IS NULL)
OR ([Type] = @p6)) AND ((@p7 = 1 AND [AuthorName] IS NULL)
OR ([AuthorName] = @p8)) AND ((@p9 = 1 AND [Vendor] IS NULL)
OR ([Vendor] = @p10)))',N'@p1 nvarchar(11),@p2 int,@p3
int,@p4 nvarchar(4),@p5 int,@p6 int,@p7
int,@p8 nvarchar(18),@p9 int,@p10 nvarchar(2)',
@p1=N'this is new',@p2=2,@p3=0,@p4=N'1001',@p5=0,@p6=3,@p7=0,
@p8=N'This is Old
Author',@p9=0,@p10=N'kk'

  在这种情况下,我尝试将“AuthorName”字段值修改为“This is new”,但更新时会检查旧值“This is old author”,下面是比较旧值的精简代码段: 


,@p8=N'This is Old Author'

  解决方案2:使用timestamp数据类型

  SQL Server有一个数据类型是timestamp,它是实现乐观锁的另一种途径,每次更新SQL Server数据时,时间戳会自动产生一个唯一的二进制数值,时间戳数据类型可用来版本化你的记录更新。

 图 6 timestamp数据类型

  为了实现乐观锁,首先需要取得旧的时间戳值,在更新时检查旧的时间戳值是否等于当前时间戳,如:


update tbl_items set itemname=@itemname where CurrentTimestamp=@OldTimeStamp

  然后检查是否发生了更新操作,如果没有发生更新,则使用SQL Server的raiserror产生一系列错误消息。 


if(@@rowcount=0)
begin
raiserror('Hello some else changed the value',16,10)
end

  如果发生了并发冲突,当你如下图所示这样调用ExecuteNonQuery时,你应该会看到错误传播。

  图 7 时间戳发生变化,存储过程产生了错误

  解决方案3:检查旧值和新值

  许多时候,我们只需要检查相关字段值的一致性,其它字段则可以忽略,在update语句中,我们可以直接做这种比较。


update tbl_items set itemname=@itemname where itemname=@OldItemNameValue

  但使用乐观锁似乎没有完全解决问题,使用乐观锁只能检查并发性问题,为了从根源上解决并发性问题,我们需要使用悲观锁,因此乐观锁能起到预防作用,而悲观锁则能治愈。

  什么是悲观锁?

  悲观锁总是假定会发生并发性/冲突问题,因此会先对记录上锁,然后再更新。

图 8 悲观锁

  如何处理悲观锁?

  我们可以在SQL Server存储过程中指定IsolationLevel(隔离级别),ADO.NET级别或使用事务范围对象处理悲观锁。

  使用悲观锁可以获得什么样的锁?

  使用悲观锁可以获得四种类型的锁:共享(Shared)、独占(Exclusive),更新(Update)和意图(Intent),前两个是真实的锁,后面两个是锁和标记的混合。

  何时使用 允许读 允许写
共享锁 当你只想读,不希望其它事务进行更新时
独占锁 当你想修改数据,同时不希望别人可以读,直到你更新完毕时
更新锁 这是一个混合锁,当你执行的更新操作有多个步骤时使用    
读阶段
操作阶段
更新阶段
意向锁(请求锁) 意向锁是分级的,当你想锁定下级资源时使用,例如,在表上的一个共享意向锁意味着共享锁是针对页面和表中的行的, 不适用 不适用
模式锁 当你修改表结构时使用
大数据块更新锁 当你执行大数据块更新时使用 表级(否) 表级(否)

 

  详解让人困惑的更新锁

  其它锁都好理解,唯独更新锁让人迷糊,因为它混合了锁和标记,在更新前我们首先要读取记录,在读取期间锁是共享的,而在真正更新时,我们需要独占锁,更新锁是非常短暂的。

 图 9 更新时用到的几种锁

  不同隔离级别之间的差异,以及何时使用它们

  有4种事务隔离级别,下表列出了这4种隔离级别及其使用时间。

隔离级别 更新 插入
读未提交的 读取未提交的数据 允许 允许
读已提交的(默认) 读取已提交的数据 允许 允许
重复读 读取已提交的数据 不允许 允许
序列化 读取已提交的数据 不允许 不允许

  如何指定隔离级别?

  隔离级别是关系数据库的一个功能,也就是说,它基本上只与SQL Server相关,而与ADO.NET,EF或LINQ是没有什么关系的,但你可以在这些组件上设置隔离级别。

图 10 隔离级别

  中间层:在中间层,你可以使用事务范围对象指定隔离级别。 


TransactionOptions TransOpt = New TransactionOptions();
TransOpt.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
using(TransactionScope scope = new
TransactionScope(TransactionScopeOption.Required, TransOptions))
{

  ADO.NET:在ADO.NET中你可以使用SqlTransaction对象指定事务隔离级别。


SqlTransaction objtransaction =
objConnection.BeginTransaction(System.Data.IsolationLevel.Serializable);

  SQL Server:你也可以在TSQL中使用“SET TRANSACATION ISOLATION LEVEL”指定隔离级别。


SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

  事务隔离级别与它能解决的并发性问题之间的对应关系

  读已提交的 重复读 序列化 读未提交的
脏读取 能解决 能解决 能解决 不能
丢失更新 不能 能解决 能解决 不能
非重复读 不能 能解决 能解决 不能
幻想行 不能 不能 能解决 不能

  解决方案4:使用“读已提交的”解决“脏读取”问题

  关于“读已提交的”的一些关键点:

  •它是SQL Server默认的事务隔离级别。

  •它只读取已提交的数据,换句话说就是,任何未提交的数据它都会置之不理,直到数据提交为止,下图对其进行了详细解释,你也可以看到更新。

  图 11 读已提交模式解析

  如果你想看到上图所述的情况,只需要按照下面的步骤做就可以了:

  •打开两个查询窗口,执行一个更新事务,但不提交;

  •在第二个窗口中执行查询时,会显示如下图所示的查询被阻止的提示。

  图 12 查询被阻止,直到更新事务提交后才能执行

  “读已提交的”对立面是“读未提交的”吗?

  是的,读未提交的是读已提交的对立面,当你设置读未提交的事务隔离级别时,未提交的数据也被读取了。

  关于“读已提交的”关键点:

  •未提交的是可见的,因此脏读取是可能的;

  •没有锁被抓住;

  • 当锁不重要时很有用,更重要的是并发性和吞吐量。

  如果你也想测试一下,试试下面的SQL语句,它执行一个更新然,在等待20秒后回滚,在此期间如果你执行查询,返回的是未提交的数据,但20秒后,你再查询,返回的将会以前的旧数据,因为提交的数据已回滚。 


set transaction isolation level read uncommitted
Begin Tran

Update customer
set CustomerName='Changed'where CustomerCode='1001'
WAITFOR DELAY '000:00:20'
rollback transet transaction isolation level read uncommitted
select * from Customer where CustomerCode='1001'

  解决方案5:使用重复读解决丢失更新和非重复读

  给重复读设置隔离级别后,其他人就不能读取和更新数据,关于重复读隔离级别的关键点包含:

   •当为查询设置重复事务隔离级别时,只读取已提交的数据。

   •当你使用重复读选择一条记录时,其它事务将不能更新该条记录,但查询是可以的。

  •如果在更新查询中设置了可重复事务,必须要等到事务完成才能读和更新相同的记录。

  • 当选择和更新查询被设置为可重复读,其它事务可以插入新记录,换句话说就是虚幻行是可能的。

  如果你想测试这个隔离级别,执行下面的语句,然后尝试查询和更新查询,它们都将被阻止,50秒后你才能看到数据。 


set transaction isolation level repeatable read
Begin Tran
Update customer set CustomerName='Changed'where CustomerCode='1001'
WAITFOR DELAY '000:00:50'
rollback tran

  如果在重复读模式下执行下面的查询语句,在50秒内你啥也干不了,直到事务完成后你才能得到查询结果。


set transaction isolation level repeatable read
begin tran
select * from Customer where CustomerCode='1001'
WAITFOR DELAY '000:00:50'
commit tran

  注意,在此期间你可以添加CustomerCode=’1001’的新记录,换句话说就是虚幻行是可能的。

  解决方案6:使用序列化隔离级别解决虚幻行问题

  这是最高级的隔离级别,在此期间,其它事务是不能更新,查询和插入记录的,关于序列化事务的一些关键点包含:

  •当隔离级别是序列化时,没有其它事务可以插入,更新,删除或查询。

  •会出现许多阻塞,但所有并发性问题都能得到解决。

时间: 2024-07-31 12:36:10

一起谈.NET技术,.NET中锁6大处理方法 悲观乐观自己掌握的相关文章

一起谈.NET技术,.NET 4九大新特性 FrameWork达到新境界

本文将向您介绍.NET框架4中的主要功能和改进特征.请注意,本文中并没有提供有关这些新功能的综合信息,并随时可能更改. 请注意,.NET框架4引入了一个改进的安全模式.有关该内容的更多的信息,请参阅文章<.NET框架4中的安全变化>. 具体来说,本文中将介绍.NET框架4的如下一些新功能和改进特征: 应用程序兼容性和部署 内核新功能及改进 托管扩展框架 并行计算 网络编程 Web开发 客户端开发 数据 通信和工作流 一.应用程序兼容性和部署 除了一些在安全.标准遵从.正确性.可靠性及性能等方面

一起谈.NET技术,.NET 4九大新特性

英文原文:What's New in the .NET Framework 4 本文将向您介绍.NET Framework 4中的主要功能和改进特征.请注意,本文中并没有提供有关这些新功能的综合信息,并随时可能更改. 请注意,.NET Framework 4引入了一个改进的安全模式.有关该内容的更多的信息,请参阅文章<.NET Framework 4中的安全变化>. 一.应用程序兼容性和部署 除了一些在安全.标准遵从.正确性.可靠性及性能等方面的改进之外,.NET Framework 4与基于

一起谈.NET技术,中软面试题-最新

      中软的面试比较经典,也比较严格,一般有四轮,类似于微软的面试.中软面过以后,根据项目组,会推到美国微软那边运用live meeting & con-call 再面一次.以下是我的面试题及个人的小分析,拿出来和大家share一下.希望更多的人能过这个坎.如有什么问题,可以一起交流.直接进入主题:  1. English communication. (sale yourself, project information, your interesting,and how to deal

一起谈.NET技术,.NET动态调用DLL的方法

很多软件都是可插拔的,最知名的便是微软的Windows操作系统.你可以在Windows操作系统上安装QQ,也可卸掉QQ,这便是可插拔.这里不谈Windows的实现,因为太过复杂.本文就谈谈管理软件的可插拔的实现.相对Windows操作系统,QQ就是它的一个插件.所以可以简单的将开发可插拔的软件分为两个部分.一个是主应用程序的开发,一个是插件的开发. 比Windows小的,常见的可插拔的软件是MSN.MSN主应用程序由MS开发,还存在一些MSN插件开发商,国内好像也有不少,这些插件开发商通过在插件

谈网站策划过程中的3大要点

网站策划是一项比较专业的工作,包括了解客户需求,客户评估.网站功能设计.网站结构规划.页面设计.内容编辑,撰写"网站功能需求分析报告",提供网站系统硬件.软件配置方案,整理相关技术资料和文字资料. 专业网站策划人才的就业前景非常广阔,目前我国专业的策划公司超过一万家,从事自主网站经营的企业在十万家以上,各类企业的信息部门也急需专业的网站策划人员,有关数据显示,有65%的企业急需聘用网站策划人员,有90%的企业招不到合适人选. 网站策划是指应用科学的思维方法,进行情报收集与分析,对网站设

一起谈.NET技术,Linq To SQL 批量更新方法汇总

方法一.官方例子 地球人都知道的,也是不少 Linq To SQL 反对者认为效率低下的一种方法. NorthwindDataContext db = new NorthwindDataContext(); var customers = db.Customers.Where(c => c.CustomerID.StartsWith("BL")); foreach (var customer in customers) { customer.Address = "Gua

一起谈.NET技术,微博是个大金矿,使用VS2010编译QOAuth支持微博通用认证OAuth实现SINA微博登陆

随着Twitter的兴起和国内Sina和QQ等公司的追随,微博现在是如日中天,将传统的SNS给完全比拼下去,微博对于大家来说完全是个尚未完全开采的大金矿,对于一直站在潮流最前端的程序员来说怎么能将这么好的机会错失呢. 在这里我抛砖引玉,先介绍下如何在Qt平台上编译QAuth来支持现在微博的通用认证OAuth, 根据Twitter的API Wiki,基本的OAuth验证workflow如下: 1. 程序利用http://api.twitter.com/oauth/request_token来从tw

一起谈.NET技术,细数十大免费构建ASP.NET网站必备工具

最近使用ASP.NET为公司构建了一个简单的公共网站(该网站的地址:http://superexpert.com/.在这个过程中,我们使用了数量很多的免费工具,如果把构建ASP.NET网站的必备工具总结一下,将会是一件十分有趣的事情.这些工具既支持ASP.NET Web Forms又支持ASP.NET MVC. 性能工具 读了两本关于网站的前端性能的书(这两本优秀的图书分别是:<High Performance Web Sites> 和 <Even Faster Web Sites>

一起谈.NET技术,技术详解:三招优化.NET中的锁(组团)

在这篇文章中,我将使用三个方法处理乐观锁,包括ADO.NET数据集.SQL Server时间戳数据类型和新旧值检查,首先我们从并发谈起,探讨5个并发问题,然后从实际出发,利用这三种方法实现乐观锁. 为什么需要锁? 在多用户环境中,大家同时更新相同的记录可能会引发冲突,这个问题用专业的术语描述就叫做并发性.并发会造成什么样的冲突?并发主要会导致四种常见的问题,详细情况请看下表. 如何解决上述冲突? 答案是使用乐观锁或悲观锁,下面将进一步进行阐述.什么是乐观锁?顾名思义,乐观锁假设多个事务相互不会影