Mysql中mvcc各场景理解应用


Posted in MySQL onAugust 05, 2022

前言

  • mysql版本为
mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.00 sec)
  • 隔离级别
mysql> show variables like '%isola%';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.02 sec)
  • 表结构
mysql> show create table test;
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table                                                                                                                                                                                                                                                                                                                                                                                 |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| test  | CREATE TABLE `test` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
  `name` char(32) NOT NULL COMMENT '用户姓名',
  `num` int DEFAULT NULL,
  `phone` char(11) DEFAULT '' COMMENT '手机号',
  PRIMARY KEY (`id`),
  KEY `idx_name_phone` (`name`,`phone`)
) ENGINE=InnoDB AUTO_INCREMENT=108 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='test表'           |
+-------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec
  • 现有表数据
mysql> select * from test;
+-----+---------------+---------+-------+
| id  | name          | num     | phone |
+-----+---------------+---------+-------+
|   1 | 执行业        | 1234567 |       |
|   2 | 执行业务1     |    NULL |       |
|   3 | a             |    NULL |       |
|   4 | a             |    NULL |       |
|   5 | a             |    NULL |       |
|   6 | b             |       1 |       |
|   7 | wdf           |    NULL |       |
|  10 | dd            |       1 |       |
|  11 | hello         |    NULL |       |
|  15 | df            |    NULL |       |
|  16 | e             |    NULL |       |
|  20 | e             |    NULL |       |
|  21 | 好的          |    NULL |       |
|  25 | g             |       1 |       |
| 106 | hello         |    NULL |       |
| 107 | a             |    NULL |       |
+-----+---------------+---------+-------+
16 rows in set (0.00 sec)

场景一

  • 事务A:select * from test where id in (7,15) for update;
  • 事务B:update test set name='d' where id=10;insert into test(id,name) values(8,'hello');
  • 事务A:select * from test where id in (7,8,10,15);。 第二步是否阻塞。第三步是否能读到事务B执行的更新。

试验步骤

事务A第一步

mysql> begin;select * from test where id in (7,15) for update;
Query OK, 0 rows affected (0.00 sec)
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
2 rows in set (0.01 sec)

持有锁情况:

mysql> select * from performance_schema.data_locks;
+--------+-----------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
| ENGINE | ENGINE_LOCK_ID              | ENGINE_TRANSACTION_ID | THREAD_ID | EVENT_ID | OBJECT_SCHEMA | OBJECT_NAME | PARTITION_NAME | SUBPARTITION_NAME | INDEX_NAME | OBJECT_INSTANCE_BEGIN | LOCK_TYPE | LOCK_MODE     | LOCK_STATUS | LOCK_DATA |
+--------+-----------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
| INNODB | 4974808984:1063:4890706744  |                 46666 |        50 |      123 | my_test       | test        | NULL           | NULL              | NULL       |            4890706744 | TABLE     | IX            | GRANTED     | NULL      |
| INNODB | 4974808984:2:4:7:4915866136 |                 46666 |        50 |      123 | my_test       | test        | NULL           | NULL              | PRIMARY    |            4915866136 | RECORD    | X,REC_NOT_GAP | GRANTED     | 15        |
| INNODB | 4974808984:2:4:9:4915866136 |                 46666 |        50 |      123 | my_test       | test        | NULL           | NULL              | PRIMARY    |            4915866136 | RECORD    | X,REC_NOT_GAP | GRANTED     | 7         |
+--------+-----------------------------+-----------------------+-----------+----------+---------------+-------------+----------------+-------------------+------------+-----------------------+-----------+---------------+-------------+-----------+
3 rows in set (0.00 sec)

发现7,15持有了行锁。

事务B执行

mysql> update test set name = 'sds' where id=10;insert into test(id,name) values(8,'hello');
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
Query OK, 1 row affected (0.00 sec)

事务A执行第二步

mysql> select * from test where id in (7,8,10,15);
+----+-------+------+-------+
| id | name  | num  | phone |
+----+-------+------+-------+
|  7 | wdf   | NULL |       |
|  8 | hello | NULL |       |
| 10 | sds   |    1 |       |
| 15 | df    | NULL |       |
+----+-------+------+-------+
4 rows in set (0.01 sec)

结果

步骤二执行了,事务A读到了事务B提交的数据。下面我们来看看正常的select;

