脏读、不可重复读 共享锁、悲观锁 和 事务五种隔离级别

1、脏读、不可重复读、幻读 html

一、脏读:脏读就是指当一个事务正在访问数据,而且对数据进行了修改,而这种修改尚未提交到数据库中,这时,另一个事务也访问这个数据,而后使用了这个数据。
例如:
  张三的工资为5000,事务A中把他的工资改成8000,但事务A还没有提交。
  与此同时,
  事务B正在读取张三的工资,读取到张三的工资为8000。
  随后,
  事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
  最后,
  事务B读取到的张三工资为8000的数据即为脏数据,事务B作了一次脏读。 程序员

二、不可重复读:是指在一个事务内,屡次读同一数据。在这个事务尚未结束时,另一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,因为第二个事务的修改,那么第一个事务两次读到的的数据多是不同的。这样就发生了在一个事务内两次读到的数据是不同的,所以称为是不可重复读。
例如:
  在事务A中,读取到张三的工资为5000,操做没有完成,事务还没提交。
  与此同时,
  事务B把张三的工资改成8000,并提交了事务。
  随后,
  在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中先后两次读取的结果并不致,致使了不可重复读。 sql

三、幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的所有数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,之后就会发生操做第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉同样。
例如:
  目前工资为5000的员工有10人,事务A读取全部工资为5000的人数为10人。
  此时,
  事务B插入一条工资也为5000的记录。
  这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。 数据库

四、提醒
不可重复读的重点是修改:
一样的条件,你读取过的数据,再次读取出来发现值不同了
幻读的重点在于新增或者删除:
一样的条件,第 1 次和第 2 次读出来的记录数不同

2、独占锁、共享锁、更新锁,乐观锁、悲观锁 c#

一、锁的两种分类方式 安全

(1)从数据库系统的角度来看,锁分为如下三种类型: session

 独占锁(Exclusive Lock)
      独占锁锁定的资源只容许进行锁定操做的程序使用,其它任何对它的操做均不会被接受。执行数据更新命令,即INSERT、 UPDATE 或DELETE 命令时,SQL Server 会自动使用独占锁。但当对象上有其它锁存在时,没法对其加独占锁。独占锁一直到事务结束才能被释放。
 共享锁(Shared Lock)
      共享锁锁定的资源能够被其它用户读取,但其它用户不能修改它。在SELECT 命令执行时,SQL Server 一般会对对象进行共享锁锁定。一般加共享锁的数据页被读取完毕后,共享锁就会当即被释放。
 更新锁(Update Lock)
      更新锁是为了防止死锁而设立的。当SQL Server 准备更新数据时,它首先对数据对象做更新锁锁定,这样数据将不能被修改,但能够读取。等到SQL Server 肯定要进行更新数据操做时,它会自动将更新锁换为独占锁。但当对象上有其它锁存在时,没法对其做更新锁锁定。 并发

(2)从程序员的角度看,锁分为如下两种类型: 框架

悲观锁(Pessimistic Lock)
      悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其余事务,以及来自外部系统的事务处理)修改持保守态度,所以在整个数据处理过程当中,将数据处于锁定状态。悲观锁的实现,每每依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,不然,即便在本系统中实现了加锁机制,也没法保证外部系统不会修改数据)。 性能

乐观锁(Optimistic Lock)
      相对悲观锁而言,乐观锁机制采起了更加宽松的加锁机制。悲观锁大多数状况下依靠数据库的锁机制实现,以保证操做最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销每每没法承受。
      而乐观锁机制在必定程度上解决了这个问题。乐观锁,大可能是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增长一个版本标识,在基于数据库表的版本解决方案中,通常是经过为数据库表增长一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。

二、数据库中如何使用锁

首先从悲观锁开始说。在SqlServer等其他不少数据库中,数据的锁定一般采用页级锁的方式,也就是说对一张表内的数据是一种串行化的更新插入机制,在任什么时候间同一张表只会插1条数据,别的想插入的数据要等到这一条数据插完之后才能依次插入。带来的后果就是性能的下降,在多用户并发访问的时候,当对一张表进行频繁操做时,会发现响应效率很低,数据库常常处于一种假死状态。而Oracle用的是行级锁,只是对想锁定的数据才进行锁定,其他的数据不相干,因此在对Oracle表中并发插数据的时候,基本上不会有任何影响。

注:对于悲观锁是针对并发的可能性比较大,而通常在咱们的应用中用乐观锁足以。

Oracle的悲观锁须要利用一条现有的链接,分红两种方式,从SQL语句的区别来看,就是一种是for update,一种是for update nowait的形式。

好比咱们看一个例子。首先创建测试用的数据库表:

CREATE TABLE TEST(ID,NAME,LOCATION,VALUE,CONSTRAINT test_pk PRIMARY KEY(ID))AS SELECT deptno, dname, loc, 1 FROM scott.dept

