什么是数据库中的MVCC


脏读、幻读是什么,MVCC?

脏读/幻读

答:脏读,是指读到了脏数据,比方说一个事务A,执行了修改了个数据,但是还没有提交;这时候事务B来读取到了事务A修改后的这个数据,然后A发生错误回滚了,这时候这个事务B的那个数据就是脏数据。

幻读是指对表的数据,比方说插入了几条数据,这时候另一个事务来了读取了这几条数据,然后这几条数据又回滚或者删除了,另一个事务读到的数据就好像是幻影好像不存在。(不对)

三种数据不一致

幻读就是你一个事务用一样的 SQL 多次查询,结果每次查询都会发现查到一些之前没看到过的数据。注意,幻读特指的是你查询到了之前查询没看到过的数据。此时说明你是幻读了

MVCC

多版本并发控制

乐观锁和 MVCC 的区别? - 知乎 (zhihu.com)

Mysql中MVCC的使用及原理详解 - 开顺 - 博客园 (cnblogs.com)

当多个用户/进程/线程同时对数据库进行操作时,会出现3种冲突情形:

  1. 读-读,不存在任何问题
  2. 读-写,有隔离性问题,可能遇到脏读(会读到未提交的数据) ,幻影读等。
  3. 写-写,可能丢失更新

多版本并发控制(MVCC)是一种用来 解决读-写冲突 的无锁并发控制,
也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,
读操作只读该事务开始前的数据库的快照

这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读

(简单回顾,脏读是读到未提交事务的数据,不可重复读是两次读数据之间另一个事务修改了此数据)

MVCC实现:版本链 + ReadView

  • 版本链:由两个隐藏列实现, trx_idroll_pointer
    • trx_id : 对此行数据执行操作的事务ID(事务ID是不断增长的一个ID)
    • roll_pointer :一个回滚指针。每条数据修改时,老版本会存在 Undo 日志中。这个 roll_pointer 就是指向当前数据的老版本的位置。
  • ReadView:存储当前正在执行的事务ID,也就是还未提交的事务的ID

读数据过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 获取当前数据的最新trx_id
TrxId currentRowTrxId = table.getRow().getTrxId();
// 如果trx_id < readView中的所有 trx_id
// 表明这条数据的修改事务已经完成,当前正在执行的事务可以直接访问它
// 避免了脏读
if(currentRowTrxId < readview.allId()){
访问;
}else{
// 表示当前数据的修改,是当前事务的后面的新事务修改
// 这时候当前事务读到的是老的数据
RollPointer rollpointer = table.getRow().getRollPointer();
// 读取上N个版本的
while(TrxId >= readView[rollPointer]){
rollPointer = rollPointer.getRow().getRollPointer();
}
}

举例:

  • 当前行数据 row 的 trx_id == 10,正在执行的事务T要来读取这个row
    • 正在执行事务 T_ID = 11,那么 readView == [11];
      判断 row_trx_id 与 readView_ID : 10 < 11 ;
      说明 row 这个数据修改的已经是在事务11之前完成,可以读取最新的row。
    • 正在执行的当前事务T_ID == 9,那么readView == [9];
      判断 row_trx_id 与 readView_ID : 9 < 10;
      9不能读取row最新的,而是去读取上一个版本的(或者上N个版本的)。

总结:

  • 解决了读-写的问题,避免了脏读和不可重复读
  • 通过版本链 + ReadView实现
  • 版本链通过 隐藏行(trx_id 和 roll_pointer) 和 Undo 日志实现
    • row_trx_id < T_ID (ReadView_ID):当前行为最新,可以读取
    • 否则,去Undo日志中读取旧版本

举例

注意:InnoDB中的MVCC

在MySQL的InnoDB引擎中,是由版本号,create_vesion,delete_version实现的。

Mysql中MVCC的使用及原理详解 - 开顺 - 博客园 (cnblogs.com)

这里的版本号并不是实际的时间值,而是系统版本号。

每开始新的事务,系统版本号都会自动递增。

事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。

每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。

  1. 插入:设定 create_version
  2. 删除:设定 delete_version
  3. 更新:新纪录设定 craete_version,旧记录保存并设定 delete_version = current_version
  4. 读取:比较当前版本号和 create_version 大小,然后根据 delete_version 去查找旧记录:
    1. create_version,保证当前事务所在的版本读取不到新的数据(即事务id为2的事务只能读取到create version<=2的已提交的事务的数据集)
    2. delete_version,保证当前事务读取的,在当前事务版本未被删除(即上述事务id为2的事务查询时,依然能读取到事务id为3所删除的数据行)

MVCC简介 - 绿洲2017 - 博客园 (cnblogs.com)


文章作者: SongX64
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 SongX64 !
  目录