Contents

数据库-mysql-小林coding-事务篇

本系列笔记为作者在跟随小林coding学习的时候做的笔记。感谢小林大大。

事务隔离级别是怎么实现的

事务有哪些特性

ACID

  1. 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成。undo log(回滚日志)保证
  2. 一致性(Consistency):是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。通过持久性+原子性+隔离性来保证
  3. 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力。通过 MVCC(多版本并发控制) 或锁机制来保证的
  4. 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。redo log (重做日志)保证

并行事务会引发什么问题

脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)

脏读:一个事务「读到」了另一个「未提交事务修改过的数据」

不可重复读:在一个事务内多次读取同一个数据,出现前后两次读到的数据不一样的情况,重在数值。

幻读:在一个事务内多次查询某个符合查询条件的「记录数量」,出现前后两次查询到的记录数量不一样的情况,重在数量。

事务的隔离级别有哪些

并发问题严重性排序

https://cdn.xiaolincoding.com//mysql/other/d37bfa1678eb71ae7e33dc8f211d1ec1.png
  • 读未提交(read uncommitted),事务还没提交时,它做的变更就能被其他事务看到;
  • 读提交(read committed),事务提交之后,它做的变更才能被其他事务看到;
  • 可重复读(repeatable read),事务执行过程中看到的数据,跟这个事务启动时看到的数据是一致的,MySQL InnoDB 引擎的默认隔离级别;
  • 串行化(serializable );对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;

按隔离水平高低排序

https://cdn.xiaolincoding.com//mysql/other/cce766a69dea725cd8f19b90db2d0430.png

针对不同的隔离级别,并发事务时可能发生的现象也会不同

https://cdn.xiaolincoding.com//mysql/other/4e98ea2e60923b969790898565b4d643.png

MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了,详见这篇文章

  • 针对快照读(普通 select 语句),通过 MVCC 方式解决了幻读。
  • 针对当前读(select … for update 等语句),是通过 next-key lock(临界锁=记录锁+间隙锁)方式解决了幻读。

四种隔离级别具体是如何实现

  • 「读未提交」:直接读取最新的数据就好了
  • 「读提交」和「可重复读」:通过 Read View 来实现的,区别在于创建 Read View 的时机不同,「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 Read View,然后整个事务期间都在用这个 Read View
  • 「串行化」:加读写锁的方式来避免并行访问

注意,执行「开始事务」命令,并不意味着启动了事务。在 MySQL 有两种开启事务的命令,分别是:

  • begin/start transaction:需要执行增删改查命令才开始事务
  • start transaction with consistent snapshot 命令:直接开启事务

Read View 在 MVCC 里如何工作的

https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/mysql/%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB/readview%E7%BB%93%E6%9E%84.drawio.png

https://cdn.xiaolincoding.com//mysql/other/f595d13450878acd04affa82731f76c5.png

InnoDB 存储引擎的数据库表的聚簇索引记录中包含下面两个隐藏列:

  • trx_id,上一次改变该记录的事务id
  • roll_pointer,undolog中上一次改变之前的记录

在创建 Read View 后,我们可以将记录中的 trx_id 划分这三种情况:

https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/mysql/%E4%BA%8B%E5%8A%A1%E9%9A%94%E7%A6%BB/ReadView.drawio.png

一个事务去访问记录的时候,除了自己的更新记录总是可见之外,还有这几种情况,不可见时查询上一个版本:

  • 记录的 trx_id < Read View 中的 min_trx_id,可见。
  • 记录的 trx_id > Read View 中的 max_trx_id,不可见。
  • 记录的 trx_id 值在 Read View 的 min_trx_id 和 max_trx_id 之间:
    • 记录的 trx_id 在 m_ids 列表中,不可见。
    • 记录的 trx_id 不在 m_ids列表中,可见。

这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制)

可重复读是如何工作的

可重复读隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View。

读提交是如何工作的

读提交隔离级别是在每次读取数据时,都会生成一个新的 Read View。

MySQL 可重复读隔离级别,完全解决幻读了吗

快照读是如何避免幻读的

MVCC(多版本并发控制)

当前读是如何避免幻读的

MySQL 里除了普通查询是快照读,其他都是当前读,比如 update、insert、delete

next-key lock。

幻读被完全解决了吗

可重复读隔离级别下虽然很大程度上避免了幻读,但是还是没有能完全解决幻读。

场景:A进行快照读->B插入记录->A当前读插入的数据

要避免这类特殊场景下发生幻读的现象的话,就是尽量在开启事务之后,马上执行 select … for update 这类当前读的语句,因为它会对记录加 next-key lock,从而避免其他事务插入一条新记录。

事务类型

  • 扁平事务:是事务类型中最简单的一种,而在实际生产环境中,这可能是使用最为频繁的事务。在扁平事务中,所有操作都处于同一层次,其由BEGIN WORK开始,由COMMIT WORK或ROLLBACK WORK结束。处于之间的操作是原子的,要么都执行,要么都回滚。
  • 带有保存点的扁平事务:除了支持扁平事务支持的操作外,允许在事务执行过程中回滚到同一事务中较早的一个状态,这是因为可能某些事务在执行过程中出现的错误并不会对所有的操作都无效,放弃整个事务不合乎要求,开销也太大。保存点(savepoint)用来通知系统应该记住事务当前的状态,以便以后发生错误时,事务能回到该状态。
  • 链事务:可视为保存点模式的一个变种。链事务的思想是:在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下一个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的。
  • 嵌套事务:是一个层次结构框架。有一个顶层事务(top-level transaction)控制着各个层次的事务。顶层事务之下嵌套的事务被称为子事务(subtransaction),其控制每一个局部的变换。
  • 分布式事务:通常是一个在分布式环境下运行的扁平事务,因此需要根据数据所在位置访问网络中的不同节点。对于分布式事务,同样需要满足ACID特性,要么都发生,要么都失效。

对于MySQL的InnoDB存储引擎来说,它支持扁平事务、带有保存点的扁平事务、链事务、分布式事务。对于嵌套事务,MySQL数据库并不是原生的,因此对于有并行事务需求的用户来说MySQL就无能为力了,但是用户可以通过带有保存点的事务来模拟串行的嵌套事务。

 |