精华内容
下载资源
问答
  • 数据库并发控制机制
    千次阅读
    2020-07-31 13:45:17

    数据库并发控制机制

       数据库的并发控制机制,顾名思义,是用来控制数据库的并发操作的机制。控制的目的是为了保证数据完整和数据一致性。
       何为数据一致性?在数据库的并发操作中,多个事务同时读取同一份数据,要保证多个事务读取的同一份数据是准确的。
        可能数据的不一致更好理解。例如事务T1、T2同时更新余额为1000余额表, T1减-100元,更新余额为900元,T2减-500元,更新余额为500元;在T2不知道T1已经更新的情况下直接把余额更新为500元,丢失了T1对余额的更新,此时的数据不能反映真实情况,数据变得不一致。并发事务处理过程中,还可能出现事务T1修改了一条记录,事务T2读取该记录后,事务T1被撤销,此时T2读取的数据就就与数据库中的数据不一致,该数据就为“脏”数据。
       因此并发操作中,需要确保事务的隔离性以保证数据的一致性。那么如何确保事务的隔离性呢?接下来我们对DB2,Mysql和ORACLE三种数据库的并发控制机制比较分析。首先我们先看什么是事务。
    

    数据库的数据一致性支持机制:事务日志

    1. 事务:又称为交易,访问数据库系统的可恢复的最小单元。
      1.1事务的ACID
      原子性(Atomicity):事务为一个整体的工作单元,事务对数据库的操作要么全部执行,要么全部取消。
      一致性(Consistency):事务完成时,所有数据都保持一致状态。
      隔离性(Isolation):事务所做的修改必须与其他事务所做的修改隔离。事务查看数据时数据的状态要么为其他事务修改之前要么为其他事务修改之后,不会为中间状态。即多个事务不能同时修改同一份数据。
      持久性(Durability):事务提交后,对数据库所做的修改会永久保存。

    1.2事务的初始化和终止
    事务在可执行的SQL第一次执行时会自动初始化,事务一旦初始化,就必须终止(COMMIT或ROLLBACK)。
    1.2.1关于事务的COMMIT和ROLLBACK
    多数情况下,事务通过执行COMMIT或ROLLBACK终止事务。执行COMMIT语句后,事务初始化后对数据库做出的所有改变都会变成永久的;执行ROLLBACK语句后,事务初始化后对数据库做出的所有改变都会被撤销,数据库返回事务开始之前的状态。
    1.2.2关于不成功的事务的结果
    上面说了当事务被COMMIT或ROLLBACK终止语句后会发生什么,如果事务完成之前系统发生故障,会发生什么?这种情况下,数据库管理器将撤销所有未COMMIT的修改,从而恢复数据的一致性。
    DB2中通过ACTIVE LOG日志文件实现撤销修改。日志文件包含关于事务执行的每个语句的信息,以及事务是否被成功COMMIT或ROLLBACK的信息。
    Mysql和Oracle利用undo log撤销修改。undo log记录了行的修改操作,执行事务中由于某种原因失败,或使用ROLLBACK时,就可以利用undo log将数据恢复到修改之前的样子。
    1.3事务的隔离级别
    事务为什么需要多种可以设置的隔离级别呢?通常,锁可以实现并发操作中事务的隔离,保证数据的一致性。锁提高了并发性能,但会带来潜在的问题:
    脏读:当前事务可以读到另外一个事务中未提交的数据。
    不可重复读:在一个事务内读到的同一条数据是不一样的。
    幻读:事务A在相同条件下第二次读取时读到新插入的数据。
    丢失更新:一个事务的更新操作会被另一个事务的更新操作所覆盖,从而导致数据的不一致。例如:
    (1)事务T1将行记录修改为V1,事务T1未提交。
    (2)事务T2将行记录修改为V2,事务T2未提交。
    (3)事务T1提交。
    (4)事务T2提交。
    在当前数据库的锁机制下不会导致理论意义上的丢失更新问题,但是实际上在所有多用户计算机系统环境下都有可能产生这个问题。例如:
    (1)事务T1查询一行数据,放入本地内存,显示给User1。
    (2)事务T2查询一行数据,放入本地内存,显示给User2。
    (3)User1修改这行记录,更新数据库并提交。
    (4)User2修改这行记录,更新数据库并提交。
    这些问题往往和系统数据库的使用方式和形态有关。而设置事务的隔离级别,就是根据不同的场景来解决以上问题。比如上面所说的丢失更新问题,隔离级别中SELECT…FOR UPDATE即带有更新意图读的时候,步骤(1)(2)都是要上写锁的,避免了丢失更新的问题。下面详解数据库的隔离级别及其加锁方式。

    SQL标准定义的四个隔离级别为:
    READ UNCOMMITTED:未提交读。事务可以看到其他事务所有未提交的数据。读取数据不加锁。
    READ COMMITTED:提交读。事务只可以看到其他事务已经提交的数据。
    REPEATABLE READ:重复度。锁定事务引用的符合检索条件的部分行,其他事务不可修改这些行,但可执行INSERT操作。即可能出现幻读。
    SERIALIZABLE:可串行化。强制的进行排序,在每个读数据行上添加锁,所有事务依次逐个执行,事务之间不会产生干扰。事务提交后释放锁。会导致大量超时现象和锁竞争。
    四种隔离级别会导致的问题:
    | 隔离级别 | 脏读 | 不可重复读 | 幻读 |
    | READ UNCOMMITTED | √ | √ | √ |
    | READ COMMITTED | × | √ | √ |
    | REPEATABLE READ | × | × | √ |
    | SERIALIZABLE | × | × | × |

    DB2中的隔离级别
    CS(Cursor Stability):游标稳定性。逐行锁定数据,该行数据未修改时,锁定解除,继续加锁读取下一行,该行数据有修改时,则该行锁定持续到事务终止。CS的程序不能查看其他程序未COMMIT的更改。
    CS提供了最大的并发性。但同一事务同一游标被处理两次,可能返回不同的结果,即不可重复度;CS程序读取的行上有任何可更新游标时,其他任何应用程序都不能更新或删除该行。
    CS是DB2默认的隔离级别。在需要最大并行性但只能看到其他程序已COMMIT的数据时使用。
    RR(Repeatable Read):可重复读。RR会锁定事务引用的所有行,直到COMMIT。其他程序不能修改该数据,如果一条数据被访问两次,返回相同的结果。
    RR是最高隔离级别,可以最好的保证数据一致性,但是大量锁定数据,会导致并发度大大降低,同时有可能超过系统定义的持有锁数量的限制。
    相当于标准定义隔离级别中的SERIALIZABLE相比,上锁范围一致。
    RS(Read Stability):读稳定性。RS会锁定事务引用的所有行中符合检索条件的部分行。其他程序不可修改,但可执行INSERT操作,所以同一事务中,如果数据被访问两次可能返回新插入的数据,即幻读,但是旧数据不会有改变。
    相比RR,RS锁定数据的数量大大减少,并发度得到提升。比较适合在并发环境下运行,但只适合在同一事物中不会多次发出相同查询,或不要求相同查询获得相同结果的程序,避免发生幻读。
    DB2的RS和标准定义隔离级别中的REPEATABLE READ(重复读)类似,避免了脏读,但是会出现幻读问题。
    UR(Uncommitted Read):未提交读,也就是“脏”读。UR不会加任何锁,可以读数据库中的任何数据,包含已修改但未COMMIT的数据。读的数据可能与真实的数据有一定差距。
    UR级别最常用于只读表上的查询,或者只执行查询且不关心能否读到其他程序未COMMIT的数据时常用。
    UR相当于标准定义隔离级别中的READ UNCOMMITTED(未提交读)。

    Mysql支持标准定义的四种隔离级别,默认的隔离级别为REPEATABLE READ(重复度),但是与标准SQL不同的是,Mysql的InnoDB存储引擎在REPEATABLE READ的隔离级别下,使用Next-Key Lock(锁定一个范围,并锁定记录本身),因此避免幻读的产生。所以说InnoDB存储引擎在REPEATABLE READ的隔离级别下已经能保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE隔离级别。

    Oracle数据库支持 READ COMMITTED(提交读)和SERIALIZABLE这两种事务隔离级别。默认的隔离级别是READ COMMITTED(提交读)。

    2. 锁
    事务隔离级别是并发控制的整体解决方案,其实际上是综合利用各种类型的锁和行版本控制来解决并发问题。
    这里我们主要看数据库中的基本锁。
    2.1 锁的类型
    S-LOCK:共享锁。又叫读锁,当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加多个。
    X-LOCK:排他锁。又叫写锁。SQL INSERT/UPDATE/DELETE语句执行时会上X-LOCK。排他锁只可以加一个,和其他的排他锁共享锁都相斥。
    以上两种类型的锁,DB2,Mysql,Oracle都支持,DB2中还有一种锁U-LOCK。
    U-LOCK:修改锁。CURSOR SELECT 有UPDATE OF 子句时,FETCH时对读出的记录,会上U-LOCK。

    2.2 事务隔离级别中读数据时的锁类型
    如上,数据库在各种隔离级别下,SQL执行 INSERT/UPDATE/DELETE语句时都会上X-LOCK,那么在读数据时如何上锁呢?
    DB2和Mysql在Uncommitted Read隔离级别下,不加任何锁。
    DB2在另外三种CS、RR、RS隔离级别时,SELECT语句,或CURSOR SELECT 无UPDATE OF 子句,FETCH时对读出的记录会上S-LOCK,不同的是,CS在读取下一行数据时就释放上一行的锁,RR、RS在事务提交时才释放锁;SELET…FOR UPDATE对读取的数据都是加U锁,CS在读取下一行数据时就释放上一行的锁,RR、RS在事务提交时才释放锁;INSERT/UPDATE/DELETE语句执行时会上X-LOCK,CS、RR、RS都是在事务提交时才释放X锁,其他事务不能对已锁定的行加任何锁。
    Mysql的InnoDB在隔离级别READ COMMITED 和 REPEATABLE READ(Mysql的默认隔离级别)下SELECT时不上锁,即Mysql中的一致性非锁定读;只有指定SELECT…LOCK IN SHARE MOAD才对记录上S-LOCK,SERIALIZABLE隔离级别下SELECT对记录上S-LOCK;三种隔离级别下,SELET…FOR UPDATE对读取的数据都是加X锁,在Mysql中叫做一致性锁定读。
    Oracle中只支持READ COMMITED和SERIALIZABLE隔离级别。这两种隔离级别下的锁机制和InnoDB一致。Oracle中不需要READ UNCOMMITTED隔离级别,是因为READ UNCOMMITTED主要功能是提高只读时的并发性,而Oracle在READ COMMITED隔离级别下使用一致性非锁定读也有同样的功能。
    2.3 一致性非锁定读
    隔离级别READ COMMITED 和 REPEATABLE READ(Mysql的默认隔离级别)都使用一致性非锁定读, SELECT时不上锁,那么如何保证事务的隔离性呢?这两种隔离级别采用快照数据的方式保证隔离性。读取时对于上了X锁的数据,都会去读取行的一个快照数据。快照数据是指该行的之前版本的数据,通过undo段实现。而undo段用来在事务中回滚数据,因此快照数据本身没有额外的开销。
    READ COMMITED 和 REPEATABLE READ两种隔离级别在读快照数据时的区别是,RC总是读取最新的快照数据,所以可能会发生不可重复读,即第二次读取的数据和第一次不一致;而RR总是读取事务开始时的快照,所以不会发生不可重复度。
    非锁定读机制不会等待行上X锁的释放,极大的提高了数据库的并发性。是InnoDB的默认读取方式。

    3. 小结
    并发控制控制在保证数据一致性的前提下提供最大的并发性,而保证数据一致性的前提就是保证事务的隔离性,事务的隔离性和并发性是成反比的,隔离级别越高,并发性越低。所以程序要视并发性和隔离性的轻重选择隔离级别。

    更多相关内容
  • 数据库并发控制

    2020-09-20 21:23:14
    数据库并发控制单机2PL (2 Phase Locking)OCC (Optimistic Concurrency Control)MVCC (Multi Version Concurrency Control)分布式2PC(Two-phaseCommit)3PC(Three-phase commit)TCCGTS 在数据库中,并发控制是指在多...


    在数据库中,并发控制是指在多个进程同时对数据库进行操作时,如何保证事务的一致性和隔离性的同时,实现最大程度地并发。
    当多个进程同时对数据库进行操作时,会出现3种冲突情形
    读-读,不存在问题
    读-写,可能遇到脏读 ,幻读等
    写-写,可能丢失更新
    要解决冲突,一种办法是是锁,即基于锁的并发控制,比如2PL,这种方式开销比较高,而且无法避免死锁

    单机

    2PL (2 Phase Locking)

    两阶段锁,是一种悲观并发控制,即在一个事务里面,分为加锁(lock)阶段和解锁(unlock)阶段
    第一阶段:获得锁的阶段,称为扩展阶段
    第二阶段:释放锁的阶段,称为收缩阶段

    加锁阶段,主要是用共享锁(读锁)和排它锁(写锁)

    1. 读锁:对数据项加读锁(共享锁),读锁不阻塞其他事务
    2. 写锁:写锁(排他锁)不允许对给定资源进行读和写操作

    优点:

    1. 维护锁开销较小时、数据冲突较少时性能较好

    缺点:

    1. 加锁协议开销大
    2. 为了避免死锁,额外增加死锁检测
    3. 在持有锁的情况下可能会等待I/O操作,会降低系统的并发吞吐量
    4. 第一阶段加锁后影响了其他事务的读写操作,第二阶段释放了数据项上的锁之后,才能运行其他要操作相同数据项的事务
    5. 很多真实的业务应用场景都是高并发. 低竞争的

    在2PL的基础上,可延伸为:
    S2PL(Strict 2PL),在2PL的基础上,写锁保持到事务结束
    SS2PL(Strong 2PL),在2PL的基础上,读写锁都保持到事务结束

    OCC (Optimistic Concurrency Control)

    OCC是在2PL并发控制存在比较多缺点的基础上提出,是一种乐观并发控制,用来解决写-写冲突的无锁并发控制,OCC认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试
    实现分为三阶段:

    1. Read Phase, 对于读,放到Read Set,对于写,把写记到临时副本,放到Write Set。因为写是写到临时区的,属于未提交结果,其它事务读不到
    2. Validation Phase,重扫Read Set,Write Set,检验数据是否满足Isolation Level,如果满足则Commit,否则Abort
    3. WritePhase,或者叫做Commit Phase,把临时副本区的数据更新到数据库中,完成事务提交

    优点:

    1. 不需要维护锁
    2. 数据不冲突时吞吐量比较高

    缺点:

    1. 事物回滚比较高

    OCC适用于低数据争用,写冲突比较少的环境。

    扩展阅读 理论研究-BOCC/FOCC

    BOCC-检查待验证事务的读集是否与事务读取阶段完成的事务的写集有交集
    FOCC-检查待验证事务的写集是否与当前活跃事务的读集有交集

    MVCC (Multi Version Concurrency Control)

    多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,简单来说是使用版本号控制数据,为每个修改保存一个版本号,以对表数据的读写互不阻塞,增大并发量。
    读操作不用阻塞写操作,写操作不用阻塞读操作,避免了脏读和不可重复读

    InnoDB中MVCC的实现是通过在每一行记录后面保存两个隐藏的列来实现: DATA_TRX_ID 、 DATA_ROLL_PTR
    每开始一个新事务,版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行纪录的版本号进行比较

    ColumnLengthremark
    DATA_TRX_ID6记录最近更新这条行记录的事务ID
    DATA_ROLL_PTR7回滚段(rollback segment)的指针,InnoDB便是通过这个指针找到之前版本的数据
    DB_ROW_ID6行标识(隐藏自增ID),如果表没有主键,InnoDB会自动生成一个隐藏主键

    优点:
    1、读写互不阻塞,回滚率也比OCC好

    1. MVCC手段只适用于Msyql隔离级别中的读已提交(Read committed)和可重复读(Repeatable Read)
    2. Read uncimmitted存在脏读,即能读到未提交事务的数据行,所以不适用MVCC,原因是MVCC的创建版本和删除版本只要在事务提交后才会产生
    3. Seriallizable会对所涉及到的表数据加锁,不存在行的版本控制问题

    多版本并发控制可以结合基于锁的并发控制来解决写-写冲突,即MVCC+2PL
    也可以结合乐观并发控制来解决写-写冲突,即MVCC+OCC。

    Transactional Information Systems
    图片

    分布式

    上面几种并发控制都是基于单机,在分布式事务系统架构下的并发控制又有以下几种并发控制

    2PC(Two-phaseCommit)

    二阶段提交(Two-phaseCommit),基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。二阶段提交也被称为是一种协议(Protocol))
    第一阶段:准备阶段(投票阶段)

    1. 协调者向所有的参与者发送是否可以执行提交操作,并开始等待各参与者的响应
    2. 各个参与者节点执行本地事务操作,但在执行完成后并不会真正提交数据库本地事务,而是先向协调者发送确认信息
    3. 各参与者向协调者反馈各事务操作结果的响应

    第二阶段:提交阶段(执行阶段)

    1. 成功,只有所有参与者反馈成功才会执行提交,释放所有事务处理过程中使用的锁资源
    2. 失败/超时,直接给每个参与者发送回滚(Rollback)消息
    协调者
    应用A
    应用B
    应用N..
    数据库A
    数据库B
    数据库N..
    确认
    成功
    失败/超时
    commint
    rollback

    缺点:

    1. 同步阻塞问题
      无论是在第一阶段的过程中,还是在第二阶段,所有参与节点都是事务阻塞型的,只有当所有节点准备完毕,协调者才会通知进行全局提交

    2. 单点故障
      当协调者发生故障,所有的参与者会一直阻塞下去

    3PC(Three-phase commit)

    三阶段提交(Three-phase commit),也叫三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本,三阶段提交协议(3PC)主要是为了解决两阶段提交协议的阻塞问题
    待完善

    LCN(Lock Control Notify)

    LCN 一种基于java代理协调技术的分布式事务系统,并不生产事务,LCN 只是本地事务的协调工。LCN模式是通过代理Connection的方式实现对本地事务的操作,然后在由TxManager统一协调控制事务
    优点:

    1. 兼容SpringCloud、Dubbo
    2. 基于切面的强一致性事务框架
    3. 高可用,使用简单,低依赖,代码完全开源
    4. 事务补偿机制,服务故障或挂机再启动时可恢复事务

    主要模式:

    TCC(Try、Confirm、Cancel)

    TCC方案其实是2PC的一种改进。其将整个业务逻辑的每个分支显式的分成了Try、Confirm、Cancel三个操作
    Try:完成业务的准备工作,confirm:完成业务的提交,cancel:完成事务的回滚
    github地址
    LCN原理

    GTS(Global Transaction Service)

    全局事务服务(Global Transaction Service,简称 GTS)是阿里推出的分布式事务处理方案,将分布式事务问题从业务中剥离出来,作为一个独立的技术切面来单独管理,以服务的形式给构建

    消息中间件

    消息一致性方案是将本地操作和发送消息放在一个事务中,保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息,收到消息后执行相应操作

    待完善

    by: 梁振波

    展开全文
  • 数据库并发控制详解

    千次阅读 2019-04-16 11:05:20
    1. 什么是并发控制数据库是一个共享资源,可以提供多个用户使用。这些用户程序可以一个一个地串行执行,每个时刻只有一个用户程序运行,执行对数据库的存取,其他用户程序必须等到这个用户程序结束以后方能对...

    概述

    1. 什么是并发控制?

    数据库是一个共享资源,可以提供多个用户使用。这些用户程序可以一个一个地串行执行,每个时刻只有一个用户程序运行,执行对数据库的存取,其他用户程序必须等到这个用户程序结束以后方能对数据库存取。但是如果一个用户程序涉及大量数据的输入/输出交换,则数据库系统的大部分时间处于闲置状态。因此,为了充分利用数据库资源,发挥数据库共享资源的特点,应该允许多个用户并行地存取数据库。但这样就会产生多个用户程序并发存取同一数据的情况,若对并发操作不加控制就可能会存取和存储不正确的数据,破坏数据库的一致性,所以数据库管理系统必须提供并发控制机制。并发控制机制的好坏是衡量一个数据库管理系统性能的重要标志之一。

    2. 为什么要进行并发控制?

    数据库是共享资源,通常有许多个事务同时在运行。当多个事务并发地存取数据库时就会产生同时读取和/或修改同一数据的情况。若对并发操作不加控制就可能会存取和存储不正确的数据,破坏数据库的一致性。所以数据库管理系统必须提供并发控制机制。
    3. 并发控制概述

    并发控制的单位――事务

    事务是数据库的逻辑工作单位,它是用户定义的一组操作序列。一个事务可以是一组SQL语句、一条SQL语句或整个程序。

    事务的开始和结束都可以由用户显示的控制,如果用户没有显式地定义事务,则由数据库系统按缺省规定自动划分事务。

    封锁

    封锁是事项并发控制的一个非常重要的技术。所谓封锁就是事务T在对某个数据对象,例如,在标、记录等操作之前,先向系统发出请求,对其加锁。加锁后事务T就对数据库对象有了一定的控制,在事务T释放它的锁之前,其他事务不能更新此数据对象。
    基本的封锁类型有两种:排它锁( Exclusive Locks ,简称 x 锁)和共享锁 ( Share Locks,简称 S 锁)。排它锁又称为写锁。若事务 T 对数据对象 A 加上 X 锁,则只允许 T 读取和修改 A ,其他任何事务都不能再对 A 加任何类型的锁,直到 T 释放 A 上的锁。这就保证了其他事务在 T 释放 A 上的锁之前不能再读取和修改 A 。共享锁又称为读锁。若事务 T 对数据对象 A 加上 S 锁,则事务 T 可以读 A但不能修改 A ,其他事务只能再对 A 加 S 锁,而不能加 X 锁,直到 T 释放 A 上的 S 锁。这就保证了其他事务可以读 A ,但在 T 释放 A 上的 S 锁之前不能对 A 做任何修改。

    封锁的粒度
    X锁和S锁都是加在某一个数据对象上的。封锁的对象可以是逻辑单元,也可以是物理单元。例如,在关系数据库中,封锁对象可以是属性值、属性值集合、元组、关系、索引项、整个索引、整个数据库等逻辑单元;也可以是页(数据页或索引页)、块等物理单元。封锁对象可以很大,比如对整个数据库加锁,也可以很小,比如只对某个属性值加锁。封锁对象的大小称为封锁的粒度(granularity)。

    封锁粒度与系统的并发度和并发控制的开销密切相关。封锁的粒度越大,系统中能够被封锁的对象就越小,并发度也就越小,但同时系统开销也越小;相反,封锁的粒度越小,并发度越高,但系统开销也就越大。
    因此,如果在一个系统中同时存在不同大小的封锁单元供不同的事务选择使用是比较理想的。而选择封锁粒度时必须同时考虑封锁机构和并发度两个因素,对系统开销与并发度进行权衡,以求得最优的效果。一般说来,需要处理大量元组的用户事务可以以关系为封锁单元;需要处理多个关系的大量元组的用户事务可以以数据库为封锁单位;而对于一个处理少量元组的用户事务,可以以元组为封锁单位以提高并发度。

    封锁协议
    封锁的目的是为了保证能够正确地调度并发操作。为此,在运用X锁和S锁这两种基本封锁,对一定粒度的数据对象加锁时,还需要约定一些规则,例如,应何时申请X锁或S锁、持锁时间、何时释放等。我们称这些规则为封锁协议(locking protocol)。对封锁方式规定不同的规则,就形成了各种不同的封锁协议,它们分别在不同的程度上为并发操作的正确调度提供一定的保证。本节介绍保证数据一致性的三级封锁协议和保证并行调度可串行性的两段锁协议,下一节将介绍避免死锁的封锁协议。

    保证数据一致性的封锁协议――三级封锁协议
    ① 1级封锁协议

    1级封锁协议的内容是:事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。事务结束包括正常结束(commit)和非正常结束(rollback)。
    1级封锁协议可以防止丢失或覆盖更新,并保证事务T是可以恢复的。
    在1级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的,所以它不能保证可重复读和脏读。

    ② 2级封锁协议

    2级封锁协议的内容是:1级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后即可释放S锁。
    2级封锁协议除防止了丢失或覆盖更新,还可进一步防止脏读。
    在2级封锁协议中,由于读完数据后即可释放S锁,所以它不能保证可重复读。

    ③ 3级封锁协议

    3级封锁协议的内容是:1级封锁协议加上事务T在读取数据之前必须先对其加S锁,直到事务结束才释放。
    3级封锁协议除防止丢失或覆盖更新和不脏读数据外,还进一步防止了不可重复读和幻想读。

    上述三级协议的主要区别在于什么操作需要申请封锁以及何时释放锁(即持锁时间)。

    死锁和活锁

    在这里插入图片描述
    如果事务 Tl 封锁了数据 R ,事务几又请求封锁 R ,于是几等待。几也请求封锁 R ,当 Tl 释放了 R 上的封锁之后系统首先批准了几的请求,几仍然等待。然后几又请求封锁 R ,当几释放了 R 上的封锁之后系统又批准了几的请求 … … 几有可能永远等待,这就是活锁的情形。活锁的含义是该等待事务等待时间太长,似乎被锁住了,实际上可能被激活。如果事务 Tl 封锁了数据 Rl ,几封锁了数据凡,然后 Tl 又请求封锁几,因几已封锁了几,于是 Tl 等待几释放几上的锁。接着几又申请封锁 Rl ,因 Tl 已封锁了 Rl ,几也只能等待 Tl 释放 Rl 上的锁。这样就出现了 Tl 在等待几,而几又在等待 T }的局面, T }和几两个事务永远不能结束,形成死锁。

    活锁产生的原因:当一系列封锁不能按照其先后顺序执行时,就可能导致一些事务无限期等待某个封锁,从而导致活锁。避免活锁的简单方法是采用先来先服务的策略。当多个事务请求封锁同一数据对象时,封锁子系统按请求封锁的先后次序对事务排队,数据对象上的锁一旦释放就批准申请队列中第一个事务获得锁。

    并发控制—锁和MVCC

    • 悲观并发控制
      控制不同的事务对同一份数据的获取是保证数据库的一致性的最根本方法,如果我们能够让事务在同一时间对同一资源有着独占的能力,那么就可以保证操作同一资源的不同事务不会相互影响。
      最简单的、应用最广的方法就是使用锁来解决,当事务需要对资源进行操作时需要先获得资源对应的锁,保证其他事务不会访问该资源后,在对资源进行各种操作;在悲观并发控制中,数据库程序对于数据被修改持悲观的态度,在数据处理的过程中都会被锁定,以此来解决竞争的问题。
      读写锁
      为了最大化数据库事务的并发能力,数据库中的锁被设计为两种模式,分别是共享锁和互斥锁。当一个事务获得共享锁之后,它只可以进行读操作,所以共享锁也叫读锁;而当一个事务获得一行数据的互斥锁时,就可以对该行数据进行读和写操作,所以互斥锁也叫写锁。

    共享锁和互斥锁除了限制事务能够执行的读写操作之外,它们之间还有『共享』和『互斥』的关系,也就是多个事务可以同时获得某一行数据的共享锁,但是互斥锁与共享锁和其他的互斥锁并不兼容,我们可以很自然地理解这么设计的原因:多个事务同时写入同一数据难免会发生各种诡异的问题。

    如果当前事务没有办法获取该行数据对应的锁时就会陷入等待的状态,直到其他事务将当前数据对应的锁释放才可以获得锁并执行相应的操作。

    两阶段锁协议
    两阶段锁协议(2PL)是一种能够保证事务可串行化的协议,它将事务的获取锁和释放锁划分成了增长(Growing)和缩减(Shrinking)两个不同的阶段。

    在增长阶段,一个事务可以获得锁但是不能释放锁;而在缩减阶段事务只可以释放锁,并不能获得新的锁,如果只看 2PL 的定义,那么到这里就已经介绍完了,但是它还有两个变种:

    Strict 2PL:事务持有的互斥锁必须在提交后再释放;
    Rigorous 2PL:事务持有的所有锁必须在提交后释放;

    虽然锁的使用能够为我们解决不同事务之间由于并发执行造成的问题,但是两阶段锁的使用却引入了另一个严重的问题,死锁;不同的事务等待对方已经锁定的资源就会造成死锁

    两个事务在刚开始时分别获取了 draven 和 beacon 资源上面的锁,然后再请求对方已经获得的锁时就会发生死锁,双方都没有办法等到锁的释放,如果没有死锁的处理机制就会无限等待下去,两个事务都没有办法完成。

    死锁的处理

    • 预防死锁
      有两种方式可以帮助我们预防死锁的出现,一种是保证事务之间的等待不会出现环,也就是事务之间的等待图应该是一张有向无环图,没有循环等待的情况或者保证一个事务中想要获得的所有资源都在事务开始时以原子的方式被锁定,所有的资源要么被锁定要么都不被锁定。

    但是这种方式有两个问题,在事务一开始时很难判断哪些资源是需要锁定的,同时因为一些很晚才会用到的数据被提前锁定,数据的利用率与事务的并发率也非常的低。一种解决的办法就是按照一定的顺序为所有的数据行加锁,同时与 2PL 协议结合,在加锁阶段保证所有的数据行都是从小到大依次进行加锁的,不过这种方式依然需要事务提前知道将要加锁的数据集。

    另一种预防死锁的方法就是使用抢占加事务回滚的方式预防死锁,当事务开始执行时会先获得一个时间戳,数据库程序会根据事务的时间戳决定事务应该等待还是回滚,在这时也有两种机制供我们选择,一种是 wait-die 机制:
    当执行事务的时间戳小于另一事务时,即事务 A 先于 B 开始,那么它就会等待另一个事务释放对应资源的锁,否则就会保持当前的时间戳并回滚。
    另一种机制叫做 wound-wait,这是一种抢占的解决方案,它和 wait-die 机制的结果完全相反,当前事务如果先于另一事务执行并请求了另一事务的资源,那么另一事务会立刻回滚,将资源让给先执行的事务,否则就会等待其他事务释放资源:
    两种方法都会造成不必要的事务回滚,由此会带来一定的性能损失,更简单的解决死锁的方式就是使用超时时间,但是超时时间的设定是需要仔细考虑的,否则会造成耗时较长的事务无法正常执行,或者无法及时发现需要解决的死锁,所以它的使用还是有一定的局限性。

    锁的粒度

    当我们拥有了不同粒度的锁之后,如果某个事务想要锁定整个数据库或者整张表时只需要简单的锁住对应的节点就会在当前节点加上显示(explicit)锁,在所有的子节点上加隐式(implicit)锁;虽然这种不同粒度的锁能够解决父节点被加锁时,子节点不能被加锁的问题,但是我们没有办法在子节点被加锁时,立刻确定父节点不能被加锁。

    在这时我们就需要引入意向锁来解决这个问题了,当需要给子节点加锁时,先给所有的父节点加对应的意向锁,意向锁之间是完全不会互斥的,只是用来帮助父节点快速判断是否可以对该节点进行加锁。

    乐观并发控制

    乐观并发控制也叫乐观锁,但是它并不是真正的锁,很多人都会误以为乐观锁是一种真正的锁,然而它只是一种并发控制的思想。

    • 基于时间戳的协议
      锁协议按照不同事务对同一数据项请求的时间依次执行,因为后面执行的事务想要获取的数据已将被前面的事务加锁,只能等待锁的释放,所以基于锁的协议执行事务的顺序与获得锁的顺序有关。在这里想要介绍的基于时间戳的协议能够在事务执行之前先决定事务的执行顺序。

    每一个事务都会具有一个全局唯一的时间戳,它即可以使用系统的时钟时间,也可以使用计数器,只要能够保证所有的时间戳都是唯一并且是随时间递增的就可以。

    基于时间戳的协议能够保证事务并行执行的顺序与事务按照时间戳串行执行的效果完全相同;每一个数据项都有两个时间戳,读时间戳和写时间戳,分别代表了当前成功执行对应操作的事务的时间戳。

    该协议能够保证所有冲突的读写操作都能按照时间戳的大小串行执行,在执行对应的操作时不需要关注其他的事务只需要关心数据项对应时间戳的值就可以了:

    无论是读操作还是写操作都会从左到右依次比较读写时间戳的值,如果小于当前值就会直接被拒绝然后回滚,数据库系统会给回滚的事务添加一个新的时间戳并重新执行这个事务。

    • 基于验证的协议

    乐观并发控制其实本质上就是基于验证的协议,因为在多数的应用中只读的事务占了绝大多数,事务之间因为写操作造成冲突的可能非常小,也就是说大多数的事务在不需要并发控制机制也能运行的非常好,也可以保证数据库的一致性;而并发控制机制其实向整个数据库系统添加了很多的开销,我们其实可以通过别的策略降低这部分开销。

    而验证协议就是我们找到的解决办法,它根据事务的只读或者更新将所有事务的执行分为两到三个阶段:

    在读阶段,数据库会执行事务中的全部读操作和写操作,并将所有写后的值存入临时变量中,并不会真正更新数据库中的内容;在这时候会进入下一个阶段,数据库程序会检查当前的改动是否合法,也就是是否有其他事务在 RAED PHASE 期间更新了数据,如果通过测试那么直接就进入 WRITE PHASE 将所有存在临时变量中的改动全部写入数据库,没有通过测试的事务会直接被终止。

    为了保证乐观并发控制能够正常运行,我们需要知道一个事务不同阶段的发生时间,包括事务开始时间、验证阶段的开始时间以及写阶段的结束时间;通过这三个时间戳,我们可以保证任意冲突的事务不会同时写入数据库,一旦由一个事务完成了验证阶段就会立即写入,其他读取了相同数据的事务就会回滚重新执行。

    作为乐观的并发控制机制,它会假定所有的事务在最终都会通过验证阶段并且执行成功,而锁机制和基于时间戳排序的协议是悲观的,因为它们会在发生冲突时强制事务进行等待或者回滚,哪怕有不需要锁也能够保证事务之间不会冲突的可能。

    多版本并发控制

    MySQL 中实现的多版本两阶段锁协议(Multiversion 2PL)将 MVCC 和 2PL 的优点结合了起来,每一个版本的数据行都具有一个唯一的时间戳,当有读事务请求时,数据库程序会直接从多个版本的数据项中具有最大时间戳的返回。

    MySQL 在InnoDB引擎下有当前读和快照读两种模式。
    1 当前读即加锁读,读取记录的最新版本号,会加锁保证其他并发事物不能修改当前记录,直至释放锁。插入/更新/删除操作默认使用当前读,显示的为select语句加lock in share mode或for update的查询也采用当前读模式。

    2 快照读:不加锁,读取记录的快照版本,而非最新版本,使用MVCC机制,最大的好处是读取不需要加锁,读写不冲突,用于读操作多于写操作的应用,因此在不显示加[lock in share mode]/[for update]的select语句,即普通的一条select语句默认都是使用快照读MVCC实现模式。所以楼主的为了让大家明白所做的演示操作,既有当前读也有快照读……

    • MVCC实现
      MVCC是通过保存数据在某个时间点的快照来实现的. 不同存储引擎的MVCC. 不同存储引擎的MVCC实现是不同的,典型的有乐观并发控制和悲观并发控制.
      InnoDB的MVCC,是通过在每行记录后面保存两个隐藏的列来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的ID),没开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的ID.

    INSERT
    InnoDB为新插入的每一行保存当前系统版本号作为版本号.

    SELECT
    InnoDB会根据以下两个条件检查每行记录:
    a.InnoDB只会查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.
    b.行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除.
    只有a,b同时满足的记录,才能返回作为查询结果.

    DELETE
    InnoDB会为删除的每一行保存当前系统的版本号(事务的ID)作为删除标识.

    UPDATE
    InnoDB执行UPDATE,实际上是新插入了一行记录,并保存其创建时间为当前事务的ID,同时保存当前事务ID到要UPDATE的行的删除时间.

    展开全文
  • 数据库并发控制及SQLSERVER的并发控制机制.ppt
  • 一篇讲透如何理解数据库并发控制

    千次阅读 2020-04-22 22:07:14
    1.数据库并发控制的作用 1.1 事务的概念 在介绍并发控制前,首先需要了解事务。数据库提供了增删改查等几种基础操作,用户可以灵活地组合这几种操作,实现复杂的语义。在很多场景下,用户希望一组操作可以做为一个...

    作者:旺德,阿里云数据库高级开发工程师

    1.数据库并发控制的作用

    1.1 事务的概念

    在介绍并发控制前,首先需要了解事务。数据库提供了增删改查等几种基础操作,用户可以灵活地组合这几种操作,实现复杂的语义。在很多场景下,用户希望一组操作可以做为一个整体一起生效,这就是事务。事务是数据库状态变更的基本单元,包含一个或多个操作(例如多条SQL语句)。经典的转账事务,就包括三个操作:(1)检查A账户余额是否足够。(2)如果足够,从A扣减100块。(3)B账户增加100块。

    事务有个基本特性:这一组操作要么一起生效,要么都不生效,事务执行过程中如遇错误,已经执行的操作要全部撤回,这就是事务的原子性。如果失败发生后,部分生效的事务无法撤回,那数据库就进入了不一致状态,与真实世界的事实相左。例如转账事务从A账户扣款100块后失败了,B账户还未增加款项,如果A账户扣款操作未撤回,这个世界就莫名奇妙丢失了100块。原子性可以通过记日志(更改前的值)来实现,还有一些数据库将事务操作缓存在本地,如遇失败,直接丢弃缓存里的操作。

    事务只要提交了,它的结果就不能改变了,即使遇到系统宕机,重启后数据库的状态与宕机前一致,这就是事务的持久性。数据只要存储非易失存储介质,宕机就不会导致数据丢失。因此数据库可以采用以下方法来保证持久性:(1)事务完成前,所有的更改都保证存储到磁盘上了。或(2)提交完成前,事务的更改信息,以日志的形式存储在磁盘,重启过程根据日志恢复出数据库系统的内存状态。一般而言,数据库会选择方法(2),原因留给读者思考。

    数据库为了提高资源利用率和事务执行效率、降低响应时间,允许事务并发执行。但是多个事务同时操作同一对象,必然存在冲突,事务的中间状态可能暴露给其它事务,导致一些事务依据其它事务中间状态,把错误的值写到数据库里。需要提供一种机制,保证事务执行不受并发事务的影响,让用户感觉,当前仿佛只有自己发起的事务在执行,这就是隔离性。隔离性让用户可以专注于单个事务的逻辑,不用考虑并发执行的影响。数据库通过并发控制机制保证隔离性。由于隔离性对事务的执行顺序要求较高,很多数据库提供了不同选项,用户可以牺牲一部分隔离性,提升系统性能。这些不同的选项就是事务隔离级别。

    数据库反映的是真实世界,真实世界有很多限制,例如:账户之间无论怎么转账,总额不会变等现实约束;年龄不能为负值,性别最多只能有男、女、跨性别者三种选项等完整性约束。事务执行,不能打破这些约束,保证事务从一个正确的状态转移到另一个正确的状态,这就是一致性。不同与前三种性质完全由数据库实现保证,一致性既依赖于数据库实现(原子性、持久性、隔离性也是为了保证一致性),也依赖于应用端编写的事务逻辑。

    1.2 事务并发控制如何保证隔离性

    为了保证隔离性,一种方式是所有事务串行执行,让事务之间不互相干扰。但是串行执行效率非常低,为了增大吞吐,减小响应时间,数据库通常允许多个事务同时执行。因此并发控制模块需要保证:事务并发执行的效果,与事务串行执行的效果完全相同(serializability),以达到隔离性的要求。

    为了方便描述并发控制如何保证隔离性,我们简化事务模型。事务是由一个或多个操作组成,所有的操作最终都可以拆分为一系列读和写。一批同时发生的事务,所有读、写的一种执行顺序,被定义为一个schedule,例如:

    T1、T2同时执行,一个可能的schedule: T1.read(A),T2.read(B),T1.write(A),T1.read(B),T2.write(A)
    如果并发事务执行的schedule效果与串行执行的schedule(serial schedule)等价,就可以满足serializability。一个schedule不断调换读写操作的顺序,总会变成一个serializable schedule,但是有的调换可能导致事务执行的结果不一样。一个schedule中,相邻的两个操作调换位置导致事务结果变化,那么这两个操作就是冲突的。冲突需要同时满足以下条件:

    1.这两个操作来自不同事务
    2.至少有一个是写操作
    3.操作对象相同

    因此常见的冲突包括:
    • 读写冲突。事务先A读取某行数据、事务B后修改该行数据,和事务B先修改某行事务、事务A后读该行记录两种schedule。事务A读到的结果不同。这种冲突可能会导致不可重复读异象和脏读异象。

    • 写读冲突。与读写冲突产生的原因相同。这种冲突可能会导致脏读异象。
    • 写写冲突。两个操作先后写一个对象,后一个操作的结果决定了写入的最终结果。这种冲突可能会导致更新丢失异象。

    数据库只要保证,并发事务的schedule,保持冲突操作的执行顺序不变,只调换不冲突的操作,可以成为serial schedule,就可以认为它们等价。这种等价判断方式叫做conflict equivalent:两个schedule的冲突操作顺序相同。

    例如下图的例子,T1 write(A)与T3 read(A)冲突,且T1先于T3发生。T1 read(B)和 T2 write(B)冲突,且T2先于T1,因此左图事务执行的schedule,与T2,T1,T3串行执行的serial schedule(右图) 等价。左图的执行顺序满足conflict serializablity。

    1.png
    2.png

    再分析一个反例:T1 read(A)与T2 write(A)冲突且T1先于T2,T2 write(A)与T2 write(A)冲突且T2先于T1。下图这个个schedule无法与任何一个serial schedule等价,是一个不满足conflict serializablity的执行顺序,会造成更新丢失的异象。
    3.png

    总体来说,serializability是比较严格的要求,为了提高数据库系统的并发性能,很多用户愿意去降低隔离性的要求以寻求更好的性能。数据库系统往往会实现多种隔离级别,供用户灵活选择,关于事务隔离级别,可以参看这篇文章。

    并发控制的要求清楚了,如何实现呢?后文将依据冲突检测的乐观程度,一一介绍并发控制常见的实现方法。

    2.基于两阶段锁的并发控制

    2.1 2PL

    既然要保证操作按正确的顺序执行,最容易想到的方法就是加锁保护访问对象。数据库系统的锁管理器模块,专门负责给访问对象加锁和释放锁,保证只有持有锁的事务,才能操作相应的对象。锁可以分为两类:S-Lock和X-Lock,S-Lock是读请求使用的共享锁,X-Lock是写请求使用的排他锁。它们的兼容性如下:操作同一个对象,只有两个读请求相互兼容,可以同时执行,读写和写写操作都会因为锁冲突而串行执行。
    4.png

    2PL(Two-phase locking)是数据库最常见的基于锁的并发控制协议,顾名思义,它包含两个阶段:

    • 阶段一:Growing,事务向锁管理器请求它需要的所有锁(存在加锁失败的可能)。

    • 阶段二:Shrinking,事务释放Growing阶段获取的锁,不允许再请求新锁。

    为什么加锁和放锁要泾渭分明地分为两个阶段呢?
    2PL并发控制目的是为了达到serializable,如果并发控制不事先将所有需要的锁申请好,而是释放锁后,还允许再次申请锁,可能出现事务内两次操作同一对象之间,其它事务修改这一对象(如下图所示),进而无法达到conflict serializable,出现不一致的现象(下面的例子是lost update)。
    5.png

    2PL可以保证conflict serializability,因为事务必须拿到所有需要的锁才能执行。例如正在执行的事务A与事务B冲突,事务B要么已经执行完,要么还在等待。因此那些冲突操作的执行顺序,与BA或AB串行执行时冲突操作执行顺序一致。

    所以,数据库只要采用2PL就能保证一致性和隔离性了吗?来看一下这个例子:
    6.png

    以上执行顺序是符合2PL的,但T2读到了未提交的数据。如果此时T1回滚,则会引发级联回滚(T1的更改,不能被任何事务看到)。因此,数据库往往使用的是加强版的S(trong)S(trict)2PL,它相较于2PL有一点不同:shrinking阶段,只能在事务结束后再释放锁,完全杜绝了事务未提交的数据被读到。

    2.2 死锁处理

    并发事务加锁放锁必然绕不开一个问题--死锁:事务1持有A锁等B锁,事务2持有B锁等A锁。目前解决死锁问题有两种方案:

    • Deadlock Detection:
    数据库系统根据waits-for图记录事务的等待关系,其中点代表事务,有向边代表事务在等待另一个事务放锁。当waits-for图出现环时,代表死锁出现了。系统后台会定时检测waits-for图,如果发现环,则需要选择一个合适的事务abort。
    7.png

    • Deadlock Prevention:
    当事务去请求一个已经被持有的锁时,数据库系统为防止死锁,杀死其中一个事务(一般持续越久的事务,保留的优先级越高)。这种防患于未然的方法不需要waits-for图,但提高了事务被杀死的比率。

    2.3 意向锁

    如果只有行锁,那么事务要更新一亿条记录,需要获取一亿个行锁,将占用大量的内存资源。我们知道锁是用来保护数据库内部访问对象的,这些对象根据大小可能是:属性(Attribute)、记录(Tuple)、页面(Page)、表(Table),相应的锁可分为行锁、页面锁、表锁(没人实现属性锁,对于OLTP数据库,最小的操作单元是行)。对于事务来讲,获得最少量的锁当然是最好的,比如更新一亿条记录,或许加一个表锁就足够了。

    层次越高的锁(如表锁),可以有效减少对资源的占用,显著减少锁检查的次数,但会严重限制并发。层次越低的锁(如行锁),有利于并发执行,但在事务请求对象多的情况下,需要大量的锁检查。数据库系统为了解决高层次锁限制并发的问题,引入了意向(Intention)锁的概念:

    • Intention-Shared (IS):表明其内部一个或多个对象被S-Lock保护,例如某表加IS,表中至少一行被S-Lock保护。

    • Intention-Exclusive (IX):表明其内部一个或多个对象被X-Lock保护。例如某表加IX,表中至少一行被X-Lock保护。

    • Shared+Intention-Exclusive (SIX):表明内部至少一个对象被X-Lock保护,并且自身被S-Lock保护。例如某个操作要全表扫描,并更改表中几行,可以给表加SIX。读者可以思考一下,为啥没有XIX或XIS

    意向锁和普通锁的兼容关系如下所示:
    8.png

    意向锁的好处在于:当表加了IX,意味着表中有行正在修改。

    (1)这时对表发起DDL操作,需要请求表的X锁,那么看到表持有IX就直接等待了,而不用逐个检查表内的行是否持有行锁,有效减少了检查开销。

    (2)这时有别的读写事务过来,由于表加的是IX而非X,并不会阻止对行的读写请求(先在表上加IX,再去记录上加S/X),事务如果没有涉及已经加了X锁的行,则可以正常执行,增大了系统的并发度。

    3.基于Timing Order(T/O)的并发控制

    为每个事务分配timestamp,并以此决定事务执行顺序。当事务1的timestamp小于事务2时,数据库系统要保证事务1先于事务2执行。timestamp分配的方式包括:
    (1)物理时钟;
    (2)逻辑时钟;
    (2)混合时钟。

    3.1 Basic T/O

    基于T/O的并发控制,读写不需加锁, 每行记录都标记了最后修改和读取它的事务的timestamp。当事务的timestamp小于记录的timestamp时(不能读到”未来的”数据),需要abort后重新执行。假设记录X上标记了读写两个timestamp:WTS(X)和RTS(X),事务的timestamp为TTS,可见性判断如下:

    读:
    • TTS < WTS(X):该对象对该事务不可见,abort事务,取一个新timestamp重新开始。
    • TTS > WTS(X):该对象对事务可见,更新RTS(X) = max(TTS,RTS(X))。为了满足repeatable read,事务复制X的值。
    • 为了防止读到脏数据,可以在记录上做特殊标记,读请求需等待事务提交后再去读。

    写:
    • TTS < WTS(X) || TTS < RTS(X):abort事务,重新开始。
    • TTS > WTS(X) && TTS > RTS(X): 事务更新X,WTS(X) = TTS。

    这里之所以要求TTS > RTS(X),是为了防止如下情况:读请求的时间戳为rts,已经读过X,时间戳设为RTS(X)=rts,如果新事务的TTS < RTS(X),并且更新成功,则rts读请求再来读一次就看到新的更改了,违反了repeatable read,因此这是为了避免读写冲突。记录上存储了最后的读写时间,可以保证conflict serializable

    这种方式也能避免write skew,例如:初始状态,X和Y两条记录,X=-3,Y=5,X+Y >0,RTS(X)=RTS(Y)=WTS(X)=WTS(Y)=0。事务T1的时间戳为TTS1=1,事务T2的时间戳TTS2=2。
    9.png

    它缺陷包括:
    • 长事务容易饿死,因为长事务的timestamp偏小,大概率会在执行一段时间后读到更新的数据,导致abort。

    • 读操作也会产生写(写RTS)。

    4.基于Validation(OCC)的并发控制

    执行过程中,每个事务维护自己的写操作(Basic T/O在事务执行过程中写就将数据写入DB)和相应的RTS/WTS,提交时判断自己的更改是否和数据库中已存在的数据冲突,如果不冲突才写入DB。OCC分为三个阶段:

    • Read & Write Phase:即读写阶段,事务维护读的结果和即将提交的更改,以及写入记录的RTS和WTS。

    • Validation Phase:检查事务是否与数据库中的数据冲突。

    • Write Phase:不冲突就写入,冲突就abort,restart。

    Read & Write Phase结束,进入Validation Phase相当于事务准备完成,进入提交阶段了,进入Validation Phase的时间被选做记录行的时间戳,来定序。不用事务开始时间是因为:事务执行时间可能较长,导致后开始的事务可能先提交,这会加大事务冲突的概率,较小时间戳的事务后写入数据库,肯定会abort。

    Validation过程

    假设当前只有两个事务T1和T2,并修改了相同数据行,T1的时间戳 < T2的时间戳(即validation顺序:T1 < T2,对用户而言,T1先发生于T2),则有如下情况:

    (1)T1在validate阶段,T2还在Read & Write Phase。此时只要T1和T2已经发生的读写没有冲突,就可以提交。

    • 如果WS(T1) ∩ (RS(T2) ∪ WS(T2)) = ∅,说明T2和T1写的记录无冲突,validation通过,可以写入。

    • 否则,T2与T1之间存在读写冲突或写写冲突,T1需要回滚。读写冲突:T2读到了T1写之前的版本,T1提交后,它可能读到T1写的版本,不可重复读。写写冲突:T2有可能在旧版本基础上更新,再次写入,造成T1的更新丢失。

    (2)T1完成validate阶段,进入write阶段直到提交完成,这已经是不可逆的了。T2在T1进入write phase之前的读写,肯定和T1的操作不冲突(因为T1 validation通过了)。T2之后继续的读写操作,有可能冲突与T1要提交的操作,因此T2进入validate阶段:
    • 如果WS(T1) ∩ RS(T2)= ∅,说明T2没读到T1写的记录,validation通过,T2可以写入。(为什么不验证WS(T2)了呢?WS(T1)已经提交了,且它的时间戳小于WS(T2),WS(T2)里之前的一部分肯定没有冲突,之后的一部分,因为没有读过T1的写入的对象,写进去也没问题,不会覆盖WS(T1)的写)

    • 否则,T2与T1之间存在读写冲突和写写冲突,T2需要回滚。读写冲突:T2读到了T1写之前的版本,T1提交后,它可能读到T1写的版本,不可重复读。写写冲突:T2有可能在旧版本基础上更新,再次写入,造成T1的更新丢失。

    5.基于MVCC的并发控制

    数据库维护了一条记录的多个物理版本。事务写入时,创建写入数据的新版本,读请求依据事务/语句开始时的快照信息,获取当时已经存在的最新版本数据。它带来的最直接的好处是:写不阻塞读,读也不阻塞写,读请求永远不会因此冲突失败(例如单版本T/O)或者等待(例如单版本2PL)。对数据库请求来说,读请求往往多于写请求。主流的数据库几乎都采用了这项优化技术。

    MVCC是读和写请求的优化技术,没有完全解决数据库并发问题,它需要与前述的几种并发控制技术组合,才能提供完整的并发控制能力。常见的并发控制技术种类包括:MV-2PL,MV-T/O和MV-OCC,它们的特点如下表:
    10.png

    MVCC还有两个关键点需要考虑:多版本数据的存储和多余多版本数据的回收。

    多版本数据存储方式,大致可以分为两类:
    (1)Append only的方式,新旧版本存储在同一个表空间,例如基于LSM-Tree的存储引擎。
    (2)主表空间记录最新版本数据,前镜像记录在其它表空间或数据段,例如InnoDB的多版本信息记录在undo log。多版本数据回收又称为垃圾回收(GC),那些没有机会再被任何读请求获取的旧版本记录,应该被及时删除。

    6.总结

    本文依据冲突处理的时机(乐观程度),依次介绍了基于锁(在事务开始前预防冲突)、基于T/O(在事务执行中判断冲突)和基于Validation(在事务提交时验证冲突)的事务并发控制机制。不同的实现适用于不同的workload,并发冲突小的workload,当然适合更乐观的并发控制方式。而MVCC可以解决只读事务和读写事务之间相互阻塞的问题,提高了事务的并发读,被大多数主流数据库系统采用。

    原文链接

    本文为云栖社区原创内容,未经允许不得转载。

    展开全文
  • DB2,Mysql,Oracle中的并发控制机制,数据库并发控制机制,顾名思义,是用来控制数据库的并发操作的机制。控制的目的是为了保证数据完整和数据一致性。
  • 数据库并发控制.pdf

    2021-10-06 00:41:08
    数据库并发控制.pdf
  • 数据库并发控制技术

    千次阅读 2018-08-25 14:27:19
    事务是一系列的数据库操作,是数据库应用程序的基本逻辑单元,也是恢复和并发控制的基本单位。 事务处理技术主要包括数据库恢复技术和并发控制技术。本篇博文主要总结下并发控制技术。 事务:是用户定义的一个...
  • 数据库是一个共享资源,可以提供多个用户使用。这些用户程序可以一个一个地串行执行, 每个时刻只有一个用户程序运行, 执行对数据库的存取, 其他用户程序必须等到这个用户程 序结束以后方能对数据库存取。但是...
  • 数据库并发控制及SQLSERVER的并发控制机制PPT学习教案.pptx
  • 数据库并发控制之并发调度

    千次阅读 2018-10-25 01:08:22
    一、并发调度的可串行性 二、两段锁协议 三、封锁的粒度 四、其他并发控制机制
  • 数据库并发控制》PPT课件.ppt
  • 数据库并发控制PPT学习教案.pptx
  • 数据库并发控制及SQL

    2012-08-06 15:32:36
    数据库并发控制及SQL 包含内容包括:数据库并发控制及SQL Server的并发控制机制 事务及并发控制的基本概念 封锁机制 SQL Server的并发控制机制 等等
  • 开发一部系列培训数据库并发访问控制;提 纲;数据库并发访问控制机制;数据库并发访问控制机制;数据库并发访问控制机制;数据库并发访问控制机制;数据库并发访问控制机制;数据库并发访问控制机制;数据库的并发访问控制...
  • 数据库并发控制知识点总结

    千次阅读 2016-08-28 13:13:40
    刚看到一篇很不错的数据库并发控制知识点总结,包括一部分可能会出现在笔试面试中的题目,适合对基础概念理解不透彻的我,就转载过来了。  原文地址:http://blog.csdn.net/xiangminjing/article/details/5922325...
  • 第9章 数据库并发控制.ppt第9章 数据库并发控制.ppt第9章 数据库并发控制.ppt第9章 数据库并发控制.ppt
  • chp数据库并发控制实用PPT课件.pptx
  • chp数据库并发控制实用PPT学习教案.pptx
  • 关于数据库并发控制,封锁,并发调度的学习攻略
  • 协同CAD系统图档数据库并发控制研究.pdf
  • 华中科技大学数据库并发控制PPT学习教案.pptx
  • 学习第八章数据库并发控制练习和答案.pdf
  • 分布式数据库并发控制分布式数据库并发控制

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 324,914
精华内容 129,965
关键字:

数据库并发控制

友情链接: ass2ofAI.rar