自己被 事务隔离级别 及产生的错误 烦扰太多次了,最近在集中学mysql 要解决一下,mysql 的问题并重视起来。 所以记录一下 实验的过程:
话不多说:
实验环境: 自己的是本机mysql 8.0 使用Navicat 15 窗口来进行会话实验。
实验准备:
一:查看自己的innodb 的隔离级别:
代码语言:javascript复制 select @@global.transaction_isolation; // 用Navicat 开一个查询窗口 允许命令
或:
show global variables; // 查看你数数据库的全局变量配置 然后找到隔离级别 transaction_isolation
运行结果:
当前事务的隔离级别为 可重复读
二: 然后准备一张表: 随便你自己正在用的一张表就行,没有什么特殊的要求,毕竟隔离级别对什么样结构的表都是适用的。
我的实验表结构如下:
代码语言:javascript复制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;
三:设置事务不自动提交
代码语言:javascript复制set autocommit = 0; // 将自动提交设置为0 不进行自动提交 自动提交将看不到效果 最好两个事务都开启
那么我们首先测试一下 可重复的读的隔离级别下 是否会出现不可以重复读的情况
分为两个session 1 session 2 实验环境下我都设置 自动提交未0 为了清晰。
session 1
代码语言:javascript复制begin;
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
查询结果: 和开始一样
session 2 执行 一个更新语句
代码语言:javascript复制begin
UPDATE content set t_content = '测试可重复读1' WHERE ip_address= '0:0:0:0:0:0:0:1'
结果: 执行成功 影响了4 行
session2 要执行 commit 提交事务,如果我们不提交就变成测试 脏读 和隔离级别应该设置成未提交读
此时我们在session1 下再执行 开始的查询语句,这时候我们就要注意,如果返回的数据和我们起初一样那就说,我们的session1执行事务过程中,没有看到其他事务提交后的结果,也就是可重复读,我们读取到的还是我们事务开始时的视图。相反如果数据不一致就导致了不可重复读,查询到的是更新后的视图。
代码语言:javascript复制SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
结果返回: 和开始查询到的数据是一致的,因此隔离事务是对的没有产生我们怀疑的不可重复读。
session1 此时如果提交事务 再查询 查询到的就是我们修改后的数据,因为刚刚那个事务已经进行提交了。
seesion1 commit 后执行
代码语言:javascript复制SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1'; 的数据
测试可重复读(隔离级别)
的执行流程如下,相当的简单,但能够反映问题
session1 的语句执行流程
代码语言:javascript复制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 的语句执行流程
代码语言:javascript复制1
BEGIN
3
UPDATE content set t_content = '测试可重复读1' WHERE ip_address= '0:0:0:0:0:0:0:1'
4
commit
‘
测试读提交 (隔离级别)
那么如果我们修改innodb 的事务隔离级别 测试一下结果是否还是这样:
代码语言:javascript复制SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL level; // 修改innodb 的隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED ; // 修改为读提交
重复上面的步骤:就可以看到,当seesion1 最后一次查询时,原本在可重复读的条件下的数据和开始时一样,但此时发生了变化。说明隔离级别发生了变化。
踩坑提示,修改完数据库的隔离级别后,重新打开连接一下数据库,再测试更佳。
sql 的执行过程和上面的一致,参照
测试幻读的出现(现象)
调回隔离级别为可重复读 一般出现的为 插入操作 也就是说,我们正常的update 和delete 操作并不会产生 那说明 insert和 更新和删除 是不一样的,通过可重复读的隔离级别可以知道,是通过mvvc 和在事务执行前一个创建的视图来进行的,delete 和 update 都是对视图上已经有的进行操作,而拆入是对视图上没有的进行操作,所以会不一样。
此时我们
session1 执行:
代码语言:javascript复制begin
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
session2 执行:
代码语言:javascript复制BEGIN
INSERT into content VALUES (249,'xxafa','0:0:0:0:0:0:0:1','2020年08月11日01时32分01秒')
commit
session1 执行: ***
代码语言:javascript复制SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
但此时并没有发生我们期待的发生,此时的查询结果还是 和开始的一样。
通过看别人的文章发现,现象是对的,需要我们在session1 中执行一次 更新或者删除操作,会发现影响到的是比之前多一行的,
比如本来4行,session 2插入了一行 现在就是5 行 ,但在*** 处我们看到的就是4 行 ,说明insert 是已经插入成功的。
看到别人的做法后继续操作
session1 :
代码语言:javascript复制UPDATE content set t_content = '测试幻读’' WHERE ip_address= '0:0:0:0:0:0:0:1'
结果:发现是影响了5行:
我们此时再查询一边:
代码语言:javascript复制SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1';
结果:发现此时查询到的为5行,说明还是造成了幻读。但是再经历了一次 dml 操纵后
session1 执行过程:
代码语言:javascript复制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执行过程
代码语言:javascript复制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
代码语言:javascript复制
begain
SELECT * FROM content where ip_address = '0:0:0:0:0:0:0:1' for UPDATE;
加上写锁
session2
代码语言:javascript复制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