Navicat 环境测试 innodb 的事务隔离级别 产生的幻读 和 不可重复读
自己被 事务隔离级别 及产生的错误 烦扰太多次了,最近在集中学mysql 要解决一下,mysql 的问题并重视起来。 所以记录一下 实验的过程:
话不多说:
实验环境: 自己的是本机mysql 8.0 使用Navicat 15 窗口来进行会话实验。
实验准备:
一:查看自己的innodb 的隔离级别:
select @@global.transaction_isolation; // 用Navicat 开一个查询窗口 允许命令
或:
show global variables; // 查看你数数据库的全局变量配置 然后找到隔离级别 transaction_isolation
运行结果:
当前事务的隔离级别为 可重复读
二: 然后准备一张表: 随便你自己正在用的一张表就行,没有什么特殊的要求,毕竟隔离级别对什么样结构的表都是适用的。
我的实验表结构如下:
CREATE TABLE `content` (
`tid` int NOT NULL AUTO_INCREMENT,
`t_content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL,
`ip_address` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`timeStamp` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`tid`) USING BTREE
)ENGINE = InnoDB AUTO_INCREMENT = 20036 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
三:设置事务不自动提交
set autocommit = 0; // 将自动提交设置为0 不进行自动提交 自动提交将看不到效果 最好两个事务都开启
那么我们首先测试一下 可重复的读的隔离级别下 是否会出现不可以重复读的情况
分为两个session 1 session 2 实验环境下我都设置 自动提交未0 为了清晰。
session 1
begin;
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
查询结果: 和开始一样
session 2 执行 一个更新语句
begin
UPDATE content set t_content = '测试可重复读1' WHERE ip_address= '0:0:0:0:0:0:0:1'
结果: 执行成功 影响了4 行
session2 要执行 commit 提交事务,如果我们不提交就变成测试 脏读 和隔离级别应该设置成未提交读
此时我们在session1 下再执行 开始的查询语句,这时候我们就要注意,如果返回的数据和我们起初一样那就说,我们的session1执行事务过程中,没有看到其他事务提交后的结果,也就是可重复读,我们读取到的还是我们事务开始时的视图。相反如果数据不一致就导致了不可重复读,查询到的是更新后的视图。
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
结果返回: 和开始查询到的数据是一致的,因此隔离事务是对的没有产生我们怀疑的不可重复读。
session1 此时如果提交事务 再查询 查询到的就是我们修改后的数据,因为刚刚那个事务已经进行提交了。
seesion1 commit 后执行
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1'; 的数据
测试可重复读(隔离级别)
的执行流程如下,相当的简单,但能够反映问题
session1 的语句执行流程
1
begin
2
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
5
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
COMMIT
session2 的语句执行流程
1
BEGIN
3
UPDATE content set t_content = '测试可重复读1' WHERE ip_address= '0:0:0:0:0:0:0:1'
4
commit
‘
测试读提交 (隔离级别)
那么如果我们修改innodb 的事务隔离级别 测试一下结果是否还是这样:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level; // 修改innodb 的隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED ; // 修改为读提交
重复上面的步骤:就可以看到,当seesion1 最后一次查询时,原本在可重复读的条件下的数据和开始时一样,但此时发生了变化。说明隔离级别发生了变化。
踩坑提示,修改完数据库的隔离级别后,重新打开连接一下数据库,再测试更佳。
sql 的执行过程和上面的一致,参照
测试幻读的出现(现象)
调回隔离级别为可重复读 一般出现的为 插入操作 也就是说,我们正常的update 和delete 操作并不会产生 那说明 insert和 更新和删除 是不一样的,通过可重复读的隔离级别可以知道,是通过mvvc 和在事务执行前一个创建的视图来进行的,delete 和 update 都是对视图上已经有的进行操作,而拆入是对视图上没有的进行操作,所以会不一样。
此时我们
session1 执行:
begin
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
session2 执行:
BEGIN
INSERT into content VALUES (249,'xxafa','0:0:0:0:0:0:0:1','2020年08月11日01时32分01秒')
commit
session1 执行: ***
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
但此时并没有发生我们期待的发生,此时的查询结果还是 和开始的一样。
通过看别人的文章发现,现象是对的,需要我们在session1 中执行一次 更新或者删除操作,会发现影响到的是比之前多一行的,
比如本来4行,session 2插入了一行 现在就是5 行 ,但在*** 处我们看到的就是4 行 ,说明insert 是已经插入成功的。
看到别人的做法后继续操作
session1 :
UPDATE content set t_content = '测试幻读’' WHERE ip_address= '0:0:0:0:0:0:0:1'
结果:发现是影响了5行:
我们此时再查询一边:
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
结果:发现此时查询到的为5行,说明还是造成了幻读。但是再经历了一次 dml 操纵后
session1 执行过程:
1
begin
2
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
5
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
6
UPDATE content set t_content = '测试幻读' WHERE ip_address= '0:0:0:0:0:0:0:1'
7
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
COMMIT
session2执行过程
1
BEGIN
3
INSERT into content VALUES (249,'xxafa','0:0:0:0:0:0:0:1','2020年08月11日01时32分01秒')
4
commit
还有既然默认的隔离 会出现幻读的问题,那么我们怎么解决呢?
通过加上 排他锁 也就是写锁来 解决幻读的问题
我们尝试上面的流程 :
session1
begain
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1' for UPDATE;
加上写锁
session2
begin
INSERT into content VALUES (245,'xxafa','0:0:0:0:0:0:0:1','2020年08月11日01时32分01秒') // 当我们执行这条语句时 当前线程会被阻塞住, 现象如下
只有当 session1 的事务提交后才会不堵塞,这条插入语句(当然dml 操做都不行)才能被执行了。 也就是加读锁后对要操作的这行会人工了进入到了串行化的步骤,因此,不会出现幻读的问题。
想做这次实验是看到这个前辈的 做的实验:还有最后的这个幻读也是参考它做的
https://zhuanlan.zhihu.com/p/103580034?utm_source=wechat_session
网上的这个也讲的不错,
https://blog.csdn.net/zhangvalue/article/details/88527378
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 文章翻译:Node.js和Redis入门-安装与命令
- Angular的built-in指令
- 浅谈前端响应式设计(一)
- 浅谈前端响应式设计(二)
- 搭建简易的物联网服务端和客户端-redis+websocket(二十五)
- Angularjs下拉联动(2)
- 大数据开发平台(Data Platform)在有赞的最佳实践
- 【LeetCode两题选手】算法类题目(8.7)
- 树莓派2配置LAMP环境
- 实习第八周
- Office OpenXml SDK 使用 Fallback 图片显示 Ole 元素
- 直播平台在线人数功能
- ZanProxy —— 本地代码调试线上页面,环境再也不是问题
- 实习第九周
- GET 和 POST 区别