数据库事务、并发问题及隔离级别

一、什么是事务

我们学习数据库,经常看到数据库事务,ACID事务等相关的概念,抛开数据库,可以将事务更广泛的定义为:一个或多个原子操作组合而成的执行单元。更通俗的讲,就是将几件小事或是几个步骤捆绑起来作为一个整体来处理对待。

而数据库事务,即一条或多条不可再分的数据操作指令组合而成的执行单元。一个标准的、合格的数据库事务,应该需要满足四个特性,即原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),这也就是所称的ACID事务。

  • 原子性(Atomicity)
    原子性就是指的一个事务是一个整体,是不可再分的,事务中的所有指令要么都执行成功,要么都不执行。比如常见的各种闯关游戏,你要么一路不死闯关成功,要么中途死了从头再来。
  • 一致性(Consistency)
    一致性指的是一个事务执行,会将数据从一个一致性状态,变为另一个一致性状态。所谓的一致性状态,我更愿意理解为一个应该是的状态、一个符合现实逻辑关系的正确的状态。比如"我们两个各有一块糖",执行一个事务"我抢了你一块糖",就理应是变为"我有两块糖,你没有糖"这个状态,这前后就是两个一致性状态。若结果变为了"我没有糖,你有两块糖"或是其他,那就自然违背了一致性了。
  • 隔离性(Isolation)
    隔离性是指当多个事务并发执行时,是互不影响的,即需要保证多个事务并发执行的结果,要与串行执行的结果相同。还是闯关游戏的例子,我们两个同时在两台电脑上各自闯关,我的闯关过程和闯关结果对你应该是丝毫没有影响的,不会因为我闯关成功而给你增加难度。而隔离性主要是为了解决事务引发的并发问题,下文将有介绍。
  • 持久性(Durability)
    持久性指的是事务提交后对数据库的更新是不可逆的、持久化的,即使系统故障发生,也不会造成数据丢失。仍是闯关游戏,我闯关成功就是闯关成功,不会我明天登录游戏又回到了起点。

在四个事务特性中,数据的一致性是最关键的,保证数据的一致性是一个数据库最基本的要求,同时也是事务这一个概念出现的一个重要原因,更是衡量一个事务是否合格的最重要的标准,也是事务最根本的追求。而影响事务一致性的因素主要有事务并发问题和数据库系统故障。说到底,其他三个特性,都是服务于保证数据一致性而存在的。

二、为什么要使用事务

事务的出现必然是为了解决某一问题。在使用DML(数据库操作语言)或DDL(数据库定义语言)使,多数情况下对数据的修改不是局限于一条记录或是一张数据表的。

试想这么一个简单的图书馆借阅情景:A同学借了一本书a,在数据库中我们要有必要的两步操作,一是插入一条A借阅书a的记录,二是把书a的库存减一。

假若没有事务,执行了第一个操作后,系统忽然出现故障没能成功执行第二个操作,这样导致的结果是A借到了书a,但图书馆中书a的库存却没有减少,是不符合逻辑,打破了数据的一致性原则。

简单来讲,事务的出现就是将这两个步骤捆绑在一起,实现的效果是:要么两个步骤都执行成功,要么都不执行。从而保证了数据的一致性。对于事务的使用,这里引出两个概念,一个叫事务的提交,即在事务成功执行后提交结束事务;一个叫事务的回滚,即在事务执行过程中某个操作出现问题,恢复到事务执行之前的那个一致性状态。

而事务的出现,也带来了不少事务并发问题,解决这些问题和追求ACID事务的目标是一致的。

三、事务并发问题

事务带来的并发问题常见的有以下四种情况

1、脏写

脏写是指两个事务A、B,A更新数据还未提交,B也来更新数据。对于A来讲,最终的数据不是自己想要更新的值。

脏写问题使用行级排它锁即可解决,在一条记录被一个事务更新时,另外的事务是无法更新这条事务的。所以不再详述。

2、脏读

脏读是至两个事务A、B,A读取了B更新尚未提交的内容,B事务回滚,导致A读取的内容错误。如图:
在这里插入图片描述

3、不可重复读

不可重复读是指两个事务A、B,A读取一个字段,B更新了这个字段,A再读取一次这个字段,两次读取的结果不同。如图
在这里插入图片描述

4、幻读

幻读是指两个事务A、B,A读取一个字段的行数,B插入或删除了某些记录,A再读取这个字段的行数,行数不同了。如图
在这里插入图片描述
不可重复读和幻读问题十分相似,不可重复读是并发事务B的UPDATE操作对事务A带来的问题,幻读是并发事务B的INSERT和DELETE操作对事务A带来的问题。

四、事务的隔离级别

为了解决事务的并发问题,相应出现了SQL的隔离级别规范的概念。一般的,常见的隔离级别有以下四种,它们可以不同程度的解决事务的并发问题。

1、读未提交(READ UNCOMMITTED)

允许事务A读取事务B未提交的更新。自然,事务的并发工作流程是与以上三幅图片所示的流程一样的,脏读、不可重复读、幻读问题均不可实现。

2、读已提交(READ COMMITTED)

只允许事务A读取事务B已经提交的更新。若B事务更改某条记录未结束(提交或回滚),A事务读取相应记录会阻塞至B事务结束。在个隔离级别下,脏读问题图示会变为:
在这里插入图片描述

3、可重复读(REPEATABLE READ)

A事务读取某个字段,在A事务操作期间,禁止B事务对该字段的更新。可重复读解决事务的不可重复读问题,这样,流程图变为:
在这里插入图片描述

4、串行化(SERIALIZABLE)

串行化即舍弃事务的并发处理能力,将所有事务串行执行,这样虽然避免了所有的并发问题,但性能效率实在太低,一般不用。
在这里插入图片描述

四个隔离级别解决并发问题总结如下:

隔离级别 解决脏写 解决脏读 解决不可重复读 解决幻读
读未提交
READ UNCOMMITTED
读已提交
READ COMMITTED
可重复读
REPEATABLE READ
串行化
SERIALIZABLE

四个隔离级别解决问题程度的递增,是用牺牲数据库的并发性能来得到的,如串行化级别,解决了所有的并发问题,但却没有任何并发能力。所以需要根据实际业务情况选择合适的隔离级别。

还需要清楚,四个隔离级别只是SQL给出的规范,包括前面所讲的ACID事务的实现,每个数据库存储引擎对这些的实现技术都是不同的,所有引擎都在追求更高的隔离级别下的更高的并发能力。比如MySQL默认使用的InnoDB引擎,使用MVCC技术在可重复读级别下,不用阻塞事务B的写操作就可以解决不可重复读甚至是幻读问题。