事务的概念
构成单一逻辑工作单元的操作集合称为事务(transaction)
事务的 ACID 特性
原子性(atomicity)
同一个事务中的所有操作,要么全都执行,要么全都不执行,如果一个事务在执行过程中因为某些原因执行失败,那么已经执行的操作就要全部撤销,这个撤销的过程叫作回滚(rollback)
一致性(consistency)
如果数据库中的数据满足预设的全部规则,则称数据库中的数据是一致的
如果在执行事务之前,数据库中的数据是一致的,那么事务执行完毕之后,数据库中的数据仍然要是一致的,保证事务具有一致性是编写事务的程序员的职责
隔离性(isolation)
尽管多个事务可能并行执行,但对于任何一对事务 T 1 T_1 T1 和 T 2 T_2 T2 ,在 T 1 T_1 T1 看来,要么 T 2 T_2 T2 还没开始执行,要么 T 2 T_2 T2 已经执行完毕
严格保证隔离性可能会降低数据库系统的性能,因此,在一些数据库系统中可以对隔离性作出一些妥协
持久性(durability)
一个事务执行完成后,对数据库的改变就应该是永久的,即使之后发生了系统故障,在故障修复之后,事务对数据库的改变也不能丢失
事务的状态
事务有以下几种状态:
- 活动状态:一个事务开始执行时,事务就进入了活动状态,对于一个活动状态的事务,如果数据库系统判断其不能继续正常执行,事务就会进入失败状态
- 部分提交状态:当一个事务的操作全部执行完毕时,事务就从活动状态进入了部分提交状态,之所以不是提交状态,是因为此时有些应该被写入磁盘的数据还保留在数据库缓冲区中,进入部分提交状态的事务,还需要等待这些数据被真正写入磁盘,在写入这些数据时,事务仍然可能由于某些原因,从而进入失败状态
- 提交状态:当一个部分提交状态的事务,向磁盘中写入了足够的数据,以至于即使出现了故障,在故障修复后也能恢复事务对数据库的影响,那么事务就进入了提交状态
- 失败状态:活动状态和部分提交状态的事务,都可能因为某些原因进入失败状态,处于失败状态的事务等待回滚
- 中止状态:当一个处于失败状态的事务回滚之后,事务就进入了中止状态,此时数据库系统有两种选择,要么重启事务,要么杀死事务,若事务失败的原因是内部的逻辑错误,则只能选择杀死事务
事务不同状态之间的相互转换如下:
并行事务的调度
并行的原因
如果所有事务都串行地执行,那么事务的隔离性和一致性都能得到保证,当多个事务并行执行时,我们就需要为保证隔离性和一致性而进行额外的工作,但允许事务并行执行有很大的好处
在计算机系统中有许多计算资源,如果事务仅仅是串行地执行,那么在很多情况下,计算资源的利用率很低,当事务在 CPU 上计算时,I/O 资源就被闲置,当事务等待 I/O 时,CPU 资源又被浪费,此外,对于多核处理器,一个事务通常情况下只能使用处理器的一个核,那么其它的核就被浪费了
在多个事务并行执行时,计算机系统的资源利用率显著提高,这样我们虽然没有加快单个事务的执行速度,但吞吐量会大大提升
并行执行事务的原因本质上和操作系统中使用多道程序的原因是一样的
调度
每个事务都是由许多指令构成的,一组事务中指令执行的时间顺序就叫作关于这组事务的调度,显然,一组事务的调度需要包含这组事务的全部指令,并且同一个事务中的指令顺序必须保持
串行调度
对于同一组事务的不同调度,产生的结果可能不同,其中一些调度可能会破坏事务的隔离性和一致性
如果一个调度中同一个事务中的指令紧挨在一起,这个调度就是一个串行调度,我们已经知道,串行调度是不会破坏事务的隔离性和一致性的
冲突可串行化
如果一个调度产生的结果和某个串行调度产生的结果相同,那么我们认为这个调度和串行调度等价,对于一个调度,我们希望能判断它是否和某个串行调度等价,因为只有这样才能知道这个调度是否会破坏隔离性和一致性
但是,要想精确判断一个调度是否和某个串行调度等价,需要仔细分析每条指令的内容,这种分析通常难以实现且代价很大,不过有方法可以找出一部分和串行调度等价的调度
对于一个调度,我们只考虑它的 I/O 操作,当两条相邻的 I/O 操作 x x x 和 y y y 交换执行顺序时,考虑交换的影响(假设原本 x x x 在 y y y 之前执行):
- x x x 和 y y y 操作的数据不同时,交换 x , y x,y x,y 无任何影响
- x = r e a d ( d ) x=read(d) x=read(d) , y = r e a d ( d ) y=read(d) y=read(d) 时,交换 x , y x,y x,y 无任何影响
- x = r e a d ( d ) x=read(d) x=read(d) , y = w r i t e ( d ) y=write(d) y=w