• MySQL可重复读隔离级别为何没有解决幻读(MVCC原理简介)

    • 一.MCVV简介
    • 二.可重复读隔离级别能解决幻读?
    • 三.什么是当前读和快照读?
    • 四.MVCC的实现原理
    • 五.RC,RR级别下的InnoDB快照读有什么不同?
    • 六.如何解决幻读
    • 七.事务是么时候开始

MySQL可重复读隔离级别为何没有解决幻读(MVCC原理简介)

一.MCVV简介

多版本并发控制(Multi-Version Concurrency Control, MVCC)是MySQL中基于乐观锁理论实现隔离级别的方式,在mysql的innodb中,在读已提交和可重复读取隔离级别会使用mvcc来提升并发。

二.可重复读隔离级别能解决幻读?

先说答案,可重复读没有完美的解决幻读,对于select(快照读)不会产生幻读,但对于update(当前读)会产生幻读,下面通过案例进行分析。

测试:

准备工作:

  • 1.创建一张测试用的book表:
DROP TABLE IF EXISTS `book`;CREATE TABLE `book` (`id` bigint(20) NOT NULL AUTO_INCREMENT,`book_name` varchar(256) COLLATE utf8_bin NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;insert into book values (1, 'java');
  • 2.设置事务隔离级别为可重复读:
    set session transaction isolation level 事务隔离级别
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;//设置read committed级别:
set session transaction isolation level read committed;//设置repeatable read级别:
set session transaction isolation level repeatable read;//设置serializable级别:
set session transaction isolation level serializable;
  • 3.查看当前事务隔离级别:
    SELECT @@tx_isolation;
mysql> select @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ        |
+------------------------+
1 row in set, 1 warning (0.00 sec)

测试1

事务1 事务2
begin begin
select * from book; \
\ insert into book values (2, ‘python’);
\ commit
select * from book; \
commit \

事务1

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> select * from book;
+----+-----------+
| id | book_name |
+----+-----------+
|  1 | java      |
+----+-----------+
1 row in set (0.00 sec)mysql> select * from book;
+----+-----------+
| id | book_name |
+----+-----------+
|  1 | java      |
+----+-----------+
1 row in set (0.00 sec)mysql> commit;
Query OK, 0 rows affected (0.00 sec)

事务2

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> insert into book values (2, 'python');
Query OK, 1 row affected (0.01 sec)mysql> commit;
Query OK, 0 rows affected (0.00 sec)

根据上面的流程执行,预期来说应该是事物1的第一条select查询出一条数据,第二个select查询出两条数据(包含事物2提交的数据)。

但是在实际测试中发现第二条select实际上也只查询处理到一条数据。

从上面的测试结果来看,貌似在MySQL中通过MVCC就解决了幻读的问题,那既然这样串行化读貌似就没啥意义了,带着疑问继续测试。

测试2

测试前数据:

mysql> select * from book;
+----+-----------+
| id | book_name |
+----+-----------+
|  1 | java      |
+----+-----------+
事务1 事务2
begin begin
select * from book; \
\ insert into book values (2, “go”);
\ commit
update book set book_name = “python”(工作中如果不想被辞退一定要写where条件) \
commit \

事务1

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> select * from book;
+----+-----------+
| id | book_name |
+----+-----------+
|  1 | java      |
+----+-----------+
1 row in set (0.00 sec)mysql> update book set book_name = "python";
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2  Changed: 2  Warnings: 0mysql> commit;
Query OK, 0 rows affected (0.01 sec)

事务2

mysql> begin;
Query OK, 0 rows affected (0.00 sec)mysql> insert into book values (2, "go");
Query OK, 1 row affected (0.00 sec)mysql> commit;
Query OK, 0 rows affected (0.00 sec)

根据上面的结果我们期望的结果是这样的:

+----+-----------+
| id | book_name |
+----+-----------+
|  1 | python    |
+----+-----------+
|  2 | go        |
+----+-----------+

但是实际上我们的经过是:

+----+-----------+
| id | book_name |
+----+-----------+
|  1 | python    |
|  2 | python    |
+----+-----------+

本来我们希望得到的结果只是第一条数据的book_name改为python,但是结果却是两条数据都被修改了。这种结果告诉我们其实在MySQL可重复读的隔离级别中并不是完全解决了幻读的问题,而是解决了读数据情况下的幻读问题。而对于修改的操作依旧存在幻读问题,就是说MVCC对于幻读的解决时不彻底的。

备注

可以通过开启两个mysql client 来模拟以上测试

三.什么是当前读和快照读?

在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读和快照读?

  • 当前读

像select lock in share mode(共享锁), select for update(排他锁); update, insert ,delete这些操作都是一种当前读,为什么叫当前读?就是它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁

  • 快照读

像不加锁的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本

四.MVCC的实现原理

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

隐式字段

每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段

  • DB_TRX_ID
    6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
  • DB_ROLL_PTR
    7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
  • DB_ROW_ID
    6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
  • 实际还有一个删除flag隐藏字段, 既记录被更新或删除并不代表真的删除,而是删除flag变了

undo日志

undo log主要分为两种:

  • insert undo log
    代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
  • update undo log
    事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除,只有在快照读或事务回滚不涉及该日志时,对应的日志才会被purge线程统一清除

对MVCC有帮助的实质是update undo log

Read View(读视图)

什么是Read View,说白了Read View就是事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID(当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大)

所以我们知道 Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。

Read View遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本

五.RC,RR级别下的InnoDB快照读有什么不同?

正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同

  • 在RR级别下的某个事务的对某条记录的第一次快照读会创建一个快照及Read View, 将当前系统活跃的其他事务记录起来,此后在调用快照读的时候,还是使用的是同一个Read View,所以只要当前事务在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
  • 即RR级别下,快照读生成Read View时,Read View会记录此时所有其他活动事务的快照,这些事务的修改对于当前事务都是不可见的。而早于Read View创建的事务所做的修改均是可见
  • 而在RC级别下的,事务中,每次快照读都会新生成一个快照和Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因

总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个事务中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。

六.如何解决幻读

很明显可重复读的隔离级别没有办法彻底的解决幻读的问题,如果我们的项目中需要解决幻读的话也有两个办法:

1.使用串行化读的隔离级别
2.MVCC + 间隙锁

mysql的间隙所是基于索引的,对于唯一索引innode会把间隙所降级为行锁,非唯一索引的话就需要用到间隙锁(也叫范围锁)

id number
1 1
2 3
13 3
23 3
31 11
40 40

事务一:select * from test where number = 3 for update
对于number索引可以分为多个范围
(无穷小,1)(1,3)(3,3)(3,11)(11,40)(40,无穷大)
这时候锁住的是(3,3)区间,对应的临界记录是(id=1,number=1)(id=31,number=11),对于这范围内的数据都是被锁住的。

事务二:insert into test(id, number) value(5, 3) //是会被阻塞
事务三:insert into test(id, number) value(25, 4) //也是会被阻塞
事务四:insert into test(id, number) value(35, 4) //也是会被阻塞
事务五:insert into test(id, number) value(22, 12) //插入成功 (因为12>11所以在锁区间外)
事务六:insert into test(id, number) value(71, 11) //插入成功 (number值一样,但是id71>31所以在锁区间外)

七.事务是么时候开始

BEGIN 语句并没有开启一个事务,实际上是对数据进行了增删改查等操作后才开启了一个事务。

引用:
1.https://www.cnblogs.com/liyus/p/10556563.html
2.https://zhuanlan.zhihu.com/p/64576887
3.https://blog.csdn.net/SnailMann/article/details/94724197

MySQL可重复读隔离级别为何没有解决幻读(MVCC原理简介)相关推荐

  1. Mysql可重复读隔离级别下如何解决幻读

    Mysql可重复读隔离级别下如何解决幻读 一些概念 具体加锁说明 使用主键索引进行等值查询 使用主键索引进行范围查询 使用二级索引进行等值查询 使用二级索引进行范围查询 一些概念 幻读:在一次事务中, ...

  2. MySQL 可重复读隔离级别,完全解决幻读了吗?

    我在上一篇文章中提到,MySQL InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它很大程度上避免幻读现象(并不是完全解决了),解决的方案有两种: 针对快照读(普通 select 语句),是通 ...

  3. 深入学习InnoDB可重复读隔离级别下如何避免幻读

    一.InnoDB可重复读隔离级别下如何避免幻读 在理解什么是幻读之前,先了解下脏读.幻读.不可重复读在实操场景中的现象. 脏读:指的就是一个事务读取到了另一个事务还未提交的数据,当该事物将数据回滚,则 ...

  4. 数据库之InnoDB可重复读隔离级别下如何避免幻读

    文章目录 一.先介绍几个概念 1.什么是当前读 2.什么是快照读 3.什么是mvcc 二.RR级别下避免幻读的方法 三.RC级别下测试快照读和当前读 3.1.测试快照读 3.2.测试当前读 四.RR级 ...

  5. Mysql在可重复读事务隔离级别下怎么解决幻读的

    目录 前言 并发事务产生的问题 更新丢失 回滚丢失 覆盖丢失 脏读 不可重复读 幻读 快照读和当前读 幻读验证 快照读如何避免幻读 当前读如何避免幻读 可重复读隔离级别发生幻读情况 小结 前言 Mys ...

  6. MySQL可重复读隔离级别能解决幻读吗?

    事务及事务隔离级别 innodb存储引擎支持事务,myisam不支持事务 事务内的操作要么全部成功,要么全部失败,中途有失败则回滚 事务的ACID:原子性,一致性,隔离性,持久性 事务隔离级别需要解决 ...

  7. MySQL 可重复读隔离级别与幻读

    在MySQL可重复读的隔离级别下,能很大程度上避免幻读,而不能完全避免. 场景复现 环境信息: MySQL版本:5.7.23-log 隔离级别:REPEATABLE-READ 测试数据: SET NA ...

  8. MySQL 事务默认隔离级别?能否解决幻读?

    事务隔离级别 MySQL 中事务隔离级别有 read uncommited.read commited.repeatable read.serializable 四种,其中默认为 repeatable ...

  9. MySQL可重复读应用场景_mysql-repeatable read 可重复读隔离级别-幻读实例场景

    本文详解 repeatable read 可重复读 隔离级别产生的影响(幻读) -- SERIALIZABLE serializable 序列化 :一个个事务排成序列的形式.事务一个挨一个执行,等待前 ...

最新文章

  1. mongodb学习参考博文
  2. 【java开发系列】—— 自定义注解
  3. 你真的懂 timeout 吗?
  4. 项目管理 - 供应商选择 - 重点条件
  5. ARM学习笔记7——乘法指令
  6. qt获取当前场景中的所有图形项的层次
  7. 打造自己的数据访问层(三)
  8. 精彩十年(4)——缔造神话
  9. adb命令重置_android – 擦除数据/通过ADB恢复出厂设置
  10. react classname多个_React全家桶简介
  11. STM32串口通信(使用C8T6)
  12. JavaSE 编写第一个程序
  13. Appium环境搭建2021年最新详细教程
  14. 入门系列- ABP 本地化
  15. 但行好事 莫问前程(九月)
  16. 罗马数字相加java_LeetCode题库 13罗马数字转整数(java)
  17. [ERP]VMI概念与运用场景
  18. 卷积,反卷积,空洞卷积
  19. 店盈通:拼多多如何看到关键词进店?
  20. mac壁纸每天自动更换

热门文章

  1. 什么是 DDoS?完整指南
  2. 51个Python鲜为人知的秘密特性,老司机看完都惊叹不已
  3. IOTOS物联中台modbus驱动对接雅达电表设备
  4. 宝贝怎么查询历史价格?有何意义?
  5. Android 耳机检测原理介绍
  6. 为SEO而生的Rabbit V1.0 WordPress主题下载
  7. 索引数组与关联数组的定义及区别
  8. 预推免的内耗---还好上岸了,要不就淹死了
  9. 短视频引流怎么做?如何利用短视频引流?短视频引流技巧
  10. k线图基础知识图解——单根K线的含义