本文以一系列详细的sql语句,使你彻底理解mysql的四种隔离级别。
下面的例子中,用到了两个会话(session),每个session中各开启了一个事务,分别记作session A, 事务A;session B, 事务B。
准备工作
首先新建一个表‘student’,并插入两行数据
CREATE TABLE `student` (
`id` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
insert student(id, name) values(1, 'zhangsan'), (2, 'lisi');
表中数据如下图:
id | name |
---|---|
1 | zhangsan |
2 | lisi |
然后用以下sql查询数据库的默认隔离级别
select @@transaction_isolation;
可以看到默认隔离级别是“可重复读”
REPEATABLE-READ
接下来通过sql详细地说明每一个隔离级别的含义。
read-uncommitted
验证“读未提交”, 首先我们开启一个会话(session A),
设置数据库的隔离级别为’read-uncommitted’。
set session transaction_isolation = 'read-uncommitted';
开启事务A
start transaction;
更新name,并查询student表
update student set name = 'hehe';
select * from student;
结果如下:
id | name |
---|---|
1 | hehe |
2 | hehe |
注意,当前事务A中,我们还未提交以上update语句的修改(start transaction之后,需要手动执行commit,才是提交)
现在我们另开一个会话(session B),并设置隔离级别为"读未提交"。
set session transaction_isolation = 'read-uncommitted';
接下来开启事务B,并查询表’student’
start transaction;
select * from student;
结果如下
id | name |
---|---|
1 | hehe |
2 | hehe |
此时,事务A和B均未提交,我们在事务B中查到了事务A中未提交的数据,此为脏读。
read-committed
接下来验证“读已提交”, 首先提交以上未提交的事务A和事务B(执行commit;),并设置session A和session B的隔离级别为“读已提交”:
set session transaction_isolation = 'read-committed';
在session B中开启事务B,并查询表’student’
start transaction;
select * from student;
结果如下:
id | name |
---|---|
1 | hehe |
2 | hehe |
在session A中开启事务A, 并更新student
start transaction;
update student set name = 'zhangsan' where id = 1;
事务A未提交的时候,再次在事务 B中查询student
select * from student;
结果与上面一样:
id | name |
---|---|
1 | hehe |
2 | hehe |
说明“读已提交”避免了脏读。
事务A中执行提交
commit;
事务B中再次执行查询student
select * from student;
结果如下:
id | name |
---|---|
1 | zhangsan |
2 | hehe |
此时,事务A已提交,事务B未提交。而事务B能读到事务A已提交的数据,并且和前面一次查询的结果不同了,此为不可重复读。“读已提交”已经可以避免脏读问题。
repeatable-read
接下来验证“可重复读”。首先提交上面的事务A和事务B,然后设置session A和session B的隔离级别是“可重复读”:
set session transaction_isolation = 'repeatable-read';
session B中,开启事务B并查询student
start transaction;
select * from student;
结果如下:
id | name |
---|---|
1 | zhangsan |
2 | hehe |
session A中开启事务A,并插入一条数据
start transaction;
insert student (id, name)values(3, 'haha');
此时事务A未提交,在事务B中插入同样id为3的一行数据
insert student (id, name)values(3, 'haha');
报错如下:
[Err] 1062 - Duplicate entry '3' for key 'PRIMARY'
再次查询student
select * from student;
还是
id | name |
---|---|
1 | zhangsan |
2 | hehe |
可以看出,事务B中查询student,只有两行数据(id是1和2),可是插入id是3的数据,却报错说已存在id为3的数据,此为幻读。“可重复读”已经可以避免不可重复读问题。
serializable
这个就不验证了,很容易理解,“串行化”可以避免幻读问题。
总结
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
read-uncommitted | 1 | 1 | 1 |
read-committed | 0 | 1 | 1 |
repeatable-read | 0 | 0 | 1 |
serializable | 0 | 0 | 0 |
特别说明,不可重复读和幻读的区别不是太明显,不可重复读是指一个事务读到了另一个事务提交的修改,幻读是指一个事务感受到了另一个事务的增删数据。也就是说前者指的是“增删改查”中的“改”,后者指的是“增删”。