MySQL之MVCC实现原理

2022-06-20 20:09:58 浏览数 (1)

SQL 标准的事务隔离级别包括四种:

读未提交

一个事务还没提交时, 它做的变更就能被别的事务看到;

带来的问题是: 脏读, 读取到了未提交的数据.

读已提交

一个事务提交之后, 它做的变更才会被其他事务看到;

带来的问题是: 数据不可重复读.

可重复读

一个事务执行过程中看到的数据, 总是跟这个事务在启动时看到的数据是一致的;当然在可重复读隔离级别下, 未提交变更对其他事务也是不可见的.

带来的问题: 幻读, 当一个事务读取数据行后, 另一个事务又修改了该数据, 第一个事务再次读取该数据行时, 出现数据两次读取不一致的情况.

串行化读

当出现读写锁冲突的时候, 后访问的事务必须等前一个事务执行完成, 才能继续执行.

MySQL默认的事务级别就是可重复读的, 那我们平时用的时候, 怎么没出现幻读的问题呢?

那是因为 MySQL实现了多版本并发控制(MVCC).

MVCC又是怎么样实现的呢?

多版本并发控制MVCC

在 MySQL 中, MVCC 是利用回滚日志(undo log)和事务ID(txID) 配合实现的.

实际上每条数据更新时, 都会同时将原数据记录到回滚日志(undo log)中. 通过回滚操作, 都可以得到前一个事务对应的值.

而每次记录的数据, 就被称为 read view, 也就是一个只读的视图. 里面不仅有数据信息, 还有事务版本信息.

假设一个值从 1 被按顺序改成了 2, 3, 在回滚日志里面就会有类似下面的记录.

如果每个事务请求又是怎么从回滚日志中找到对应的 read-view的呢?

这就要从数据行说起了.

数据行隐藏字段

每行数据除了我们自定义的字段外, 还有数据库隐式定义的一些字段.

DB_TRX_ID 6b, 最近修改(修改/插入)事务ID, 记录创建这条记录/最后一次修改该记录的事务ID

DB_ROLL_PTR 7b, 回滚指针, 指向这条记录的上一个版本信息(存储在回滚日志中)

DB_ROW_ID 6b, 隐含的自增ID(隐藏主键), 如果数据表没有主键, InnoDB会自动以DB_ROW_ID产生一个聚簇索引.

Info flags

4b, 包括删除flag等记录数据行状态字段

对照这些信息, 再看上面的read view, 就能发现MVCC的实现机制了.

在执行select时, 就是使用这些隐藏列配合查找对应数据的.

Innodb只查找对应事务ID(DB_TRX_ID)的数据行; 当前数据行事务ID太高, 就到回滚日志中查找对应数据.

其他操作也会对这些隐藏字段进行维护和修改:

insert

新插入的数据保存当前事务ID(DB_TRX_ID).

delete

记录当前事务ID(DB_TRX_ID)和删除标识.

update

转变为insert和delete组合, insert的数据保存当前事务ID;

delete数据, 记录当前事务ID(DB_TRX_ID)和删除标识.

注意: 由于旧数据并不真正的删除, 所以必须对这些数据进行清理, Innodb会开启一个后台线程, 将删除事务ID小于当前系统版本的数据行, 这个过程叫做purge.

0 人点赞