数据库并发机制和事务的隔离级别详解


 (尊重劳动成果,转载请注明出处:http://blog.csdn.net/qq_25827845/article/details/64444896冷血之心的博客)

本文将从以下4个方面来展开:

(1)事务的4大特性:

  • 原子性
  • 一致性
  • 隔离性
  • 持久性
(2)数据库并发操作产生的问题:
  • 丢失更新
  • 脏读
  • 不可重复读
  • 幻读
(3)数据库的锁机制:
  • 共享锁
  • 排他锁
  • 更新锁

  • 悲观锁
  • 乐观锁
(4)事务的4大隔离级别:
  • read_uncommited (读未提交)
  • read_commited(读已提交)
  • repeatable_read (可重复读)
  • Serialization   (串行读)

 

1、事务的4大特性:


      事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。
事务和程序是两个概念。一般地讲,一个程序中包含多个事务。
事务的开始与结束可以由用户显式控制。如果用户没有显式地定义事务,则由DBMS按缺省规定自动划分事 务。

在SQL语言中,定义事务的语句有三条: 
BEGIN TRANSACTION 
COMMIT 
ROLLBACK

事务具有4个属性:原子性、一致性、隔离性、持久性。这四个属性通常称为ACID特性。

  • 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
  • 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
  •  隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
  •  持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

 

2、数据库并发操作产生的问题:

在多个事务并发做数据库操作的时候,如果没有有效的避免机制,就会出现种种问题。大体上有四种问题,归结如下:

1、丢失更新:

如果两个事务都要更新数据库一个字段X,X=100

两个不同事物同时获得相同数据,然后在各自事务中同时修改了该数据,那么先提交的事务更新会被后提交事务的更新给覆盖掉,这种情况事务A的更新就被覆盖掉了、丢失了。


2、脏读

防止一个事务读到另一个事务还没有提交的记录。 如:

 

事务读取了未提交的数据,事务B的回滚,导致了事务A的数据不一致,导致了事务A的脏读 !

3、不可重复读


一个事务在自己没有更新数据库数据的情况,同一个查询操作执行两次或多次的结果应该是一致的;如果不一致,就说明为不可重复读。还是用上面的例子


这种情况事务A多次读取x的结果出现了不一致,即为不可重复读 。

 

4、幻读

事务A读的时候读出了15条记录,事务B在事务A执行的过程中 增加 了1条,事务A再读的时候就变成了 16 条,这种情况就叫做幻影读。


不可重复读说明了做数据库读操作的时候可能会出现的问题。

 

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

 

3、数据库的锁机制:

1、锁的两种分类方式

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

mysql锁机制分为表级锁和行级锁,行级锁包括共享锁、排他锁和更新锁。

 独占锁,简称X锁(Exclusive Lock)
      独占锁锁定的资源只允许进行锁定操作的程序使用,其它任何对它的操作均不会被接受。执行数据更新命令,即INSERT、UPDATE 或DELETE 命令时,SQL Server 会自动使用独占锁。但当对象上有其它锁存在时,无法对其加独占锁。独占锁一直到事务结束才能被释放。

 

 共享锁,简称S锁(Shared Lock)
      共享锁锁定的资源可以被其它用户读取,但其它用户不能修改它。在SELECT 命令执行时,SQL Server 通常会对对象进行共享锁锁定。通常加共享锁的数据页被读取完毕后,共享锁就会立即被释放。


 更新锁(Update Lock)
      更新锁是为了防止死锁而设立的。当SQL Server 准备更新数据时,它首先对数据对象作更新锁锁定,这样数据将不能被修改,但可以读取。等到SQL Server 确定要进行更新数据操作时,它会自动将更新锁换为独占锁。但当对象上有其它锁存在时,无法对其作更新锁锁定。

      对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,对于排他锁大家的理解可能就有些差别,我当初就犯了一个错误,以为排他锁锁住一行数据后,其他事务就不能读取和修改该行数据,其实不是这样的。排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,select语句默认不会加任何锁类型,如果加排他锁可以使用select ...for update语句,加共享锁可以使用select ... lock in share mode语句。所以加过排他锁的数据行在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式查询数据,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制。

 

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

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

 

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

      常见实现方式:在数据表增加version字段,每次事务开始时将取出version字段值,而后在更新数据的同时version增加1(如: update xxx set data=#{data},version=version+1 where version=#{version} ),如没有数据被更新,那么说明数据由其它的事务进行了更新,此时就可以判断当前事务所操作的历史快照数据。

 

4、事务的隔离级别:

 隔离级别:

为了避免数据库事务操作中的问题,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同:

  ◆读未提交(Read Uncommitted):允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。


  ◆读已提交(Read Committed):允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。


  ◆可重复读(Repeatable Read):禁止不可重复读取和脏读取,但是有时可能出现幻影数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。


  ◆序列化(Serializable):提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行。如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
  

       隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、虚读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

 

隔离级别实现机制:


x锁 排他锁 被加锁的对象只能被持有锁的事务读取和修改,其他事务无法在该对象上加其他锁,也不能读取和修改该对象
s锁 共享锁 被加锁的对象可以被持锁事务读取,但是不能被修改,其他事务也可以在上面再加s锁。
 
在运用X锁和S锁对数据对象加锁时,还需要约定一些规则 ,例如何时申请X锁或S锁、持锁时间、何时释放等。称这些规则为封锁协议(Locking Protocol)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议。
  
   一级封锁协议 (对应 read uncommited)  
   一级封锁协议是:事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。
   一级封锁协议可以防止丢失修改,并保证事务T是可恢复的。使用一级封锁协议可以解决丢失修改问题。   
   在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,它不能保证可重复读和不读“脏”数据。
  
   二级封锁协议 (对应read commited) 
   二级封锁协议是:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后方可释放S锁 
   二级封锁协议除防止了丢失修改,还可以进一步防止读“脏”数据。但在二级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读。
  
   三级封锁协议 (对应reapetable read )
   三级封锁协议是:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。  
   三级封锁协议除防止了丢失修改和不读“脏”数据外,还进一步防止了不可重复读。

 

锁和隔离级别的关系

        一般来说,实际开发中,直接操作数据库中各种锁的几率相对比较少,更多的是利用数据库提供的四个隔离级别,未提交读、已提交读、可重复读、可序列化,那隔离级别和锁是什么关系?通俗来说,隔离级别是锁的一个整体打包解决方案,我的理解是隔离封装了锁。


       隔离级别从上到下依次增加,级别越低,引起的问题也就比较多,比如脏读、丢失更新等,但等级越高,也就意味着需要管理更多的锁,无法并行处理,性能方面又受损,因此,我们在设计系统时,只需要根据业务需求选择一种当下适合的隔离级别。一种隔离级别,就有一套利用锁的方案,如此设计,目的就是为了平衡性能和功能。


(多谢前人的总结)

转载摘抄于以下网页:

http://blog.csdn.net/tuolaji8/article/details/48549481

http://blog.sina.com.cn/s/blog_44b84ae20100ak2r.html

http://www.cnblogs.com/houweijian/p/5869243.html

http://www.wujianjun.org/2016/05/27/transaction-isolation-levels/?utm_source=tuicool&utm_medium=referral

http://harisxiong.blog.51cto.com/7513022/1351370

http://www.cnblogs.com/adforce/archive/2011/04/20/2021929.html

http://www.cnblogs.com/boblogsbo/p/5602122.html

 

 

如果对你有帮助,记得点赞哦~欢迎大家关注我的博客,可以进群366533258一起交流学习哦~奋斗






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

温柔狠角色

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值