这里咱们利用了Oracle的Sample的scott用户的表,把数据copy到咱们的test表中。

(1)for update 形式介绍

而后咱们看一下for update锁定方式。咱们执行以下的select for update语句:

select * from test where id = 10 for update

经过这条检索语句锁定之后,再开另一个sql*plus窗口进行操做,再把上面这条sql语句执行一便,你会发现sqlplus好像死在那里了,好像检索不到数据的样子,可是也不返回任何结果,就属于卡在那里的感受。这个时候是什么缘由呢,就是一开始的第一个Session中的select for update语句把数据锁定住了。因为这里锁定的机制是wait的状态(只要不表示nowait那就是wait),因此第二个Session(也就是卡住的那个sql*plus)中当前这个检索就处于等待状态。当第一个session最后commit或者rollback以后,第二个session中的检索结果就是自动跳出来,而且也把数据锁定住。

不过若是你第二个session中你的检索语句以下所示:select * from test where id = 10,也就是没有for update这种锁定数据的语句的话,就不会形成阻塞了。

(2)for update nowait 形式介绍

另一种状况,就是当数据库数据被锁定的时候,也就是执行刚才for update那条sql之后,咱们在另一个session中执行for update nowait后又是什么样呢。
好比以下的sql语句:

select * from test where id = 10 for update nowait

因为这条语句中是制定采用nowait方式来进行检索,因此当发现数据被别的session锁定中的时候,就会迅速返回ORA-00054错误,内容是资源正忙, 但指定以 NOWAIT 方式获取资源。因此在程序中咱们能够采用nowait方式迅速判断当前数据是否被锁定中,若是锁定中的话,就要采起相应的业务措施进行处理。

那这里另一个问题,就是当咱们锁定住数据的时候,咱们对数据进行更新和删除的话会是什么样呢。

好比一样,咱们让第一个Session锁定住id=10的那条数据,咱们在第二个session中执行以下语句:

update test set value=2 where id = 10

这个时候咱们发现update语句就好像select for update语句同样也停住卡在这里,当你第一个session放开锁定之后update才能正常运行。当你update运行后,数据又被你update 语句锁定住了,这个时候只要你update后尚未commit,别的session照样不能对数据进行锁定更新等等。

总之,Oracle中的悲观锁就是利用Oracle的Connection对数据进行锁定。在Oracle中,用这种行级锁带来的性能损失是很小的,只是要注意程序逻辑,不要给你一不当心搞成死锁了就好。并且因为数据的及时锁定,在数据提交时候就不呼出现冲突,能够省去不少恼人的数据冲突处理。缺点就是你必需要始终有一条数据库链接,就是说在整个锁定到最后放开锁的过程当中,你的数据库联接要始终保持住。

与悲观锁相对的,咱们有了乐观锁。乐观锁一开始也说了,就是一开始假设不会形成数据冲突,在最后提交的时候再进行数据冲突检测。

在乐观锁中,咱们有3种经常使用的作法来实现:

a. 在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。
    当发现两个数据如出一辙之后,就表示没有冲突能够提交,不然则是并发冲突,须要去用业务逻辑进行解决。

b. 乐观锁的作法就是采用版本戳,这个在Hibernate中获得了使用。
    采用版本戳的话,首先须要在你有乐观锁的数据库table上创建一个新的column,好比为number型,当你数据每更新一次的时候,版本数就会往上增长1。
    好比一样有2个session一样对某条数据进行操做。二者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和本身一开始取到的版本相同。就正式提交,而后把版本号增长1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知作别人更新过此条数据,这个时候再进行业务处理,好比整个Transaction都Rollback等等操做。
    在用版本戳的时候,能够在应用程序侧使用版本戳的验证,也能够在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销仍是比较的大,因此能在应用侧进行验证的话仍是推荐不用Trigger。

c. 第三种作法和第二种作法有点相似,就是也新增一个Table的Column,不过此次这个column是采用timestamp型,存储数据最后更新的时间。
    在Oracle9i之后能够采用新的数据类型,也就是timestamp with time zone类型来作时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),通常来讲,加上数据库处理时间和人的思考动做时间,微秒级别是很是很是够了,其实只要精确到毫秒甚至秒都应该没有什么问题。
    和刚才的版本戳相似,也是在更新提交的时候检查当前数据库中数据的时间戳和本身更新前取到的时间戳进行对比,若是一致则OK,不然就是版本冲突。若是不想把代码写在程序中或者因为别的缘由没法把代码写在现有的程序中,也能够把这个时间戳乐观锁逻辑写在Trigger或者存储过程当中。

 

3、事务五种隔离级别

Isolation 属性一共支持五种事务设置,具体介绍以下:
(1)DEFAULT
  使用数据库设置的隔离级别(默认),由DBA 默认的设置来决定隔离级别。