场景二

还原数据:

mysql> update test set name = 'dd' where id=10;delete from test where id=8;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1  Changed: 1  Warnings: 0
Query OK, 1 row affected (0.00 sec)
  • 事务A:select * from test where id in (7,15);
  • 事务B:update test set name='d' where id=10;insert into test(id,name) values(8,'hello');
  • 事务A:select * from test where id in (7,8,10,15);。 第二步是否阻塞。第三步是否能读到事务B执行的更新。

试验步骤

事务A第一步

mysql> begin;select * from test where id in (7,15);
Query OK, 0 rows affected (0.00 sec)
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
2 rows in set (0.00 sec)

持有锁情况:

mysql> select * from performance_schema.data_locks;
Empty set (0.00 sec)

事务B执行

mysql> update test set name = 'sds' where id=10;insert into test(id,name) values(8,'hello');
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0
Query OK, 1 row affected (0.00 sec)

事务A执行第二步

mysql> select * from test where id in (7,8,10,15);
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
| 10 | dd   |    1 |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
3 rows in set (0.00 sec)

结果

步骤二执行了,事务A没读到了事务B提交的数据。笔者猜测for update加锁之后会清除readview或者没开启readview,所以后面会读到事务B的。

所以我们来看看到底是清除还是没开启。

事务A后续步骤

mysql> select * from test where id in (7,15) for update;
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
2 rows in set (0.00 sec)
mysql> select * from test where id in (7,8,10,15);
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
| 10 | dd   |    1 |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
3 rows in set (0.00 sec)

可以发现重新执行了场景一的步骤后结果没变。

所以应该是没开启,应该是当前读不会开启readview。

笔者找了下资料没找到,找到的笔者可以留言。

不过我们可以使用继续实验验证下。

场景三

  • 事务A:update test set name = 'dgf' where id in (7,15);
  • 事务B:update test set name='d' where id=10;insert into test(id,name) values(8,'hello');
  • 事务A:select * from test where id in (7,8,10,15);。 第二步是否阻塞。第三步是否能读到事务B执行的更新。

这个场景就不搞实验步骤了,结果是和笔者的猜想一样的 ”当前读不会开启readview,第一个快照读才会开启“

场景四

  • 事务A:select * from test where id in (7,15);
  • 事务B:insert into test(id,name) values(8,'hello');
  • 事务A:select * from test where id in (7,8,15);
  • 事务A:update test set name ='cv' where id =8;
  • 事务A:select * from test where id in (7,8,15);

事务A第一步

mysql> begin;select * from test where id in (7,15);
Query OK, 0 rows affected (0.00 sec)
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
2 rows in set (0.00 sec)

开启了事务,浅读一下。

事务B执行

insert into test(id,name) values(8,'hello');

事务A第二步

mysql> select * from test where id in (7,8,15);
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
2 rows in set (0.00 sec)

检验一下是否读的到,发现读不到。

事务A第三步

mysql> update test set name ='cv' where  id =8;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

对插入的进行更新。

事务A第四步

mysql> select * from test where id in (7,8,15);
+----+------+------+-------+
| id | name | num  | phone |
+----+------+------+-------+
|  7 | wdf  | NULL |       |
|  8 | cv   | NULL |       |
| 15 | df   | NULL |       |
+----+------+------+-------+
3 rows in set (0.00 sec)

发现可以读到了。

原因

能读到的原因是因为本事务对版本链内容进行了修改,所以就读到了。

这个场景可能会出现在实际开发中,会比较懵,当然“事务A第三步”是笔者随便模拟的,实际生产中直接拿大不到刚刚插入的id,所以应该是模糊(没有确定行)update。所以在生产中还是要确定行去进行修改,避免出现这种比较难理解的场景。

虽然也可以使用lock in share mode或者for update读当前借助next-key去实现不幻读(第二次读到第一次没有读到的行),还是需要根据具体业务选择。

总结

根据以上的场景,我们可以知道:

  • readview是第一个select的时候才会创建的。
  • rr级别下读快照如果中间出现修改版本链内容还是会出现幻读(很合理,但是不容易发现这个原因),如果真的要想做到不幻读还是要通过加锁(当然要有索引,没有的话就锁表了)。

