事务是保障数据正确的重要方式,事务具有原子性、一致性、隔离性和持久性四个属性。本篇文章主要说一下基于MySQL事务中的隔离性。MySQL事务的隔离性分四个级别,分别是RU(读未提交)、RC(不可重复读)、RR(可重复读)、Serializable(串行化)。不同的隔离级别,可以避免脏读、不可重复、幻读的发生,具体如下图:
本篇文章将通过Navicat图形化工具来验证MySQL的隔离级别,具体操作请阅读正文:
i. 数据准备:
# 创建数据库 CREATE DATABASE test; # 选择数据库 USE test; # 创建用户表 CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) ) ; # 插入数据 INSERT INTO `user` (id,name) VALUES (1,'小周'); INSERT INTO `user` (id,name) VALUES (2,'小张'); INSERT INTO `user` (id,name) VALUES (3,'小王');
ii. 临时修改MySQL隔离级别语句:
set session TRANSACTION ISOLATION level READ UNCOMMITTED ;
iii. 临时手动提交事务语句:
set autocommit = 0;
我们在Navicat中启动三个查询窗口,在不同窗口执行不同的语句,来验证我们的猜想。
时间 | 事务窗口一 | 事务窗口一 | 无事务窗口 |
---|---|---|---|
T1 | set autocommit = 0; | set autocommit = 0; | |
T2 | set session TRANSACTION ISOLATION level READ UNCOMMITTED ; | set session TRANSACTION ISOLATION level READ UNCOMMITTED ; | 默认隔离级别 |
T3 | BEGIN ; | BEGIN ; | |
T4 | update user set name ='小周2' where id = 1 ; | ||
T5 | select * from user ; | select * from user ; | select * from user ; |
T6 | ROLLBACK; | ||
T7 | select * from user ; | select * from user ; | |
T8 | COMMIT ; | COMMIT ; |
T5时间内三个窗口同时查询情况如下图。窗口一事务更新的数据没有提交时,就被窗口二事务获取到。如果窗口一事务在之后的时间中发送回滚,窗口二事务则获取了脏数据,即出现了脏读。
窗口一事务在T6时,发生了回滚,T7时间内三个窗口的查询结果如下,三个窗口数据一致了。
我们在Navicat中启动三个查询窗口,在不同窗口执行不同的语句,来验证我们的猜想。
时间 | 事务窗口一 | 事务窗口一 | 无事务窗口 |
---|---|---|---|
T1 | set autocommit = 0; | set autocommit = 0; | |
T2 | set session TRANSACTION ISOLATION level READ COMMITTED ; | set session TRANSACTION ISOLATION level READ COMMITTED ; | 默认隔离级别 |
T3 | BEGIN ; | BEGIN ; | |
T4 | update user set name ='小周2' where id = 1 ; | ||
T5 | select * from user ; | select * from user ; | select * from user ; |
T6 | COMMIT ; | ||
T7 | select * from user ; | select * from t_user ; | |
T8 | COMMIT ; |
T5时间内三个窗口同时查询情况如下图。窗口一事务更新的数据没有提交时,窗口二事务没有获取到窗口一事务未提交的数据,避免了脏读。
窗口一事务在T6时,提交了事务,T7时间内窗口二事务再次读取数据,数据发生了改变,造成了不可重复读。
我们在Navicat中启动三个查询窗口,在不同窗口执行不同的语句,来验证我们的猜想。
时间 | 事务窗口一 | 事务窗口一 | 无事务窗口 |
---|---|---|---|
T1 | set autocommit = 0; | set autocommit = 0; | |
T2 | set session TRANSACTION ISOLATION level REPEATABLE READ ; | set session TRANSACTION ISOLATION level REPEATABLE READ ; | 默认隔离级别 |
T3 | BEGIN ; | BEGIN ; | |
T4 | update user set name ='小周2' where id = 1 ; | ||
T5 | select * from user ; | select * from user ; | select * from user ; |
T6 | COMMIT ; | ||
T7 | select * from user ; | select * from t_user ; | |
T8 | COMMIT ; |
T1-T6,步骤不再复述,直接展示T7时间,T7时间内窗口二事务再次读取数据,数据没有改变,避免了不可重复读。
我们在Navicat中启动三个查询窗口,在不同窗口执行不同的语句,来验证我们的猜想。
时间 | 事务窗口一 | 事务窗口一 | 无事务窗口 |
---|---|---|---|
T1 | set autocommit = 0; | set autocommit = 0; | |
T2 | set session TRANSACTION ISOLATION level REPEATABLE READ ; | set session TRANSACTION ISOLATION level REPEATABLE READ ; | 默认隔离级别 |
T3 | BEGIN ; | BEGIN ; | |
T4 | insert into user VALUES (4,'小韩') ; | ||
T5 | select * from user ; | select * from user ; | select * from user ; |
T6 | COMMIT ; | ||
T7 | select * from user ; | select * from t_user ; | |
T8 | insert into user VALUES (4,'小韩') ; | ||
T9 | COMMIT ; |
窗口二事务在T7时间时,查询数据结果如下:
窗口二事务在T7时间时,没有第四条数据,所有决定在T8时间插入第四条数据,结果不能插入,这出现了幻读。
提示:所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行。
不可重复读和幻读的区别是:不可重复读是指读到了已经提交的事务的更改数据(修改或删除),幻读是指读到了其他已经提交事务的新增数据。
防止读到更改数据,需要对操作的数据添加行级锁,防止操作中的数据发生变化;防止读到新增数据,需要添加表级锁,将整张表锁定,防止新增数据。
我们在Navicat中启动三个查询窗口,在不同窗口执行不同的语句,来验证我们的猜想。
时间 | 事务窗口一 | 事务窗口一 | 无事务窗口 |
---|---|---|---|
T1 | set autocommit = 0; | set autocommit = 0; | |
T2 | set session TRANSACTION ISOLATION level SERIALIZABLE; | set session TRANSACTION ISOLATION level SERIALIZABLE; | 默认隔离级别 |
T3 | BEGIN ; | BEGIN ; | |
T4 | insert into user VALUES (4,'小韩') ; | ||
T5 | select * from user ; | select * from user ; | select * from user ; |
T6 | COMMIT ; | ||
T7 | select * from user ; | select * from t_user ; | |
T8 | insert into user VALUES (4,'小韩') ; | ||
T9 | COMMIT ; |
窗口二事务在T5时间内,会一直在处理中。如果窗口一事务不提交,窗口二事务就会等锁超时,即事务必须串行化执行。
窗口二事务在T7时间时,窗口一事务以及提交,可以执行查询,并且发现有第四条数据,就不会在插入数据,避免了幻读。