数据库:事务处理

事务基本概念

事务

是一系列的数据库操作,是数据库应用程序的基本逻辑单元

所谓事务是用户自定义的一个数据库操作序列,这些操作要么全做,要么不做,是一个不可分割的工作单位。

例如:在关系型数据库中,一个事务可以是一条sql,一组sql或整个程序

事务和程序的区别:一般来说,一个程序中包含很多事务

事务的定义

事务的开始与结束可以由用户显式控制,若用户没有显式控制,则有数据库管理系统默认划分事务

1、begin transaction; 

事务的开始,以commit或rollback结束。

2、commit;

提交事务的所有操作将事务中所有对数据库的更新写回到磁盘上的物理数据库中,事务正常结束

3、rollback;

回滚,即当事务运行过程中发生了某种故障,不在继续执行时,系统将事务中对数据库所有已完成的操作全部撤销,回滚到事务开始时的状态

事务的特点

  1. 原子性
  2. 一致性
  3. 隔离性
  4. 持续性【永久性】

事务处理技术

数据库恢复

数据恢复的常用技术:数据转储、登记日志文件

日志文件:

用来记录事务对数据库的更新操作的文件

作用:在数据库恢复中起到重要作用,并协助后备副本进行介质故障恢复

主要包含:事务标识、操作类型、操作对象、更新前数据的旧值、更新后数据的新值

数据故障

1、事务内部故障

是非预期的,不能由应用程序处理

2、系统故障【软故障】

指造成系统停止运转的任何事件,使得系统要重启

3、介质故障【硬故障】

4、计算机病毒

2、采用什么机制来保证数据库并发操作的正确性?

并发控制

单处理机系统中,事务的并行执行实际上是这些并行事务的并行操作轮流交叉运行 

并发操作带来的数据不一致有三种情况:

1、丢失修改

写-写

两个事务T1和T2读入同一数据并修改,T2提交的结果破坏了T1提交的结果,导致T1的修改被丢失。

解决办法:

一种办法是是锁,即基于锁的并发控制,比如2PL,这种方式开销比较高,而且无法避免死锁。

乐观并发控制(OCC)是一种用来解决写-写冲突的无锁并发控制,认为事务间争用没有那么多,所以先进行修改,在提交事务前,检查一下事务开始后,有没有新提交改变,如果没有就提交,如果有就放弃并重试。乐观并发控制类似自选锁。乐观并发控制适用于低数据争用,写冲突比较少的环境。

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

2、不可重复读

读-写

事务T1读取数据后,事务T2执行更新操作,使T1无法再现前一次读取结果。

3、读“ 脏 ”数据 【污读】

读-写

事务 T1修改某一数据,并将其写回磁盘,事务T2读取同一数据后, T1由于某种原因被撤消,这时 T1已修改过的数据恢复原值, 
T2读到的数据就与数据库中的数据不一致,则T2读到的数据就为""数据,即不正确的数据。(读取到未提交数据)

 

解决办法:

多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是系统为每个事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读。

并发控制的解决办法

并发控制的主要技术有封锁(locking)时间戳(timestamp)乐观控制法(optimistic sheduler)多版本并发控制(multi-version concurrency control,MVCC)等,各个技术之间是存在交叉配合使用的。商用的DBMS一般都采用封锁方法。

悲观并发控制:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。 
乐观并发控制:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。 

封锁

事务T在对某个数据对象(例如表、记录等)操作之前,先向系统发出请求,对其加锁。加锁后事务T就对该数据对象有了一定的控制权,在事务T释放它的锁之前,其它的事务不能更新此数据对象,属于一种悲观控制法

封锁的基本类型

基本的封锁类型有两种:排他锁(X锁)共享锁(S锁)

(1)排它锁又称写锁X。若事务T对数据对象A加上X锁,只有事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

(2)共享锁又称读锁S。若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。

封锁协议

一级封锁协议:对应事务隔离级别中的读未提交(Read uncommited)

事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。事务结束包括正常结束(COMMIT)和非正常结束(ROLLBACK)。

一级封锁协议可防止丢失修改,并保证事务T是可恢复的,因为防止其他事务进行同时修改。

