`

数据库提供了四种事务隔离级别

阅读更多

sql server锁的机制

 

 

锁有两种分类方法。

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

 

  • 独占锁(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” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如 果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。


     sql server的所有活动都会产生锁。锁定的单元越小,就越能越能提高并发处理能力,但是管理锁的开销越大。如何找到平衡点,使并发性和性能都可接受是sql server的难点。
sql server有如下几种琐:

  1. 共享锁  用于只读操作(select),锁定共享的资源。共享锁不会阻止其他用户读,但是阻止其他的用户写和修改。
  2. 更新锁   更新锁是一种意图锁,当一个事务已经请求共享琐后并试图请求一个独占锁的时候发生更新琐。例如当两个事务在几行数据行上都使用了共享锁,并同时试 图获取独占锁以执行更新操作时,就发生了死锁:都在等待对方释放共享锁而实现独占锁。更新锁的目的是只让一个事务获得更新锁,防止这种情况的发生。
  3. 独占锁  一次只能有一个独占锁用在一个资源上,并且阻止其他所有的锁包括共享缩。写是独占锁,可以有效的防止’脏读’。
  4. 意图缩  在使用共享锁和独占锁之前,使用意图锁。从表的层次上查看意图锁,以判断事务能否获得共享锁和独占锁,提高了系统的性能,不需从页或者行上检查。
  5. 计划锁   sch-m,sch-s。对数据库结构改变时用sch-m,对查询进行编译时用sch-s。这两种锁不会阻塞任何事务锁,包括独占锁。


读是共享锁,写是排他锁,先读后更新的操作是更新锁,更新锁成功并且改变了数据时更新锁升级到排他锁。锁的类型有:
db-----数据库,由于 dbid 列已包含数据库的数据库 id,所以没有提供任何信息
fil----文件
idx----索引
pg-----页,数据或索引页。页码。页由 fileid:page 组合进行标识,其中,fileid 是 sysfiles 表中的 fileid,而 page 是该文件内的逻辑页码。
key----键,用于保护可串行事务中的键范围
tab----表,包括所有数据和索引在内的整个表。由于 objid 列已包含表的对象 id,所以没有提供任何信息
ext----区域, 相邻的八个数据页或索引页构成的一组。正被锁定的扩展盘区中的第一个页码。页由 fileid:page 组合进行标识
rid----行,表内已锁定行的行标识符。行由 fileid:page:rid 组合进行标识,其中,rid 是页中的行标识符。

细分锁的模式:
0 null 没有得到资源的访问权限
1 sch-s (schema stability) 对查询进行编译时。能防止加锁的对象被删除直到解锁
2 sch-m (schema modification) 改变数据库结构时发生。能防止其他的事务访问加锁的对象
3 is (intent shares) 意图共享锁。
4 siu(share intent update) 意图在维护资源的共享锁时,把更新锁放到锁层次结构的下层资源上
5 is-s(intent share-shared) 复合键范围锁
6 ix(intent exclusive) 意图排他锁
7 six(share intent exclusive)
8 s(share) 共享锁
9 u(update) 更新锁。防止死锁
10 iin-nul(intent insert-null) 索引行层次的锁定,复合键范围锁
11 is-x(intent share-exclusive)
12 iu(intent update) 意图更新锁
13 is-u(intent share update) 串行更新扫描
14 x(exclusive) 排他锁
15 bu 块操作使用的锁

所以有如下的结论。

  1. 一个连接在修改数据块时别的连接不能修改这个数据块,直到解锁。并行访问是任何数据库解决方案都最为重视的问题了,为了解决并行访问方面的问题各类数据库系统提出了各种各样的方案。sql server采用了多线程机制,它当然能够一次处理多个请求。不过,在用户修改数据的情况下并行访问问题就变得复杂起来了。显然,数据库通常只允许唯一用 户一次修改特定的数据。当某一用户开始修改某块数据时, sql server能很快地锁定数据,阻止其他用户对这块数据进行更新,直到修改该数据的第一位用户完成其操作并提交交易或者回滚。但是,当某一位用户正在修改 某块数据时假设另一位用户又正想查询该数据的信息时会发生什么情况呢?
  2. 通常情况下,一个连接在修改数据块时别的连接也不能查询这个数据块,直到解锁。反之亦然:读的时候不能写和修改。这个方案会降低系统的性能和 效率,尽管现在是行级锁(7.0以前是锁页甚至是锁表),如果你一次修改多行数据,sql server则会把数据锁定范围提升到页级别乃至锁定整个数据表,从而不必针对每一记录跟踪和维护各自的数据锁,这样能加快修改的速度,消耗小的服务器资 源,但是并发性就差了。。
  3. 一个连接写的时候,另一个连接可以写,但是不得读
  4. 多个连接可以同时读同一行。


所以锁发生在读、写的竞争上。
5.设置事务的级别
数据库事务有严格的定义,它必须同时满足4个特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和 持久性(Durabiliy) ,简称为ACID。其中隔离性指在并发数据操作时,不同的事务拥有各自的数据空间,其操作不会对对方产生干扰。隔离允许事务 行为独立或隔离于其他并发运行的事务。通过控制隔离,每个事务在其行动时间里都像是修改数据库的惟一事务。一个事务与其他事务隔离的程度称为隔离级别。数 据库规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱

  1. read uncommitted   级别read uncommitted(又称读取未提交内容)允许任务读取数据库中未提交的数据更改。也称为脏读,原因在于任务会显示以后被回退的结果。
  2. Read Committed     级别Read Committed(又称读取已提交内容)可防止脏读。该级别查询只读取已提交的数据更改。如果事务需要读取被另一未完成事务修改的数据,该事务将等待,直到第一个事务完成(提交或回退)。
  3. repeatable read:锁定查询中使用的所有数据以防止其他用户更新数据,但是其他用户可以将新的幻像行插入数据集,且幻像行包括在当前事务的后续读取中。因为并发低于默认隔离级别,所以应只在必要时才使用该选项。
  4. Serializable   级别Serializable(可串行化)可防止幻像和非重复读。在非Serializable的级别下,如果一个事务读取了满足搜索条件的一组 数据记录,而第二个事务修改了数据(通过 insert、delete 或 update 语句);如果第一个事务按相同的搜索条件重复读取,它将获得一组不同的数据记录,产生幻像读或非重复读。


注释
一次只能设置这些选项中的一个,而且设置的选项将一直对那个连接保持有效,直到显式更改该选项为止。这是默认行为,除非在语句的 from 子句中在表级上指定优化选项。
set transaction isolation level 的设置是在执行或运行时设置,而不是在分析时设置。


术语解释:
在一个程序中,依据事务的隔离级别将会有三种情况发生:

  1. 脏读(dirty read) :当一个事务读取另一个事务尚未提交的修改时 ,产生脏读。
  2. 非重复读(non-repeatable read) :同一查询在同一事务中多次进行,由于其他提交事务所做的修改或删除 ,每次返回不同的结果集,此时发生非重复读。
  3. 幻像读(phantom read) :             同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作 ,每次返回不同的结果集,此时发生幻像读。



结合以上的理论知识,将isolationlevel枚举的各值解释如下:

  1. readuncommitted: 假设a事务即不发布共享锁,也不接受独占锁 那么并发的b或者其它事务可以改写a事务读取的数据,那么并发的c事务读取到的数据的状态和a的或者b的数据都可能不一致,那么。脏读、不可重复读、幻象数据都可能存在。
  2. readcommitted: 该隔离级别与二级封锁协议相对应。读数据前加共享锁,读完就释放 假设a事务对正在读取数据data放置了共享锁 ,那么data不能被其它事务改写,所以当b事务对data进行读取时总和a读取的data数据是 一致的,所以避免了脏读。由于在a没有提交之前可以对data进行改写,那么b读取到的某个值可能会在其读取后被a更改从而导致了该值不能被重复取得;或 者当b再次用相同的where字句时得到了和前一次不一样数据的结果集,也就是幻像数据。 
  3. repeatableread: RepeatableRead对应第三级封锁协议:读前加共享锁,事务完成才释放 (注意msdn原文中的第一句话:在查询中使用的所有数据上放置锁,所以不存在脏读的情况)。假设a事务对读取的所有数据data放置了锁,以阻止其它事务对data的更改,在a没有提交之前,新的并发事务读取到的数据如果存在于data 中,那么该数据的状态和a事务中的数据是一致的,从而避免了不可重复的读取。但在a事务没有结束之前,b事务可以插入新记录到data所在的表中,那么其 它事务再次用相同的where字句查询时,得到的结果数可能上一次的不一致,也就是幻像数据。它是MySQL的默认隔离级别
  4. serializable: 在数据表上放置了排他锁,以防止在事务完成之前由其他用户更新行或向数据集中插入行, 这是最严格的锁 。它防止了脏读、不可重复读取和幻象数据。


以下是对照表:

隔离级别                                    脏读             不可重复读            幻读
读未提交(read uncommitted)  可能            可能                     可能 
读已提交(read committed)     不可能          可能                     可能 
可重复读(repeatable read)    不可能          不可能                  可能 
可串行化(serializable )           不可能          不可能                   不可能 

 

 

数据库提供了四种事务隔离级别, 不同的隔离级别采用不同的锁类开来实现.

在四种隔离级别中, Serializable的级别最高, Read Uncommited级别最低.

大多数数据库的默认隔离级别为: Read Commited,如Sql Server , Oracle.

少数数据库默认的隔离级别为Repeatable Read, 如MySQL InnoDB存储引擎

即使是最低的级别,也不会出现 第一类 丢失 更新问题 .

  1. 脏读(事务没提交,提前读取) :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中(读取另一个事务尚未提交的修改时 ),这时,另外一个事务也访问这个数据,然后使用了这个数据。
  2. 不可重复读(两次读的不一致) :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改 ,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
  3. 幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入 一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
  4. 第一类更新丢失(回滚丢失 )
      当2个事务更新相同的数据源,如果第一个事务被提交,而另外一个事务却被撤销,那么会连同第一个事务所做的跟新也被撤销。也就是说第一个事务做的跟新丢失了。
  5. 第二类更新丢失(覆盖丢失 )
      第二类更新丢失实在实际应用中经常遇到的并发问题,他和不可重复读本质上是同一类并发问题,通常他被看做不可重复读的特例:当2个或这个多个事务查询同样的记录然后各自基于最初的查询结果更新该行时,会造成第二类丢失更新。因为每个事务都不知道不知道其他事务的存在,最后一个事务对记录做的修改将覆盖其他事务对该记录做的已提交的跟新...

 

补充 : 基于元数据的 Spring 声明性事务 :

Isolation 属性一共支持五种事务设置,具体介绍如下:

l          DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .

l          READ_UNCOMMITTED 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )

l          READ_COMMITTED  会出现不可重复读、幻读问题(锁定正在读取的行)

l          REPEATABLE_READ 会出幻读(锁定所读取的所有行)

l          SERIALIZABLE 保证所有的情况不会发生(锁表)

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics