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写操作 | 阻塞 | 阻塞 |
四种隔离级别对比
脏读 | 不可重复度 | 幻读 | |
---|---|---|---|
读未提交 | 可能 | 可能 | 可能 |
读已提交 | 不会 | 可能 | 可能 |
可重复度 | 不会 | 不会 | 可能 |
可串行化 | 不会 | 不会 | 不会 |