数据库事务详解

Posted by 胡奚冰 on April 25, 2019

什么是事务?

事务(Transaction)是由一系列对系统中数据进行访问与更新的操作所组成的一个程序执行逻辑单元。

事务具有4个基本特性,分别是:

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Duration)

习惯上被称之为ACID特性。

ACID特性

原子性(Atomicity)

事务的原子性是指事务必须是一个原子的操作序列单元。事务中包含的各项操作在一次执行过程中,只允许出现两种状态之一。

  • 全部执行成功
  • 全部执行失败

任何一项操作的失败都会导致整个事务的失败,同时其它已经被执行的操作都将被撤销并回滚,只有所有的操作全部成功,整个事务才算是成功完成。

一致性(Consistency)

事务的一致性是指事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处以一致性状态。

拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。

隔离性(Isolation)

事务的隔离性是指在并发环境中,并发的事务是互相隔离的,一个事务的执行不能被其它事务干扰。也就是说,不同的事务并发操作相同的数据时,每个事务都有各自完整的数据空间。

一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务是不能互相干扰的。

关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。

持久性(Durability)

事务的持久性是指事务一旦提交后,数据库中的数据必须被永久的保存下来。即使服务器系统崩溃或服务器宕机等故障。只要数据库重新启动,那么一定能够将其恢复到事务成功结束后的状态。

事务并发引起的问题

当多个线程都开启事务操作数据库中的数据时,可能会发生的以下几种问题。

脏读

脏读是指在一个事务处理过程里读取了另一个未提交的事务中的数据。

在事务A和事务B同时执行时可能会出现如下场景:

时间 事务A(存款) 事务B(取款)
T1 开始事务
T2 开始事务
T3 查询余额(1000元)
T4 取出1000元(余额0元)
T5 查询余额(0元)
T6 撤销事务(余额恢复1000元)
T7 存入500元(余额500元)
T8 提交事务

余额应该为1500元才对。请看T5时间点,事务A此时查询的余额为0,这个数据就是脏数据,他是事务B造成的,很明显是事务没有进行隔离造成的。

不可重复读

不可重复读是指在对于数据库中的某个数据,一个事务范围内多次查询却返回了不同的数据值,这是由于在查询间隔,被另一个事务修改并提交了。

不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。

时间 事务A(存款) 事务B(取款)
T1 开始事务
T2 开始事务
T3 查询余额(1000元)
T4 查询余额(1000元)
T5 取出1000元(余额0元)
T6 提交事务
T7 查询余额(0元)
T8 提交事务

事务A其实除了查询两次以外,其它什么事情都没做,结果钱就从1000变成0了,这就是不可重复读的问题。

虚读/幻读

幻读是事务非独立执行时发生的一种现象。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。

幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。

事务隔离级别

为了控制以上情况的发生,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。

MySQL数据库为我们提供的四种隔离级别:

  • 读未提交(READ_UNCOMMITTED)
  • 读已提交(READ_COMMITTED)
  • 可重复读(REPEATABLE_READ)
  • 顺序读(SERIALIZABLE)

越往下隔离级别逐渐提升,MySQL默认级别是可重复读(REPEATABLE_READ)。

而在Oracle数据库中,只支持Serializable (串行化)级别和Read committed (读已提交)这两种级别,其中默认的为Read committed级别。

读未提交(READ_UNCOMMITTED)

最低级别,任何情况都无法保证

读已提交(READ_COMMITTED)

一个事务只能看见已经提交事务所做的改变。

可重复读(REPEATABLE_READ)

确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。

顺序读(SERIALIZABLE)

串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了。

事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。

事务隔离级别对比

事务隔离级别 脏读 不可重复读 幻读
读未提及(READ_UNCOMMITTED)
读已提交(READ_COMMITTED)
可重复读(REPEATABLE_READ)
顺序读(SERIALIZABLE)

事务隔离级别越高,安全性越高,但是并发效率越低。

参考:

数据库事务详解

数据库事务的四大特性以及事务的隔离级别