mvcc浅析

在讲Mvcc前,我觉得有必要先了解一下InnoDB的行锁。mysql从5.5.5开始使用InnoDB作为默认存储引擎,lock的对象是事务,用来锁定的是数据库中的对象,如表、页、行。并且一般lock的对象仅在事务commit或rollback后进行释放(不同事务隔离级别释放的时间可能不同)。InnoDB存储引擎实现了如下两种标准的行锁:

  1. 共享锁(S Lock)允许事务读一行数据
  2. 排他锁(X Lock)允许事务删除或更新一行数据

说明:

  • 读锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上S锁之前不能对A做任何修改。
  • 写锁、X锁,若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁,这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

一致性非锁定读

一致性的非锁定读(consistent nonlocking read )是指InnoDB通过行多版本控制(multi versioning)的方式来读取当前被锁定行的数据。简单的来说,如果需要读取的行正在执行DELETE或UPDATE操作(X lock),这时读取操作不会因此去等待行上锁的释放,相反地,InnoDB会去读取行的一个快照数据。如下图:

图中的快照数据是指该行的之前版本的数据,是通过undo log来实现的,后面说。可以看到,非锁定读极大提高了数据库的并发性。该方式是InnoDB默认的读取方式,即读取不会占用和等待表上的锁。但在不同的事务隔离级别下,读取的方式不同,并不是在每个事务隔离级别都是采用非锁定的一致性读。此外,即使都是采用非锁定的一致性读,但是对快照数据的定义也各不相同。
上图可以知道,快照数据其实就是当前行数据之前的历史版本,每行记录可能有多个版本。一般称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(
mvccMulti Version Concurrency Control,MVCC)。

在事务隔离级别READ COMMITTED 和REPEATABLE READ (InnoDB默认隔离级别)下,InnoDB存储引擎使用非锁定一致性读。然而,对于快照数据的定义却不同。在READ COMMITTED事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新的一份快照数据。而在REPPEATABLE READ 事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。(通过readview实现,后面会讲)

一致性锁定读

在某些情况下,用户需要显式地对数据库读取操作进行枷锁以保证数据逻辑的一致性
显示地对数据库读取操作进行加锁:

1
2
3
4
5
// X 锁
select ··· for update

// S 锁
select ··· lock in share mode

select ··· for update对读取的行记录加一个X锁,其他的事务不能对已锁定的行加上任何锁。select ··· lock in share mode 对读取的行记录加上一个S锁,其他事务可以向被锁定的行加S锁,但如果加X锁,则会被阻塞。
需要注意的是,以上两种锁,对于一致性非锁定读,都是可以进行读取的。另外,以上两种锁必须在一个事务中,当事务提交了,锁也就释放了。因此在使用上述两句select锁定语句时,必须加上BEGIN,START TRANSACTION 或者SET AUTOCOMMIT=0。

mvcc实现浅析

在InnoDB中,没一行都有两个隐藏列DATA_TRX_ID和DATA_ROLL_PTR(如果没有定义主键,则还有个隐藏主键列ROWID):

  • DATA_TRX_ID: 表示最近修改的事务的id
  • DATA_ ROLL_PTR: 表示指向该行回滚段(undo segment 中的 undo log)的指针,该行上所有旧的版本,在undo中都通过链表的形式组织,而该值,正式指向undo中该行的历史记录链表

事务链表

MySQL中的事务在开始到提交这段过程中,都会被保存到一个叫trx_sys的事务链表中,这是一个基本的链表结构:

事务链表中保存的都是还未提交的事务,事务一旦被提交,则会被从事务链表中摘除。

ReadView

未完待续。。。


mvcc浅析
https://www.weypage.com/2020/02/22/mysql/mvcc浅析/
作者
weylan
发布于
2020年2月22日
许可协议