MySQL-事务的隔离级别

Posted by yycoder on August 27, 2022

MySQL-事务的隔离级别

实验数据准备

mysql> create table city(
    -> id int(10) auto_increment,
    -> name varchar(30),
    -> primary key (id)
    -> )engine=innodb charset=utf8mb4;

insert into city(name) values('武汉市');

mysql> select * from city;
+----+-----------+
| id | name |
+----+-----------+
| 1 | 武汉市 |
+----+-----------+

事务并发可能出现的问题

脏读(Dirty Read

一个事务读到了另一个未提交事务修改过的数据

  事务A 事务B
1 begin;  
2   begin;
3   update city set name=’温州市’ where id = 1;
4 <p>select name from city where id =1;
读取的记录为温州市,出现了脏读</p>
 
5 commit;  
6   rollback;

会话B开启一个事务,把id=1的name为武汉市修改成温州市,此时另外一个会话A也开启一个事务,读取id=1的name,此时的查询结果为温州市,会话B的事务最后回滚了刚才修改的记录,这样会话A读到的数据是不存在的,这个现象就是脏读。(脏读只在读未提交隔离级别才会出现)

不可重复读(Non-Repeatable Read)

一个事务只能读到另一个已经提交事务修改过的数据,并且其他事务对该数据每进行一次修改并提交后,该事务都能查询到最新值。(不可重复读在读未提交和读已提交隔离级别都可能会出现)

  事务A 事务B
1 begin;  
2 <p>select name from city where id = 1;
读取的记录为武汉市</p>
 
3   update city set name=’温州市’ where id = 1;
4 <p>select name from city where id = 1;
读取的记录为温州市,此时出现不可重复度</p>
 
5   update city set name=’杭州市’ where id = 1;
6 <p>select name from city where id = 1;
读取的记录为杭州市,此时出现不可重复度</p>
 
7 commit ;  

会话A开启一个事务,查询id=1的结果,此时查询的结果name为武汉市。接着会话B把id=1的name修改为温州市(隐式事务,因为此时的autocommit为1,每条SQL语句执行完自动提交),此时会话A的事务再一次查询id=1的结果,读取的结果name为温州市。会话B再此修改id=1的name为杭州市,会话A的事务再次查询id=1,结果name的值为杭州市,这种现象就是不可重复读。

幻读(Phantom)

一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来。(幻读在读未提交、读已提交、可重复读隔离级别都可能会出现)

  事务A 事务B
1 begin;  
2 <p>select name from city where id > 1;
读取的记录为武汉市</p>
 
3   insert into city(name) values (‘温州市’);
4 <p>select name from city where id > 1;
读取的记录为武汉市和温州市,出现幻读</p>
 
5 commit;  

会话A开启一个事务,查询id>0的记录,此时会查到name=武汉市的记录。接着会话B插入一条name=温州市的数据(隐式事务,因为此时的autocommit为1,每条SQL语句执行完自动提交),这时会话A的事务再以刚才的查询条件(id>0)再一次查询,此时会出现两条记录(name为武汉市和温州市的记录),这种现象就是幻读。

MySQL事务的隔离级别

MySQL的事务隔离级别一共有四个,分别是读未提交读已提交可重复读以及可串行化

MySQL的隔离级别的作用就是让事务之间互相隔离,互不影响,这样可以保证事务的一致性。

隔离级别比较:可串行化>可重复读>读已提交>读未提交

隔离级别对性能的影响比较:可串行化>可重复读>读已提交>读未提交

由此看出,隔离级别越高,所需要消耗的MySQL性能越大(如事务并发严重性),为了平衡二者,一般建议设置的隔离级别为可重复读,MySQL默认的隔离级别也是可重复读。

读未提交(READ UNCOMMITTED)

  事务A 事务B
1 begin;  
2 update city set name=’温州市’ where id = 1; begin;
3   <p>select name from city where id = 1;
读取的记录为温州市</p>
4 commit;  
5   <p>select name from city where id = 1;
读取的记录为温州市</p>

在读未提交隔离级别下,事务A可以读取到事务B修改过但未提交的数据。

可能发生脏读、不可重复读和幻读问题,一般很少使用此隔离级别。

读已提交(READ COMMITTED)

  事务A 事务B
1 begin;  
2 update city set name=’温州市’ where id = 1; begin;
3   <p>select name from city where id = 1;
读取的记录为武汉市</p>
4 commit;  
5   <p>select name from city where id = 1;
读取的记录为温州市</p>

在读已提交隔离级别下,事务B只能在事务A修改过并且已提交后才能读取到事务B修改的数据。

读已提交隔离级别解决了脏读的问题,但可能发生不可重复读和幻读问题,一般很少使用此隔离级别。

可重复读(REPEATABLE READ)

  事务A 事务B
1 begin;  
2 update city set name=’温州市’ where id = 1; begin;
3 <p>select name from city where id = 1;
读取的记录为温州市</p>
<p>select name from city where id = 1;
读取的记录为武汉市</p>
4 commit;  
5   commit;
6   <p>select name from city where id = 1;
读取的记录为温州市</p>

在可重复读隔离级别下,事务B只能在事务A修改过数据并提交后,自己也提交事务后,才能读取到事务B修改的数据。

可重复读隔离级别解决了脏读和不可重复读的问题,但可能发生幻读问题。

提问:为什么上了写锁(写操作)别的事务还可以读操作?

因为InnoDB有MVCC机制(多版本并发控制),可以使用快照读,而不会被阻塞。

可串行化(SERIALIZABLE)

串行化各种问题(脏读、不可重复读、幻读)都不会发生,通过加锁实现(读锁和写锁)。

读读操作不阻塞

  事务A 事务B
1 begin;  
2 <p>select name from city where id = 1;
读取的记录为武汉市</p>
begin;
3   <p>select name from city where id = 1;
读取的记录为武汉市,说明事务A的读操作不会影响事务B的读操作</p>

读写操作会阻塞

  事务A 事务B
1 begin;  
2 <p>select name from city where id = 1;
读取的记录为武汉市</p>
begin;
3   <p>update city set name=’温州市’ where id = 1;
此时修改操作会被阻塞,说明事务A的读操作会阻塞事务B的写操作</p>
4 commit;  
5   此时写操作才会执行

写读操作会阻塞

  事务A 事务B
1 begin;  
2 update city set name=’温州市’ where id = 1; begin;
3   <p>select name from city where id = 1;
此时读操作会被阻塞,说明事务A的写操作会阻塞事务B的读操作</p>
4 commit;  
5   此时读操作才会执行,读取到的数据是温州市

写写操作会阻塞

  事务A 事务B
1 begin;  
2 update city set name=’温州市’ where id = 1; begin;
3   <p>update city set name=’杭州市’ where id = 1;
此时写操作会被阻塞,说明事务A的写操作会阻塞事务B的写操作</p>
4 commit;  
5 <p>select name from city where id = 1;
此时读取到的数据是温州市</p>
此时修改操作才会被执行
6   <p>select name from city where id = 1;
此时读取到的数据是杭州市</p>
7   commit;

串行化隔离级别阻塞区别

  事务A读操作 事务A写操作
事务B读操作 不阻塞 阻塞
事务B写操作 阻塞 阻塞

四种隔离级别对比

  脏读 不可重复度 幻读
读未提交 可能 可能 可能
读已提交 不会 可能 可能
可重复度 不会 不会 可能
可串行化 不会 不会 不会