(2)READ_UNCOMMITTED
  这是事务最低的隔离级别,它充许别外一个事务能够看到这个事务未提交的数据。
  会出现脏读、不可重复读、幻读 (隔离级别最低,并发性能高)。
(3)READ_COMMITTED
  保证一个事务修改的数据提交后才能被另一个事务读取。另一个事务不能读取该事务未提交的数据。
  能够避免脏读,但会出现不可重复读、幻读问题(锁定正在读取的行)。
(4)REPEATABLE_READ
  能够防止脏读、不可重复读,但会出幻读(锁定所读取的全部行)。
(5)SERIALIZABLE
  这是花费最高代价可是最可靠的事务隔离级别,事务被处理为顺序执行。
  保证全部的状况不会发生(锁表)。

 

4、c# 事务原理

企业级的数据库每一秒钟均可能应付成千上万的并发访问,于是带来了并发控制的问题。由数据库理论可知,因为并发访问,在不可预料的时刻可能引起以下几个能够预料的问题:
  脏读:包含未提交数据的读取。例如,事务1 更改了某行。事务2 在事务1 提交更改以前读取已更改的行。若是事务1 回滚更改,则事务2 便读取了逻辑上从未存在过的行。
  不可重复读取:当某个事务不止一次读取同一行,而且一个单独的事务在两次(或屡次)读取之间修改该行时,由于在同一个事务内的屡次读取之间修改了该行,因此每次读取都生成不一样值,从而引起不一致问题。
  幻象:经过一个任务,在之前由另外一个还没有提交其事务的任务读取的行的范围中插入新行或删除现有行。带有未提交事务的任务因为该范围中行数的更改而没法重复其原始读取。

如你所想,这些状况发生的根本缘由都是由于在并发访问的时候,没有一个机制避免交叉存取所形成的。而隔离级别的设置,正是为了不这些状况的发生。事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别能够增长并发,但代价是下降数据的正确性。相反,较高的隔离级别能够确保数据的正确性,但可能对并发产生负面影响。

根据隔离级别的不一样,DBMS为并行访问提供不一样的互斥保证。在SQL Server数据库中,提供四种隔离级别:未提交读、提交读、可重复读、可串行读。这四种隔离级别能够不一样程度地保证并发的数据完整性: 

隔离级别 脏 读 不可重复读取 幻 像
未提交读
提交读
可重复读
可串行读

 

 

 

 

能够看出,“可串行读”提供了最高级别的隔离,这时并发事务的执行结果将与串行执行的彻底一致。如前所述,最高级别的隔离也就意味着最低程度的并发,所以,在此隔离级别下,数据库的服务效率事实上是比较低的。尽管可串行性对于事务确保数据库中的数据在全部时间内的正确性至关重要,然而许多事务并不老是要求彻底的隔离。例如,多个做者工做于同一本书的不一样章节。新章节能够在任意时候提交到项目中。可是,对于已经编辑过的章节,没有编辑人员的批准,做者不能对此章节进行任何更改。这样,尽管有未编辑的新章节,但编辑人员仍能够确保在任意时间该书籍项目的正确性。编辑人员能够查看之前编辑的章节以及最近提交的章节。这样,其它的几种隔离级别也有其存在的意义。

在.net框架中,事务的隔离级别是由枚举System.Data.IsolationLevel所定义的:

[Flags] [Serializable] public enum IsolationLevel

其成员及相应的含义以下:

成 员 含 义
Chaos 没法改写隔离级别更高的事务中的挂起的更改。
ReadCommitted 在正在读取数据时保持共享锁,以免脏读,可是在事务结束以前能够更改数据,从而致使不可重复的读取或幻像数据。
ReadUncommitted 能够进行脏读,意思是说,不发布共享锁,也不接受独占锁。
RepeatableRead 在查询中使用的全部数据上放置锁,以防止其余用户更新这些数据。防止不可重复的读取,可是仍能够有幻像行。
Serializable 在DataSet上放置范围锁,以防止在事务完成以前由其余用户更新行或向数据集中插入行。
Unspecified 正在使用与指定隔离级别不一样的隔离级别,可是没法肯定该级别。

显而意见,数据库的四个隔离级别在这里都有映射。

默认的状况下,SQL Server使用ReadCommitted(提交读)隔离级别。

关于隔离级别的最后一点就是若是你在事务执行的过程当中改变了隔离级别,那么后面的命名都在最新的隔离级别下执行——隔离级别的改变是当即生效的。有了这一点,你能够在你的事务中更灵活地使用隔离级别从而达到更高的效率和并发安全性。

 

转载:http://cupoy.iteye.com/blog/251796         http://luijnijei.blog.163.com/blog/static/35024594201061485547777/         http://hi.baidu.com/eredlab/blog/item/13d84ef4896207cdf3d385fc.html         http://pengzong155.blog.163.com/blog/static/104028160200912073636329/

相关文章
相关标签/搜索