以上就是Mysql中mvcc各场景理解的详细内容,更多关于Mysql mvcc场景的资料请关注三水点靠木其它相关文章!

MySQL 相关文章推荐
MySQL主从搭建(多主一从)的实现思路与步骤
May 13 MySQL
如何设计高效合理的MySQL查询语句
May 26 MySQL
MySQL 常见存储引擎的优劣
Jun 02 MySQL
MySQL8.0无法启动3534的解决方法
Jun 03 MySQL
浅谈mysql返回Boolean类型的几种情况
Jun 04 MySQL
SpringBoot连接MySQL获取数据写后端接口的操作方法
Nov 02 MySQL
MySQL表类型 存储引擎 的选择
Nov 11 MySQL
SQL基础查询和LINQ集成化查询
Jan 18 MySQL
MySQL优化常用的19种有效方法(推荐!)
Mar 17 MySQL
MySQL磁盘碎片整理实例演示
Apr 03 MySQL
MYSQL优化之数据表碎片整理详解
Apr 03 MySQL
MySQL数据库Innodb 引擎实现mvcc锁
May 06 MySQL
数据设计之权限的实现
一文解答什么是MySQL的回表
Aug 05 #MySQL
MySQL一劳永逸永久支持输入中文的方法实例
Aug 05 #MySQL
SQLServer常见数学函数梳理总结
Aug 05 #MySQL
MySQL生成千万测试数据以及遇到的问题
Aug 05 #MySQL
面试官问我Mysql的存储引擎了解多少
MySQL索引失效场景及解决方案
Jul 23 #MySQL
You might like
几个学习PHP的网址
2006/11/25 PHP
PHP中图片等比缩放的实例
2013/03/24 PHP
Javascript模块化编程(一)AMD规范(规范使用模块)
2013/01/17 Javascript
js类型转换与引用类型详解(Boolean_Number_String)
2014/03/07 Javascript
一个字符串反转函数可实现字符串倒序
2014/09/15 Javascript
基于Jquery和CSS3制作数字时钟附源码下载(CSS3篇)
2015/11/24 Javascript
基于BootStrap环境写jQuery tabs插件
2016/07/12 Javascript
Jquery获取当前城市的天气信息
2016/08/05 Javascript
node.js中module.exports与exports用法上的区别
2016/09/02 Javascript
jQuery实现图片滑动效果
2017/03/08 Javascript
Bootstrap弹出框(Popover)被挤压的问题小结
2017/07/11 Javascript
Angular2关于@angular/cli默认端口号配置的问题
2017/07/15 Javascript
详解如何从零开始搭建Express+Vue开发环境
2018/07/17 Javascript
解决axios会发送两次请求,有个OPTIONS请求的问题
2018/10/25 Javascript
Vue组件之单向数据流的解决方法
2018/11/10 Javascript
layui多iframe页面控制定时器运行的方法
2019/09/05 Javascript
js 计算月/周的第一天和最后一天代码
2020/02/01 Javascript
[01:01:13]2018DOTA2亚洲邀请赛 4.5 淘汰赛 Mineski vs VG 第三场
2018/04/06 DOTA
python遍历目录的方法小结
2016/04/28 Python
Python实现购物系统(示例讲解)
2017/09/13 Python
python PrettyTable模块的安装与简单应用
2019/01/11 Python
Order by的几种用法
2013/06/16 面试题
协议书模板
2014/04/23 职场文书
企业指导教师评语
2014/04/28 职场文书
《梅花魂》教学反思
2014/04/30 职场文书
幼儿教师师德承诺书
2014/05/23 职场文书
三八节标语
2014/06/27 职场文书
农村党建工作汇报材料
2014/10/27 职场文书
2015年元旦主持词结束语
2014/12/14 职场文书
主持人大赛开场白
2015/05/29 职场文书
NodeJs内存占用过高的排查实战记录
2021/05/10 NodeJs
spring项目中切面及AOP的使用方法
2021/06/26 Java/Android
关于Python OS模块常用文件/目录函数详解
2021/07/01 Python
2007年老电脑安装win11会怎么样? 网友实测win11在老电脑运行良好
2021/11/21 数码科技
日元符号 ¥
2022/02/17 杂记
Android使用EventBus发送消息,Fragment中接收消息的方法会执行多次
2022/04/24 Java/Android