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

本文引用:https://blog.csdn.net/qq_25827845/article/details/64444896

引用链接:数据库事务和四种隔离级别 推荐 排版内容都很舒适

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

(1)事务的4大特性:(acid)

  • 原子性
  • 一致性
  • 隔离性
  • 持久性

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

  • 丢失更新
  • 脏读
  • 不可重复读
  • 幻读
  • 第一类更新和第二类更新(用于事务A回退 或则提交导致事务的B的操作被覆盖掉)

(3)数据库的锁机制:

  • 共享锁  s 锁 读锁
  • 排他锁  x锁  写锁   
  • 更新锁

 

  • 悲观锁
  • 乐观锁

(4)事务的4大隔离级别:
         隔离级别越高事务越安全 性能开销越大;大多数程序可以考虑设置为read_commited 关键数据由程序锁(乐观、悲观)控制

  • read_uncommited (读未提交)
  • read_commited(读已提交)
  • repeatable_read (可重复读)
  • Serialization   (串行读)

(5)隔离级别实现机制:

        一级封锁协议 (对应 read uncommited)  
       二级封锁协议 (对应 read  commited)
       三级封锁协议 (对应 RR)

   下一篇:事务隔离级别在数据库的具体实现原理

 

 

 

1、事务的4大特性:

事务(Transaction):访问并可能更新数据库中各种数据项的一个程序执行单元(unit),它通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起。当在数据库中更改数据成功时,在事务中更改的数据便会提交,不再改变。否则,事务就取消或者回滚,更改无效。

事务解释:指要做的或所做的事情

事务本质:一系列操作

事务特性事务是恢复和并发控制的基本单位

事务的开始与结束可以由用户显式控制(BEGIN TRANSACTION  COMMIT  ROLLBACK)。如果用户没有显式地定义事务,则由DBMS按缺省规定自动划分事 务。

 

事务的属性

事务必须满足四个属性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),即ACID四种属性。

(1)原子性

一个事务是一个不可分割的整体,为了保证事务的总体目标,事务必须具有原子性,即当数据修改时,要么全都执行,要么全都不执行。即,不允许事务部分地完成,避免了只执行这些操作的一部分而带来的错误。

(2)一致性

一个事务在执行之前和执行之后,数据库数据必须保持一致性。数据库的一致性状态应该满足模式锁指定的约束条件,那么在完整执行该事务后,数据库仍然处于一致性状态。

例如:银行转账,转账前后两个账户金额之和应保持不变。

(3)隔离性

由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据库时数据所处的状态,要么是另一并发事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看中间状态的数据。

例如:对任何一对事务T1和T2,对T1而言,T2要么在T1开始之前已经结束,要么在T1完成之后再开始执行。

(4)持久性

也被称为永久性,事务完成以后,DBMS(数据库管理系统)保证它对数据库中数据的修改是永久性的,当系统或介质发生故障时,该修改也永久保持。持久性一般通过数据库备份与恢复来保证。

注意    严格而言,数据库事务属性都是由数据库管理系统来进行保证的,在整个应用程序的运行过程中,应用程序无须去考虑数据库的ACID实现。

并发控制

(1)DBS(数据库系统)一个明显的特点是多个用户共享数据库资源,尤其是多个用户可以同时存取相同数据。

  • 串行控制:如果事务是顺序执行的,即一个事务完成之后,再开始另一事务。
  • 并行控制:如果DBMS可以同时接受多个事务,并且这些事务在时间上可以重叠执行。

(2)并发控制概述

事务是并发控制的基本单位,保证事务ACID的特性是事务处理的重要任务,而并发操作有可能会破坏其ACID特性。

DBMS并发控制机制的责任:对并发操作进行正确调度,保证事务的隔离更一般,确保数据库的一致性。

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

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

1、丢失更新:

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

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


2、脏读

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

 

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

3、不可重复读


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


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

 

4、幻读

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


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

注意:不可重复读重点是修改,而幻读重点是新增或删除。

很多人容易搞混不可重复读和幻读,确实这两者有些相似。但不可重复读重点在于update和delete,而幻读的重点在于insert。避免不可重复读需要锁行(某一行在select操作时,不允许update与delete)就行,避免幻读则需要锁表。如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,幻读不能通过行锁来避免,需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。所以说不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。

上面两段话有点矛盾;待仔细了解后再来修改。

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):提供严格的事务隔离。要求失去序列化执行,事务只能一个接一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。 以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待 

隔离级别越高,越能保证数据的完整性和统一性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为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锁,所以它不能保证可重复读。
        加上s锁导致其他数据不能对数据x锁,所以可以防止脏读
  
   三级封锁协议 (对应reapetable read )
   三级封锁协议是:一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。  
   三级封锁协议除防止了丢失修改和不读“脏”数据外,还进一步防止了不可重复读。

 

锁和隔离级别的关系

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


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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值