彻底搞懂mysql数据库四种隔离级别,实验实战

531 阅读4分钟

数据库四种隔离级别

隔离级别 脏读 不可重复读 幻读
Read uncommitted 读未提交 可能 可能 可能
Read committed 读已提交 不可能 可能 可能
Repeatable read 可重复读 不可能 不可能 可能
Serializable read 串行化 不可能 不可能 不可能

创建一个student表

CREATE TABLE `student` (   `id` int(11) NOT NULL AUTO_INCREMENT,   `class_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,   `class_id` int(11)
NOT NULL,   PRIMARY KEY (`id`),   KEY `idx_class_id` (`class_id`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ;
INSERT INTO `student`( `class_name`, `class_id`) VALUES ( '初一一班', 1);
INSERT INTO `student`( `class_name`, `class_id`) VALUES ( '初二一班', 2);
INSERT INTO `student`( `class_name`, `class_id`) VALUES ( '初二一班', 3);

一、RU(读未提交)级别下(测试脏读)

事务A 事务B
set session transaction isolation level read uncommitted; set session transaction isolation level read uncommitted;
begin; begin;
select * from student select * from student
update student set class_name='初一三班' where class_id=1;
select * from student; select * from student;

(出现了脏读,读取到了未提交数据)
commit; commit;

结论:和理论一致,出现了出现了脏读,读取到了未提交数据

一、RC(读已提交)级别下(测试脏读、不可重复读)

事务A 事务B
set session transaction isolation level read committed; set session transaction isolation level read committed;
begin; begin;
select * from student select * from student
update student set class_name='初一三班' where class_id=1;
select * from student; select * from student;

(数据没变化)
commit;
select * from student;

(读取到了已提交数据,但是一个事务里俩次读取不一致,出现不可重复读)
commit;

结论:避免了脏读,但是事务B同样的查询,后一次和前一次的结果不一样,这就是不可重读(重新读取产生的结果不一样)。这就很可能带来一些问题,那么我们来看看在RR级别中MySQL的表现。

三、RR(可重复读)级别下(测试不可重复读)

事务A 事务B
set session transaction isolation level Repeatable Read; set session transaction isolation level Repeatable Read;
begin; begin;
select * from student select * from student
update student set class_name='初一三班' where class_id=1;
select * from student; select * from student;

(数据没变化)
commit;
select * from student;

(数据没变化)
commit;

结论:和理论一致。避免了脏读,和不可重复读

四、RR(可重复读)级别下(测试幻读)

事务A 事务B
set session transaction isolation level Repeatable Read; set session transaction isolation level Repeatable Read;
begin; begin;
select * from student select * from student
INSERT INTO student(class_name, class_id) VALUES ( '初三一班',4);
commit;
select * from student;
select * from student;

(数据没变化)
commit;

结论:RR 级别下和理论出现不一致。居然避免了幻读

注意: 幻读和不可重复读容易搞混。但不可重复读重点在于update和delete,而幻读的重点在于insert。

在RR级别中,MySQL以乐观锁为理论基础的MVCC(多版本并发控制)来避免这两种问题。通过MVCC机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据!这在一些对于数据的时效特别敏感的业务中,就很可能出问题。

数据库的读操作可以分为快照读、当前读。

快照读 select * from XXX

当前读 select * from XXX for update, lock in share mode, update insert delete 等

前面已经介绍了快照读的幻读 在RR级别是不会出现的下面测试当前读是否会有幻读

五、RR(可重复读)级别下(测试[当前读]幻读)

情形1: 针对新增数据的索引id(class_id=1) 与当前读索引id(class_id=1) 相同的幻读测试

事务A 事务B
set session transaction isolation level Repeatable Read; set session transaction isolation level Repeatable Read;
begin; begin;
select * from student select * from student
update student set class_name='初一四班' where class_id=1;
INSERT INTO student(class_name, class_id) VALUES ( '初三一班',1);
waiting....
select * from student;

修改成功
commit; insert语句执行成功
commit;

情形1:没问题 没有幻读

情形2: 针对新增数据索引id(class_id=30) 大于当前读索引id(class_id=20)的幻读测试

事务A 事务B
set session transaction isolation level Repeatable Read; set session transaction isolation level Repeatable Read;
begin; begin;
select * from student select * from student
update student set class_name='初一四班' where class_id=20;
INSERT INTO student(class_name, class_id) VALUES ( '初三一班',30);
waiting
commit; insert语句执行成功
commit;

情形2:没问题 没有幻读

情形3: 针对新增数据索引id(class_id=20) 大于当前读索引id(class_id=15)的幻读测试

事务A 事务B
set session transaction isolation level Repeatable Read; set session transaction isolation level Repeatable Read;
begin; begin;
select * from student select * from student
update student set class_name='初一四班' where class_id=15;
INSERT INTO student(class_name, class_id) VALUES ( '初三一班',20);
insert语句执行成功
update student set class_name='初二一班' where class_id=20;
waiting
commit;
update执行成功
commit;

结论:RR 级别下 当前读也做到了一个事务中查询一致性 避免了幻读

RR级别中,事务A在update后加锁,事务B无法插入新数据,这样事务A在update前后读的数据保持一致,避免了幻读。这个锁,就是Gap锁。

表里带索引的数据classid 为1和20

当当前读修改ID为15锁住了class这部分区间 (1,15]和[15,20)

当当前读修改ID为20锁住了class这部分区间 (1,20]和[20,∞)

当当前读修改ID为30锁住了class这部分区间 [20,30]和[30,∞)