我们学习数据库,经常看到数据库事务,ACID事务等相关的概念,抛开数据库,可以将事务更广泛的定义为:一个或多个原子操作组合而成的执行单元。更通俗的讲,就是将几件小事或是几个步骤捆绑起来作为一个整体来处理对待。
而数据库事务,即一条或多条不可再分的数据操作指令组合而成的执行单元。一个标准的、合格的数据库事务,应该需要满足四个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),这也就是所称的ACID事务。
在四个事务特性中,数据的一致性是最关键的,保证数据的一致性是一个数据库最基本的要求,同时也是事务这一个概念出现的一个重要原因,更是衡量一个事务是否合格的最重要的标准,也是事务最根本的追求。而影响事务一致性的因素主要有事务并发问题和数据库系统故障。说到底,其他三个特性,都是服务于保证数据一致性而存在的。
事务的出现必然是为了解决某一问题。在使用DML(数据库操作语言)或DDL(数据库定义语言)使,多数情况下对数据的修改不是局限于一条记录或是一张数据表的。
试想这么一个简单的图书馆借阅情景:A同学借了一本书a,在数据库中我们要有必要的两步操作,一是插入一条A借阅书a的记录,二是把书a的库存减一。
假若没有事务,执行了第一个操作后,系统忽然出现故障没能成功执行第二个操作,这样导致的结果是A借到了书a,但图书馆中书a的库存却没有减少,是不符合逻辑,打破了数据的一致性原则。
简单来讲,事务的出现就是将这两个步骤捆绑在一起,实现的效果是:要么两个步骤都执行成功,要么都不执行。从而保证了数据的一致性。对于事务的使用,这里引出两个概念,一个叫事务的提交,即在事务成功执行后提交结束事务;一个叫事务的回滚,即在事务执行过程中某个操作出现问题,恢复到事务执行之前的那个一致性状态。
而事务的出现,也带来了不少事务并发问题,解决这些问题和追求ACID事务的目标是一致的。
事务带来的并发问题常见的有以下四种情况
脏写是指两个事务A、B,A更新数据还未提交,B也来更新数据。对于A来讲,最终的数据不是自己想要更新的值。
脏写问题使用行级排它锁即可解决,在一条记录被一个事务更新时,另外的事务是无法更新这条事务的。所以不再详述。
脏读是至两个事务A、B,A读取了B更新尚未提交的内容,B事务回滚,导致A读取的内容错误。如图:
不可重复读是指两个事务A、B,A读取一个字段,B更新了这个字段,A再读取一次这个字段,两次读取的结果不同。如图
幻读是指两个事务A、B,A读取一个字段的行数,B插入或删除了某些记录,A再读取这个字段的行数,行数不同了。如图
不可重复读和幻读问题十分相似,不可重复读是并发事务B的UPDATE操作对事务A带来的问题,幻读是并发事务B的INSERT和DELETE操作对事务A带来的问题。
为了解决事务的并发问题,相应出现了SQL的隔离级别规范的概念。一般的,常见的隔离级别有以下四种,它们可以不同程度的解决事务的并发问题。
允许事务A读取事务B未提交的更新。自然,事务的并发工作流程是与以上三幅图片所示的流程一样的,脏读、不可重复读、幻读问题均不可实现。
只允许事务A读取事务B已经提交的更新。若B事务更改某条记录未结束(提交或回滚),A事务读取相应记录会阻塞至B事务结束。在个隔离级别下,脏读问题图示会变为:
A事务读取某个字段,在A事务操作期间,禁止B事务对该字段的更新。可重复读解决事务的不可重复读问题,这样,流程图变为:
串行化即舍弃事务的并发处理能力,将所有事务串行执行,这样虽然避免了所有的并发问题,但性能效率实在太低,一般不用。
四个隔离级别解决并发问题总结如下:
隔离级别 | 解决脏写 | 解决脏读 | 解决不可重复读 | 解决幻读 |
---|---|---|---|---|
读未提交 READ UNCOMMITTED |
是 | 否 | 否 | 否 |
读已提交 READ COMMITTED |
是 | 是 | 否 | 否 |
可重复读 REPEATABLE READ |
是 | 是 | 是 | 否 |
串行化 SERIALIZABLE |
是 | 是 | 是 | 是 |
四个隔离级别解决问题程度的递增,是用牺牲数据库的并发性能来得到的,如串行化级别,解决了所有的并发问题,但却没有任何并发能力。所以需要根据实际业务情况选择合适的隔离级别。
还需要清楚,四个隔离级别只是SQL给出的规范,包括前面所讲的ACID事务的实现,每个数据库存储引擎对这些的实现技术都是不同的,所有引擎都在追求更高的隔离级别下的更高的并发能力。比如MySQL默认使用的InnoDB引擎,使用MVCC技术在可重复读级别下,不用阻塞事务B的写操作就可以解决不可重复读甚至是幻读问题。