MySQL实战45讲-08 事务到底是隔离的还是不隔离的?

如下示例中,事务 B 查到的 k 的值是 3,而事务 A 查到的 k 的值是 1。为什么?

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `k` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;
insert into t(id, k) values(1,1),(2,2);

注释:

  • 事务的启动时机
  1. begin/start transaction 命令并不是一个事务的起点,在执行到它们之后的第一个操作 InnoDB 表的语句,事务才真正启动。
  2. 如果你想要马上启动一个事务,可以使用 start transaction with consistent snapshot 这个命令。
  • 例子中如果没有特别说明,都是默认 autocommit=1。
  • “视图”的概念:
  1. 一个是 view。它是一个用查询语句定义的虚拟表,在调用的时候执行查询语句并生成结果。创建视图的语法是 create view … ,而它的查询方法与表一样。
  2. 另一个是 InnoDB 在实现 MVCC 时用到的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别的实现。

“快照”在 MVCC 里是怎么工作的?

InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id

数据表中的一行记录,其实可能有多个版本 (row),每个版本有自己的 row trx_id。如图 2 所示,就是一个记录被多个事务连续更新后的状态。

其中U1、U2、U3是undo log;V1、V2、V3都是通过当前版本V4和undo log计算出来的。

InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。

事务 A 查到的 k 的值是 1。为什么?

InnoDB 为每个事务构造了一个数组,用来保存这个事务启动瞬间,当前启动了但还没提交的所有事务 ID。

  • 如果一个数据版本是在当前事务启动之前生成的,就认;如果是在当前事务以后生成的,就不认,必须要找到它的上一个版本”。
  • 如果是当前事务自己更新的数据,它自己还是要认的。

也即,一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:

  • 版本未提交,不可见;
  • 版本已提交,但是是在视图创建后提交的,不可见;
  • 版本已提交,而且是在视图创建前提交的,可见。

事务 B 查到的 k 的值是 3,为什么?

更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。

除了 update 语句外,select 语句如果加锁,也是当前读。下面这两个 select 语句,就是分别加了读锁(S 锁,共享锁)和写锁(X 锁,排他锁)。

mysql> select k from t where id=1 lock in share mode;
mysql> select k from t where id=1 for update;

假设事务 C' 不是马上提交的,而是在事务B之后提交,会怎么样?

此时,章节07 行锁功过:怎么减少行锁对性能的影响?“两阶段锁协议”生效。事务 C’没提交,也就是说 (1,2) 这个版本上的写锁还没释放。而事务 B 是当前读,必须要读最新版本,而且必须加锁,因此就被锁住了,必须等到事务 C’释放这个锁,才能继续它的当前读。

事务的可重复读的能力是怎么实现的?

可重复读的核心就是一致性读(consistent read);而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就需要进入锁等待。

假设还是事务A、B、C(不是C'),但是隔离级别是读提交

在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。每个视图只管自己一个语句。

事务 B 查询结果 k=3。

对于事务 A:

  • (1,3) 还没提交,属于情况 1,不可见;
  • (1,2) 提交了,属于情况 3,可见。
posted @ 2020-09-09 10:24  lee2guang  阅读(144)  评论(0编辑  收藏  举报