MySQL幻读
1. 幻读说明
之前一篇文章叙述了MySQL事务的一些常识,再次补充测试并说明下幻读的问题,其中脏读和不可重复度的问题也可以根据这篇文章的操作类似方法验证。之前的文章MySQL事务具体也可以看看。
2. 幻读普通查询测试
- 把隔离级别设置为Read Commited
实例:假如出现幻读,就会出现会话1插入的数据,会话2能查询到。
创建表数据:
CREATE TABLE
test
(
id
int(11) NOT NULL AUTO_INCREMENT,
name
varchar(255) DEFAULT NULL,
address
varchar(255) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
--会话1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
show VARIABLES like "%tx_isolation%"; -- 查看会话隔离级别为RC tx_isolation
show VARIABLES like "%isolation%"; -- V8.0 参数更改 transaction_isolation
TRUNCATE table test; -- 清表重置
begin;
select * from test; -- 结果为空
-- 会话2
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
show VARIABLES like "%tx_isolation%"; -- 查看会话隔离级别为RC
begin;
select * from test; -- 结果为空
--会话1
insert into test(name,address) values ("zhangsan","beijing");
select * from test; -- 结果: 1 zhangsan beijing
-- 会话2
select * from test; -- 结果为空
--会话1
commit;
-- 会话2
select * from test; -- 结果: 1 zhangsan beijing 出现幻读
commit;
- 隔离级别设置为默认的RR级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
show VARIABLES like "%tx_isolation%";
然后再次执行上述操作,就会发现之前会话2出现的幻读现象没有了。在执行会话2之后执行再次查询才会有结果:
-- 会话2
select * from test; -- 结果为空
commit;
select * from test; -- 结果: 1 zhangsan beijing
这样就没有出现幻读的情况。
由此可以证明普通查询的场景解决了幻读的问题。
3. 当前读
MySQL的查询可以有当前读和快照读,为了保证事务的正确性,一般的查询都是快照读,根据数据行中字段 DB_ROLL_PTR 存放的就是undo log中的指针。
当前读有三种方式:
1. select … lock in share mode
2. select … for update
3. DML 操作 update,insert ,delete
而上述事务A 插入的数据,在事务B种即使查不到,但是可以更改的。
虽然有点反常识,但是确实在MySQL中可以发生的。但是考虑到我们的正常业务一般也不会出现问题。
理论上但是也可能出现这种情况,事务A插入数据,事务B全局更改,事务A和B再查询的时候,是可以查询到的,而且可能发生事务A查询到的仍然是原来的结果,事务B查询到是更新后的结果,事务A提交后再查询才是最新结果。
验证(顺序执行):
--窗口1 设置 RR 隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
show VARIABLES like "%tx_isolation%";
TRUNCATE test; -- 清空表
BEGIN;
select * from test; -- 空
--窗口2
BEGIN;
select * from test; -- 空
-- 窗口1
insert into test(name,address) values("zhangsan","beijing"),("lisi","nanjing");
select * from test;
-- 结果:1 zhangsan beijing
-- 2 lisi nanjing
-- 窗口2
select * from test; -- 空,复合RR级别解决幻读的情况
update test set name = "zs" where id = 1; -- 阻塞
-- 窗口1
commit;
-- 窗口2
select * from test; -- 空
update test set name = "zs" where id = 1; -- 成功
select * from test; -- 成功 1 zs beijing
commit;
4. 总结
- 事务A和事务B在执行的过程中不会发生影响。
- 事务A在提交后,事务B普通查询不会发生影响。
- 事务A在提交后,事务B查询事务A插入的数据当前读会查询到结果。
- 事务A在提交后,事务B查询事务A插入的数据当前读查询到的结果仅限于影响的行,不会对插入所有行起作用。
- 事务A和事务B都提交后正常。
经测试 共享锁(lock in share mode) 和 排他锁(for update) 据由同样的现象。