在一级封锁协议中,如果仅仅是读数据不对其进行修改,是不需要加锁的。所以它不能保证可重复读不 读"脏"数据

 

非正常结束时,X锁就自动释放了! 

二级封锁协议:对应事务隔离级别中的读已提交(Read Committed)

一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,读完后即可释放S锁。二级封锁协议防止了丢失修改和读"脏"数据。 

遵从二级封锁协议时发生的“不可重复读”的过程

三级封锁协议:对应事务隔离级别中的可重复读(Repeatable Read)

一级封锁协议加上事务T在读取数据R之前必须先对其加S锁,直到事务结束才释放。三级封锁协议除防止了丢失修改和不读'脏'数据外,还进一步防止了不可重复读

封锁中的死锁与活锁 

活锁

存在现象:如果事务T1封锁了数据R,事务T2又请求封锁数据R,于是T2等待。事务T3也请求封锁R,当事务T1释放了数据R上的封锁之后系统首先批准了事务T3的封锁请求,T2仍然等待。然后T4又申请封锁R,当T3释放了R的封锁之后系统又批准了T4的封锁请求。T2有可能一直等待下去,这就是活锁。

解决办法:避免活锁的方法就是先来先服务的策略。当多个事务请求对同一数据对象封锁时,封锁子系统按照请求的先后对事务排队。数据对象上的锁一旦释放就批准申请队列中的第一个事务获得锁。

死锁

存在现象:如果事务T1封锁了数据R1,事务T2封锁了数据R2,然后T1又请求封锁数据R2,因为T2已经封锁了数据R2,于是T1等待T2释放R2上的锁。接着T2又申请封锁R1,因为因为T1已经封锁了数据R1,T2也只能等待T1释放R1上的锁。这样就出现了T1在等待T2,T2也在等待T1的局面,T1和T2两个事务永远不能结束,形成死锁。

解决办法:

1) 死锁的预防:

①一次封锁法

一次封锁法要求事务必须一次将所有要使用的数据全部加锁,否则不能继续执行。例如上图中的事务T1将数据R1和R2一次加锁,T1就能执行下去,而T2等待。T1执行完成之后释放R1,R2上的锁,T2继续执行。这样就不会产生死锁。

一次封锁法虽然能防止死锁的发生,但是缺点却很明显。一次性将以后要用到的数据加锁,势必扩大了封锁的范围 ,从而降低了系统的并发度。

②顺序封锁法

顺序封锁法是预先对数据对象规定一个封锁顺序,所有的事务都按照这个顺序实行封锁。

顺序封锁法虽然可以有效避免死锁,但是问题也很明显。第一,数据库系统封锁的数据对象极多,并且随着数据的插入、删除等操作不断变化,要维护这样的资源的封锁顺序非常困难,成本很高。第二,事务的封锁请求可以随着事务的执行动态的确定,因此很难按照规定的顺序实行封锁。

可见,预防死锁的产生并不是很适合数据库的特点,所以在解决死锁的问题上普遍采用的是诊断并且解除死锁。

2) 死锁的诊断与解除:

①超时法

如果一个事务的等待时间超过了默认的时间,就认为是产生了死锁。

②等待图法

一旦检测到系统中存在死锁就要设法解除。通常的解决方法是选择一个处理死锁代价最小的事务,将其撤销,释放此事务持有的所有的锁,恢复其所执行的数据修改操作,使得其他事务得以运行下去。

两段锁协议2PL

所谓的二段锁协议是指所有事务必须分两个阶段对数据进行加锁和解锁操作。

  • 在对任何数据进行读、写操作之前,首先要申请并获得该数据的封锁。

  • 在释放一个封锁之后,事务不在申请和获得其他封锁。

也就是说事务分为两个阶段。第一个阶段是获得封锁,也称为扩展阶段。在这个阶段,事务可以申请获得任何数据项任何类型的锁,但是不能释放任何锁第二阶段是释放封锁,也称为收缩阶段。在这个阶段,事务可以释放任何数据项上任何类型的封锁,但是不能再申请任何锁

