数据库事务
定义
事务指的是一组数据库操作要么全部成功,要么全部失败。如果出现部分成功,那么将造成数据不一致等情况。
重点概念
事务特性(ACID):
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
隔离性和隔离级别
多个事务同时执行,就可能出现脏读、不可重复读、幻读的问题,于是就有了隔离级别。隔离得越严实,效率就会越低。隔离级别从低到高分为:读未提交、读已提交、可重复读、串行化(读写加锁)四种,依次解决上面提到的三种问题。
- 读未提交:事务未提交前,它做的变更在其它事务中可见。
- 读已提交:事务提交后,它做的变更才能在其它事务中可见。
- 可重复读:事务执行过程看到的所有数据,总是跟它启动时看到的一致。
- 串行化:对同一行数据,读写都加锁,发生锁冲突时,后面的事务等前面执行完才能执行。
示例:
事务A | 事务B | |
---|---|---|
1 | 启动事务 | 启动事务 |
2 | 查询得到值为1 | 查询得到值为1 |
3 | 将1改为2 | |
4 | 查询得到值 v1 | |
5 | 提交事务 | |
6 | 查询得到值 v2 | |
7 | 提交事务 | |
8 | 查询得到值 v3 |
下面我们看看不同隔离级别下,v1、v2、v3 分别是多少
读未提交:v1
是2,虽然B未提交但结果对A可见,v2
、v3
也是2。
读已提交:v1
是1,v2
、v3
才是2。
可重复读:v1、v2
是1,事务A未提交前看到的数据保持一致,v3
才是2。
串行化:事务A查询会加锁,事务B执行更新被锁住,直到A提交后B才能继续执行。因此v1、v2
是1,v3
是2
小结:
- 隔离级别实现上离不开视图创建,读已提交在每个SQL语句开始执行时创建;可重复读在事务启动时创建。
- 读未提交直接返回记录上的最新值,没有视图概念;串行化使用加锁避免并行访问。
MySQL事务
MySQL是一个支持多引擎系统,但不是所有引擎都支持事务。
事务支持是在引擎层实现的,我们以常见的 InnoDB 引擎为例。
MySQL实现隔离级别
MySQL通过回滚日志实现”可重复读“隔离级别。
每一条记录在更新时会记录一条回滚操作,记录上最新值可通过回滚,得到前一个状态值。
不同的事务中的视图中,同一条记录可能存在多个版本,这就是数据库的多版本并发控制(MVCC)
MVCC
同一行记录存在多个版本,不同事务的快照(视图)是如何选取自己需要的版本呢?
- 每个事务在开始时向InnoDB申请一个的事务id,这个id是严格按照申请顺序递增的
- 每个数据版本都是由事务更新生成的,版本号就是对应的事务id,它说明了这个版本由谁生成
- 一行记录除了最新版本,其它旧版本都不是物理上真实存在的,都是通过当前版本和undo log计算得到
- 数据版本对事务视图是否可见总结如下:除了自己更新的总是可见
- 版本未提交,不可见;
- 版本已提交,但是是在视图创建后提交的,不可见;
- 版本已提交,而且是在视图创建前提交的,可见。
InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。
MVCC也只适用于有视图概念的隔离级别
更新操作
RR(可重复读)隔离级别,如果只是查询操作,那么在事务执行期间会一直使用快照视图。
如果需要更新数据,能在视图基础上更新吗?答案是不能的,因为会丢失掉其它事务的更新(其它版本)
因此更新操作不能使用一致性读,而需要当前读(最新版本),当前读需要拿锁访问。所以不同事务更新相同行,由于行锁冲突进入锁等待。
注意:
如果更新数据失败,即该数据最新版本号非当前事务id,则继续查询该数据依然拿的是一致性视图
这就是一致性读、当前读、行锁之间的关系。