什么是MVCC

MVCC,全称 Multi-Version Concurrency Control ,即多版本并发控制。mvcc,它是一种并发控制方法,一般在数据库管理系统中,实现数据库的并发访问,在编程语言中实现事务内存。

总结:主要为了提升并发性能

为什么需要MVCC

数据库原生的锁
最原生的锁,锁住一个资源后会禁止其他任何线程访问同一个资源。但是很多应用的一个特点都是读多写少的场景,很多数据的读取次数远大于修改的次数,而读取数据间互相排斥显得不是很必要。

读写锁的出现
读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥。这样就很大提升了系统的并发能力。之后人们发现并发读还是不够

mvcc概念出现
能不能让读写之间也不冲突的方法,就是读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据。当然快照是一种概念模型,不同的数据库可能用不同的方式来实现这种功能

MVCC适用于的事务隔离级别

MVCC只在 READ COMMITTED (读取已提交) 和 REPEATABLE READ (可重复读) 两个隔离级别下工作。其他两个隔离级别够和MVCC不兼容, 因为 READ UNCOMMITTED (读取未提交) 总是读取最新的数据行, 而不是符合当前事务版本的数据行。而 SERIALIZABLE (可串行化) 则会对所有读取的行都加锁。

MVCC实现原理✔

MVCC的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主要是依赖记录中的 3个隐式字段undo日志 ,**Read View** 来实现的。

3个隐式字段

1
`DB_TRX_ID`, `DB_ROLL_PTR`, `DB_ROW_ID
列名 长度(字节) 作用
DB_TRX_ID 6 插入或更新行的最后一个事务的事务标识符。(删除视为更新,将其标记为已删除)
DB_ROLL_PTR 7 写入回滚段的撤销日志记录(若行已更新,则撤销日志记录包含在更新之前重建行内容所需的信息)
DB_ROW_ID 6 行标识(隐藏单调自增id)

比如:

id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
1 张三 18 1

DB_ROW_ID 是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID 是当前操作该记录的事务 ID ,而 DB_ROLL_PTR 是一个回滚指针,用于配合 undo日志,指向上一个旧版本

事务A:对数据进行了修改(将name中的张三改为李四)

  • 第一步:用排他锁锁定这一条记录
id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
1 张三 18 1
  • 第二步:UNDOLOG会记录日志,作为旧记录,既在 undo log 中有当前行的拷贝副本
UNDO_LOG
id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
1 张三 18 1
  • 第三步:将回滚指针的值copy到UNDOLOG中
UNDO_LOG
id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR(这就是存储回滚指针的值)
1 张三 18 1 ox29349384
  • 第四步:修改当前的name值并且修改隐藏字段的事务 ID 为当前事务 1的 ID, 我们默认从 1 开始,之后递增,回滚指针指向拷贝到 undo log 的副本记录,既表示我的上一个版本就是它
id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR(这就是存储回滚指针的值)
1 李四 18 1 1 ox29349384

事务B:事务A修改但未提交,同时对事务B也对该行数据做了修改

下表就是事务B做出的改变(改变的是年龄)

id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
1 张三 30 1 2 ox23874982

上表的ox23874982指的地址是下表的地址

UNDO_LOG
id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
1 张三 18 1 1 ox29349384

上表的ox29349384指的地址是下表的地址

id name age DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
1 张三 18 1

所以总结:

如果有当前事务,最早事务,最晚事务

最早事务ID<当前事务ID<最晚事务ID

  • 事务的排他锁形式修改数据
  • 修改之前先把数据放到undolog,通过回滚指针关联
  • 失败了从undolog回滚

undo日志

insert undo log
代表事务在 insert 新记录时产生的 undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃

update undo log
事务在进行 update 或 delete 时产生的 undo log ; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快速读或事务回滚不涉及该日志时,对应的日志才会被 purge 线程统一清除
在不考虑redo log 的情况下利用undo log工作的简化过程为:

序号 动作
1 开始事务
2 记录数据行数据快照到undolog
3 更新数据
4 将undolog写到磁盘
5 将数据写到磁盘
6 提交事务

1)为了保证数据的持久性数据要在事务提交之前持久化

2)undo log的持久化必须在在数据持久化之前,这样才能保证系统崩溃时,可以用undo log来回滚事务

执行流程如下:

*一、比如一个有个事务插入 persion 表插入了一条新记录,记录如下,name 为 小明 , age 为 10 岁,*隐式主键*是 1,*事务 ID*和*回滚指针*,我们假设为 NULL*

image-20220830171156856

二、 现在来了一个事务 1对该记录的 name 做出了修改,改为 小红

  • 事务 1修改该行(记录)数据时,数据库会先对该行加排他锁

image-20220830171325637

  • 然后把该行数据拷贝到 undo log 中,作为旧记录,既在 undo log 中有当前行的拷贝副本

Undo日志

image-20220830172747995

  • 拷贝完毕后,修改该行name为小红,并且修改隐藏字段的事务 ID 为当前事务 1的 ID, 我们默认从 1 开始,之后递增,回滚指针指向拷贝到 undo log 的副本记录,既表示我的上一个版本就是它

    image-20220830172826227

    上个表的回滚指针地址指的是下个表

    image-20220830172852127

  • 事务提交后,释放锁