开始在学习这几个概念的时候,发现不仅是身边,而且网上有很多人都把这些概念搞混淆了,特别是幻读.又由于这些都是一些概念性的东西,所以我就去查了一下官方文档:详见: MySQL 事务隔离级别相关官方文档翻译
为了测试我们先准备一个表并插入几条数据:
drop table if exists t;
create table t(
id int primary key auto_increment,
name varchar(10),
sal decimal(10,2)
);
insert into t(name,sal) values(‘a’,100);
insert into t(name,sal) values(‘b’,200);
insert into t(name,sal) values(‘c’,300);
insert into t(name,sal) values(‘d’,400);
insert into t(name,sal) values(‘e’,500);
查看当前隔离级别:
show variables like ‘tx%’;
1.脏读 : 读取了未被提交的数据
测试脏读与READ-UNCOMMITTED
set tx_isolation=’READ-UNCOMMITTED’;
事务A:
start transaction;
update t set sal=sal*2;
insert into t(name,sal) values (‘f’,600);
delete from t where id=1; – 不commit.
rollback;
set tx_isolation=’READ-UNCOMMITTED’;
事务B:
start transaction;
select * from t; – 在事务A之前查询
select * from t; – 在事务A增删改之后查询,查询到了事务A未提交的数据:发生了脏读
rollback;
2.不可重复读(Unrepeatable Read): 一个事务范围内两个相同的查询却返回了不同数据
测试不可重复读与READ-COMMITTED
set tx_isolation=’READ-COMMITTED’;
事务A:
start transaction;
update t set sal=sal*2;
insert into t(name,sal) values (‘f’,600);
delete from t where id=1;
commit; – 在事务B第二次查询之后
select * from t; – 用于和事务B中对比
set tx_isolation=’READ-COMMITTED’;
事务B:
start transaction;
select * from t; – 在事务A之前查询
select * from t; – 在事务A增删改但未提交之后查询,已经看不到被事务A改动的数据:避免了脏读.
select * from t; – 在事务A提交之后查询,可以看到被A改动的数据:发生了不可重复读.
rollback;
重建表格
MySQL文档中:MySQL 5.7 Reference Manual :: 14.5.4 Phantom Rows
The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.
幻影行的解释:如果一个SELECT被执行了两次,但是第二次返回了一个第一次没有返回的行,那么该行就称为“幻影”行.
MySQL文档中:MySQL 5.7 Reference Manual :: MySQL Glossary(节选)
A row that appears in the result set of a query, but not in the result set of an earlier query.
This occurrence is known as a phantom read.
在查询一组结果中出现的行,但不是在之前查询的一组结果中出现的行。
这种现象被称为“幻读”。
3.幻读(phantom read): 一个事务范围内对于一组的查询看到了原本没有的行
测试幻读与REPEATABLE-READ
set tx_isolation=’REPEATABLE-READ’;
事务A:
start transaction;
insert into t(name,sal) values (‘f’,600);
commit;
set tx_isolation=’REPEATABLE-READ’;
事务B:
start transaction;
select * from t; – 在事务A之前,查询出一组数据.
select * from t; – 在事务A提交之后,查询到的数据仍然是上一次查询的数据:避免了不可重复读.
update t set sal=sal*2; – 查询之后进行修改.
select * from t; – 在本事务中对全表进行修改之后查询,发现多了一行name=f
我们的预期结果本来是在本事务内每次查询都能看到与第一次查询一样的结果,但是
现在查询发现了原本没有的行:发生了幻读.
rollback;
重点:在同一事务内查询(一些人把幻读误解为:在事务A中对全表数据进行修改 事务B插入一行数据,结束事务后查询到了一行未被修改的数据.)
重建表格
4.测试SERIALIZABLE
①
set tx_isolation=’SERIALIZABLE’;
事务A:
start transaction;
select * from t;
commit; – 在事务B进行修改之后提交
set tx_isolation=’SERIALIZABLE’;
事务B:
start transaction;
select * from t;
update t set sal=sal*2 where id=1; – 被阻塞
– 当事务A提交之后(事务结束),修改成功.
rollback;
②
事务A:
start transaction;
insert into t(name,sal) values (‘f’,600); – 在事务B查询之后插入,被阻塞
事务B:
start transaction;
select * from t;
update t set sal=sal*2; – 在事务A进行插入(被阻塞)之后修改数据.
select * from t; – 未看到被事务A插入的数据(事务A中的插入操作还未执行):避免了幻读.
REPEATABLE-READ级别下
易混淆点:在REPEATABLE-READ级别下的查询结果是进行第一次查询时固定的,而不是开启事务时.
因为RR中的consistent read是以事务中第一个select语句执行的时间点作为snapshot建立的时间点的.(详见文档中consistent read)
测试:
事务A:
start transaction; – 开启事务A时事务B还未插入数据.
select * from t; – 在事务B进行插入操作并提交之后.
select * from t; – 在事务C进行插入操作并提交后.
事务B:
start transaction;
insert into t(name,sal) values (‘f’,600);
commit;
事务C:
start transaction;
insert into t(name,sal) values (‘g’,700);
commit;
结果:事务A的查询能够查询到被事务B插入的数据.但是看不到被事务C插入的数据.
结论:在RR模式下查询的快照是根据第一个select是保存的.
==================================================================================================
以下是个人的其它思考
在SERIALIZABLE下
事务A只对表进行查询,事务B只能对表进行查询,想进行增删改操作都会被阻塞.
在这里当B对表也进行查询之后,事务A再对数据进行增删改操作都会被阻塞,也就是说这个锁定,是根据最后一个对表进行查询的事务来决定的.
SERIALIZABLE下
*都是行级锁
事务A:
start transaction;
select * from t where id=1;
事务B:
start transaction;
update t set sal=sal*2 where id=2; – 在事务A查询之后,不被阻塞.
update t set sal=sal*2 where id=1; – 在事务A查询之后,被阻塞.
事务A对数据进行增删改操作(未commit),被A动过的数据,事务B想进行增删改查会被阻塞,但是事务A未修改到的数据,事务B是可以进行修改删除的;
事务A对表内数据进行查询时,会对那些数据加一个锁,被锁住的数据在别的事务中只能够进行查询.
事务A对表内数据进行增删改时,加的锁,会使这些数据在别的事务中的增删改查都阻塞.
七个名词:三个现象+四个限制级别
三个现象:
脏读:查询到了另一个事务更新但未提交的数据,不符合数据库设计的ACID原则
不可重复读:在一个事务中一次查询读取到了与上一次一样的查询不同的数据.
幻读:一个事务范围内对于一组的查询看到了原本没有的行.
四个限制级别:
READ UNCOMMITTED(基本不用):性能最高,可以读取到别的事务更新但未提交的数据.
READ COMMITTED:不能从其他事务中看到未提交的数据,但是它们可以看到在当前事务启动后由另一个事务提交的数据.避免了脏读.
REPEATABLE READ(MySQL默认):在事务中进行了第一次查询后,之后每次相同的查询查询的结果都是一样的.避免了不可重复读与脏读.
但是另一事务对表进行插入并提交后,本事务对全表进行修改,会发现幻影行.也就是发生了幻读.
SERIALIZABLE(基本不用):性能最低,保护性最高,通过一系列限制,避免了脏读、不可重复读、幻读操作.
现象与限制级别的关系是:不同限制级别通过一系列不同的限制行为,避免了不同的现象产生.