脏读、幻读是什么,MVCC?
脏读/幻读
答:脏读,是指读到了脏数据,比方说一个事务A,执行了修改了个数据,但是还没有提交;这时候事务B来读取到了事务A修改后的这个数据,然后A发生错误回滚了,这时候这个事务B的那个数据就是脏数据。
幻读是指对表的数据,比方说插入了几条数据,这时候另一个事务来了读取了这几条数据,然后这几条数据又回滚或者删除了,另一个事务读到的数据就好像是幻影好像不存在。(不对)
幻读就是你一个事务用一样的 SQL 多次查询,结果每次查询都会发现查到一些之前没看到过的数据。注意,幻读特指的是你查询到了之前查询没看到过的数据。此时说明你是幻读了
MVCC
多版本并发控制
当多个用户/进程/线程同时对数据库进行操作时,会出现3种冲突情形:
- 读-读,不存在任何问题
- 读-写,有隔离性问题,可能遇到脏读(会读到未提交的数据) ,幻影读等。
- 写-写,可能丢失更新
多版本并发控制(MVCC)是一种用来 解决读-写冲突 的无锁并发控制,
也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,
读操作只读该事务开始前的数据库的快照。
这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读
(简单回顾,脏读是读到未提交事务的数据,不可重复读是两次读数据之间另一个事务修改了此数据)
MVCC实现:版本链 + ReadView
- 版本链:由两个隐藏列实现,
trx_id
和roll_pointer
- trx_id : 对此行数据执行操作的事务ID(事务ID是不断增长的一个ID)
- roll_pointer :一个回滚指针。每条数据修改时,老版本会存在 Undo 日志中。这个 roll_pointer 就是指向当前数据的老版本的位置。
- ReadView:存储当前正在执行的事务ID,也就是还未提交的事务的ID
读数据过程:
1 | // 获取当前数据的最新trx_id |
举例:
- 当前行数据 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个版本的)。
- 正在执行事务 T_ID = 11,那么 readView == [11];
总结:
- 解决了读-写的问题,避免了脏读和不可重复读
- 通过版本链 + 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操作时,就通过版本号的比较来达到数据版本控制的目的。
- 插入:设定
create_version
; - 删除:设定
delete_version
; - 更新:新纪录设定
craete_version
,旧记录保存并设定delete_version = current_version
; - 读取:比较当前版本号和
create_version
大小,然后根据delete_version
去查找旧记录:-
create_version
,保证当前事务所在的版本读取不到新的数据(即事务id为2的事务只能读取到create version<=2的已提交的事务的数据集) -
delete_version
,保证当前事务读取的,在当前事务版本未被删除(即上述事务id为2的事务查询时,依然能读取到事务id为3所删除的数据行)
-