阅读 1010

数据库事务的方方面面

事务是关系型数据的一个重要特性,但很少有人能对事务有全面性的了解,这篇文章就把事务的方方面面讲给你。

事务的概念

什么是事务

数据库事务(事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

并发问题

五种并发问题分别是:

  • 第一类丢失更新
  • 第二类丢失更新
  • 脏读
  • 不可重复读
  • 幻读

下面依次举例讲解一下。

更新丢失(Lost Update)

一个事务覆盖另一个事务已提交的更新数据叫丢失更新。

第一类丢失更新:

时间 draw money事务 deposit money事务
T1 开始事务
T2 开始事务
T3 查询账户余额为10,000
T4 查询账户余额为10,000
T5 存入1,000并修改余额为11,000
T6 提交事务
T7 取出1,000并修改余额为9,000
T8 撤销事务并回滚余额为10,000

最终余额少了1,000。

第二类丢失更新:

时间 draw money事务 deposit money事务
T1 开始事务
T2 开始事务
T3 查询账户余额为10,000
T4 查询账户余额为10,000
T5 取出1,000并修改余额为9,000
T6 提交事务
T7 存入1,000并修改余额为11,000
T8 提交事务

最终余额多了1,000。

脏读(Dirty Read)

一个事务读取到另一个事务还没提交的数据叫脏读。

例如,事务A修改了一行数据,但没有提交,事务 B读取了被事务A修改后的数据,之后事务A因为某种原因Rollback了,那么事务B读取的数据就是脏的。

时间 draw money事务 deposit money事务
T1 开始事务
T2 开始事务
T3 查询账户余额为10,000
T4 取出1,000并修改余额为9,000
T5 查询账户余额为9,000 (脏读)
T6 撤销事务并回滚余额为10,000
T7 存入1,000并修改余额为11,000
T8 提交事务

不可重复读(NonRepeatable Read)

一个事务先后读到另一个事务提交之前的数据和已提交的更新数据。

A和B事务并发执行,A事务查询数据,然后B事务更新该数据,A再次查询该数据时,发现该数据变化了。

不可重复读经常发生在updatedelete操作。

时间 draw money事务 deposit money事务
T1 开始事务
T2 开始事务
T3 查询账户余额为10,000
T4 查询账户余额为10,000
T5 取出1,000并修改余额为9,000
T6 提交事务
T7 查询账户余额为9,000
T8 同一个事务两次查询查到的结果不同

幻读(Phantom Read)

事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据。

例如,A和B事务并发执行,A事务查询数据,B事务插入或者删除数据,A事务再次查询发现结果集中有以前没有的数据,或者以前有的数据消失了,仿佛出现了幻觉。

幻读经常发生在insert操作。

时间 draw money事务 deposit money事务
T1 开始事务
T2 开始事务
T3 查询账号数为100,000
T4 注册新账号
T5 提交事务
T6 查询账号数为100,001
T7 同一个事务两次查询查到的结果不同

事务的特性

事务有4个特性,被称为ACID,分别是:

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

原子性

一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。

事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

一致性

在事务开始之前和事务结束以后,数据库的完整性没有被破坏。

即事务前后,数据库的状态都满足所有的完整性约束。

隔离性

数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。

事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

持久性

事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

事务的隔离级别

在介绍事务的隔离性的适合提到了事务的4个隔离级别:

  • 未提交读(Read Uncommitted)
  • 已提交读(Read Committed)
  • 可重复读(Repeatable Read)
  • 可串行化(Serializable)

同时事务的隔离级别也与前面提到的5中并发问题有关。隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

隔离级别与并发问题:

隔离级别 脏读 不可重复读 幻读
未提交读 可能 可能 可能
已提交读 不可能 可能 可能
可重复读 不可能 不可能 可能
可串行化 不可能 不可能 不可能

对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免更新丢失、脏读,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

MySQL InnoDB 默认隔离级别是可重复读(Repeatable read) 。Oracle等数据库默认都是已提交读(Read Committed),即只能读取到已经提交的数据。

读未提交(Read Uncommitted)

读事务不阻塞其他读事务和写事务,未提交的写事务阻塞其他写事务但不阻塞读事务。

此隔离级别可以防止更新丢失,但不能防止脏读、不可重复读、幻读。

读已提交(Read Committed)

读事务允许其他读事务和写事务,未提交的写事务禁止其他读事务和写事务。

读未提交可以防止更新丢失、脏读,但不能防止不可重复读、幻读。

可重复读(Repeatable Read)

以操作同一行数据为前提,读事务禁止其他写事务但不阻塞读事务,未提交的写事务禁止其他读事务和写事务。

此隔离级别可以防止更新丢失、脏读、不可重复读,但不能防止幻读。

可串行化(Serializable)

提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。

此隔离级别可以防止更新丢失、脏读、不可重复读、幻读。

如果仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

事务操作

BEGIN

BEGINSTART TRANSACTION :显式开启一个事务。

COMMIT

COMMITCOMMIT WORK : 提交事务,并使已对数据库进行的所有修改成为永久性的。

ROLLBACK

ROLLBACKROLLBACK WORK : 回滚并撤销正在进行的所有未提交的修改。

SAVEPOINT

SAVEPOINT identifier : SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT。

RELEASE SAVEPOINT identifier : 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常。

ROLLBACK

ROLLBACK : 回滚事务。

ROLLBACK TO identifier : 把事务回滚到保存点。

TRANSACTION

SET TRANSACTION : 设置事务的隔离级别。

InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

AUTOCOMMIT

select @@autocommit : 查看事务自动提交设置。

set autocommit=0 : 设置事务不自动提交。

set autocommit=1 : 设置事务自动提交。


-- 查看事务自动提交设置
select @@autocommit;

-- 设置事务不自动提交
set autocommit=0;

-- 设置事务自动提交
set autocommit=1;
复制代码

事务日志

事务的机制实现很大一部依赖事务日志文件。

事务日志是一个与数据库文件分开的文件。它存储对数据库进行的所有更改,并全部记录插入、更新、删除、提交、回退和数据库模式变化。事务日志还称作前滚日志或重做日志。事务日志是备份和恢复的重要组件。

数据库锁

是数据库事务很重要的一个话题,由于篇幅原因,我们这里不展开讲了,下面我会专门提供一篇文章讲解。


Wechat-westcall

相关资源

Wechat-westcall

MySQL系列文章: