数据库四种隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
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; |