关于mysql:InnoDB-中的多版本并发控制MVCC

2次阅读

共计 2570 个字符,预计需要花费 7 分钟才能阅读完成。

MVCC(Multiversion Concurrency Control):多版本并发管制,是一个数据库设计实践,解决了读写之间阻塞的问题,使得读操作不阻塞写,写操作不阻塞读,它进步了数据库的并发能力。MVCC 是通过快照来实现的,事务启动时会对整个库拍一个快照。

简略来说,MVCC 的思维就是保留数据的历史版本,通过治理数据行的多个版本实现并发管制 ;MVCC 没有一个对立的实现规范,典型的有 乐观(optimistic)锁实现 乐观(pessimistic)锁实现

MVCC 只在 READ COMMITTED(读取已提交)REPEATABLE READ(可反复读) 两个 隔离级别 下工作,其余两个隔离级别和 MVCC 不兼容,READ UNCOMMITTED 总是读取最新的数据行,而不是合乎以后事务版本的数据行;SERIALIZABLE 则会对所有读取的行都加锁。

可反复读和读提交 在 MVCC 里的区别是:

  • 在可反复读隔离级别下,只在事务开始时创立读视图,之后事务里的其余查问都用这个读视图;
  • 在读提交隔离级别下,每一个语句执行前都会创立一个读视图;

MySQL 的 InnoDB 实现了 MVCC,InnoDB 的 MVCC 实现依赖:暗藏字段、Read View、Undo log

首先咱们来理解一下快照读和以后读:

快照读和以后读

快照读(SnapShot Read):是一种一致性不加锁的简略 SELECT 操作,读取历史版本的数据或本身插入、批改的数据;

一致性读:指的是事务读取到的数据,要么是存在的历史数据,要么是本身插入或批改的数据;

以后读(Current Read):读取到的是 最新的数据;更新数据时都是先读后写的,而这个读,只能读取以后的值,所以称为以后读,以后读必须加锁。以下 SQL 语句均应用以后读:

select ... lock in share mode
select ... for update
insert
update
delete

InnoDB 如何存储多个版本的?

首先,咱们每开启一个事务,就会取得一个事务 ID,这个 ID 是自增长的,能够通过 ID 晓得事务执行的工夫程序;

而后,InnoDB 在每行记录的前面增加了 3 个暗藏字段

  • DB_TRX_ID (6 字节):示意最近一次对 本记录行 批改(insert | update)的事务 ID,对于删除(delete)操作,InnoDB 认为是一个 update 操作,并 不会真的删除这条记录 ,而是 设置该行的删除标记位 deleted_bit,将行示意为 deleted;
  • DB_ROLL_PTR (7 字节):回滚指针,指向这个记录的 Undo log 信息;
  • DB_ROW_ID (6 字节):暗藏的枯燥递增的行 ID,如果表中没有主键或非空的惟一索引时,InnoDB 会用这个 ID 主动生成聚簇索引;这个字段与 MVCC 关系不大;

回滚指针将数据行的所有历史版本通过 链表 连接起来,每个历史版本都保留了过后的事务 ID,这样咱们能够通过遍历链表来找到历史版本;

Read View

读视图,也叫做 一致性视图(Consistent Read View),是用来做 可见性判断 的,InnoDB 为每一个事务创立了一个 数组 ,数组保留了 事务启动霎时,以后正在沉闷的事务的 ID,沉闷指的是曾经启动但未提交。

Read View 的几个重要字段:

trx_ids: 以后零碎沉闷的事务 ID 数组

low_limit_id: 创立以后 read view 时「已提交事务 ID 的最大值 + 1」

up_limit_id: 创立以后 read view 时「trx_idx 里的沉闷事务 ID 的最小值」

creator_trx_id: 创立以后 read view 的事务版本号;

在事务启动霎时会创立一个事务视图,一个数据版本对于一个事务视图来说,除了 事务本人的更新总是可见(事务 ID = creator_trx_id)以外,可见性有三种状况:

  1. 数据版本对应的事务 ID >= low_limit_id,即数据版本在事务视图创立之后提交,不可见;
  2. 数据版本对应的事务 ID < up_limit_id,即数据在事务视图创立之前就曾经存在了,可见;
  3. up_limit_id <= 数据版本对应的事务 ID < low_limit_id,此时将事务 ID 与数组 trx_ids 的元素匹配:

    • 匹配不到,阐明产生 read view 的时候事务曾经提交,可见;
    • 匹配到了,事务 ID = creator_trx_id,事务本人生成的数据,可见;
    • 匹配到了,事务 ID != creator_trx_id,阐明产生 read view 的时候事务正在沉闷,不可见;

Undo log

Undo log 保留的是事务的相同操作逻辑,当一个事务须要 读取 记录行时,如果以后记录行不可见,就会顺着 Undo log 链表找到满足其可见性条件的记录行版本;留神:Undo log 并不是真的保留历史数据,而是记录事务的相同操作,当须要回滚时就执行相同操作,从而失去历史数据。

在 InnoDB 里,Undo log 分为以下两类:

  • Insert Undo log:事务中 insert 操作产生的 Undo log,只在事务回滚时须要,在事务提交后即可删除;集体了解:因为 insert 是插入了一条新数据,不存在历史版本,所以 Undo log 也就没有存在的必要;
  • Update Undo log:事务中 update 和 delete 操作产生的 Undo log,不仅在事务回滚时须要,快照读也须要,只有当数据库所应用的快照中不波及该日志记录,对应的回滚日志才会被 purge 线程删除;

    purge 线程工作是清理 deleted_bit 为 true 的记录;

MVCC 具体实现

  1. 获取事务的 ID;
  2. 在事务启动霎时创立一个事务视图 read view;
  3. 将事务 ID 与事务视图匹配,判断查问的数据可见性,如果可见,则返回数据;
  4. 如果不可见,则到回滚日志中找到满足可见性的历史版本数据。

MVCC 解决了哪些问题?

  1. 读写之间阻塞的问题:通过 MVCC 能够让读写之间相互不阻塞,即读不阻塞写,写不阻塞读,进步了并发能力;

    进步并发的演进思路:

    • 一般锁,只能串行执行;
    • 读写锁,能够实现 读读并发
    • 多版本并发管制,能够实现 读写并发
  2. 升高了死锁的概率:在 InnoDB 中,MVCC 采纳乐观锁的形式实现,读取数据不加锁,写数据时也只锁必要的行,锁缩小了,也就升高了死锁产生的概率;

参考资料:

  1. 数据库根底(四)Innodb MVCC 实现原理
  2. MySQL 45 讲 08 讲 事务到底是隔离的还是不隔离的
正文完
 0