事务遵守两段锁协议是可串行化调度的充分条件,而不是必要条件。也就是说遵守两段锁协议一定是可串行化调度的,而可串行化调度的不一定是遵守两段锁协议的。

两段锁协议和一次封锁法的异同

一次封锁法要求事务必须将要使用的数据全部加锁,否则不能继续执行。因此一次封锁法遵守两段锁协议。

但是两段锁协议并不要求事务将要使用的数据一次全部加锁,因此两段锁协议可能发生死锁。

时间戳

给每个事务分配一个全局惟一的时间戳。时间截的值产生了一个精确的顺序,事务按照该顺序提交。时间戳必须有两个特性:惟一性单调性,惟一性保证不存在相等的时间戳值,单调性保证时间戳的值是一直增长的。

同一事务中所有的数据库操作(读和写)都必须有相同的时间戳。DBMS按照时间戳顺序执行冲突的事务,因此保证了事务的可串行化。如果两个事务冲突,通常终止其中一个,将其回滚并重新调度,赋予新的时间戳

用时间戳实现并发控制,需要为数据库中每个值附加两个字段

读时间戳:用于保存所有访问该记录的事务中的最大时间戳(最后读取时间)

写时间戳:用于保存将记录改到当前值的事务的时间戳(最后修改时间)

因此时间戳增加了内存需求和数据库的处理开销,因为有可能导致许多事务被终止,重新调度和重新赋予时间戳,时间戳方法一般需要大量的系统资源.

这样的事务在并行执行时,用的是乐观控制,先任由事务对数据进行修改,在写回去的时候在判断记录的时间戳有没有修改,如果没有被修改,就写入,否则,就生成一个新的时间戳并再次尝试更新数据。

乐观控制法

乐观方法基于这样的假设,数据库操作的大部分都不会发生冲突,乐观方法不要求锁定,作为替换,事务不受限制地被执行,直到它被提交,便用乐观方法,每个事务经过两个或者三个阶段,它们是读、确认、写。

  (1)读阶段,事务读取数据库,执行需要的计算,并对一个私有的数据库值的副本进行更新,事务的所有更新操作都记录在一个临时更新文件中,该文件将不 ,会被剩下的其他事务访问.

  (2)确认阶段,对事务进行确认以保证所做的修改不会影响数据库的完整性和一致性,如果确认检查是肯定的,事务进入写阶段;如果确认检查是否定的,则事务回滚,重新启动,所做的修改被抛弃

  (3)写阶段,所做的修改被永久地写入到数据库中,乐观方法对于大多数只有较少更新事务的查询数据库系统来说是可以接受的,

多版本并发控制

MVCC (Multiversion Concurrency Control),即多版本并发控制技术,是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。在MVCC中,每个事务操作的是数据的一个快照,写操作造成的变化在写操作完成之前(或者数据库事务提交之前)对于其他的事务来说是不可见的。

当一个 MVCC 数据库需要更一个一条数据记录的时候,它不会直接用新数据覆盖旧数据,而是将旧数据标记为过时(obsolete)并在别处增加新版本的数据。这样就会有存储多个版本的数据,但是只有一个是最新的。这种方式允许事务读取在他读之前已经存在的数据,即使这些在读的过程中半路被别人修改、删除了,也对先前正在读的用户没有影响。这种多版本的方式避免了填充删除操作在内存和磁盘存储结构造成的空洞的开销,但是需要系统周期性整理(sweep through)以真实删除老的、过时的数据

MVCC 并发控制下的读事务一般使用时间戳或者事务ID去标记当前读的数据库的状态(版本),读取这个版本的数据。读、写事务相互隔离,不需要加锁。读写并存的时候,写操作会根据目前数据库的状态,创建一个新版本,并发的读则依旧访问旧版本的数据。

一句话讲,MVCC就是用 同一份数据临时保留多版本的方式的方式,实现并发控制。

这里留意到 MVCC 关键的两个点:

    在读写并发的过程中如何实现多版本;
    在读写并发之后,如何实现旧版本的删除(毕竟很多时候只需要一份最新版的数据就够了)

转载

原文地址:https://www.cnblogs.com/pam-sh/p/12791244.html