一、MySql的四种隔离级别

  SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。隔离级别由低到高依次为:

READ UNCOMMITTED(未提交读)

  在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可⻅的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会⽐其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有非常必要的理由,在实际应用中一般很少使用。

READ COMMITTED(提交读)

  大多数数据库系统的默认隔离级别都是READ COMMTTED(但MySQL不是)。READ COMMITTED满足前面提到的隔离性的简单定义:一个事务开始时,只能"看见"已经提交的事务所做的修改。换句话说,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候叫做不可重复读(nonrepeatble read),因为两次执行同样的查询,可能会得到不一样的结果。

REPEATABLE READ(可重复读)

  这是MYSQL的默认事务隔离级别,REPEATABLE READ解决了脏读的问题。该隔离级别保证了在同一个事务中多次读取同样记录结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻读(Phantom Row)。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)解决了幻读的问题。

SERIALIZABLE(可串行化)

SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE会在读取每一行数据都加锁,所以可能导致⼤大量的超时和锁争用问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

二、出现的问题

这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:
脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read): 在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read): 在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
在MySQL中,实现了这四种隔离级别,分别有可能产生问题如下所示:

  三、隔离级别的验证

查看当前数据库隔离级别:

select @@transaction_isolation (mysql版本 8.0 以后)

select @@tx_isolation (mysql版本 8.0 之前)

设置数据库隔离级别(仅当前会话有效):

set session transaction isolation level [隔离级别];

数据库原始记录

 1、READ UNCOMMITTED

session1:

BEGIN;
SELECT * FROM stock;  -- 原记录

session2:

BEGIN;
UPDATE stock SET stock=5, VERSION=1 WHERE id=1;

此时session1和session2结果均为

 

 可看到session1读取了session2尚未提交事务的修改,造成了脏读

2、READ COMMITTED

session1:

BEGIN;
SELECT * FROM stock;  -- 原记录

session2:

BEGIN;
UPDATE stock SET stock=5, VERSION=1 WHERE id=1;

此时session1不能读取修改数据:

 此时session2可看到自己修改数据

 可看到session2尚未提交事务的修改session不可见

当session2提交事务后:

COMMIT;

此时sesion1查询可看到session2的修改:

 session1也提交事务:

COMMIT;

同样可看到session2的修改。session1在自己事务未提交前的两个时间段查询同一条数据的结果不一样,造成了不可重复读

 3、REPEATABLE READ

session1:

BEGIN;
SELECT * FROM stock;  -- 原记录

session2:

BEGIN;
UPDATE stock SET stock=5, VERSION=1 WHERE id=1;

同read committed场景,session1不能看到记录修改,session2能看到记录修改

然后session2提交事务

COMMIT;

session1此时查询,仍无法看到session2修改

 

 session1提交事务后,方可看到session2事务提交

 

 REPEATABLE READ解决了可重复读问题,session1在自己事务未提交前的两个时间段查询同一条数据结果是一样的。但是未解决幻读问题

下面演示下幻读:

session1开启事务,然后查询

session2开启事务,插入一条数据

BEGIN;
INSERT INTO stock(id, NAME, stock, VERSION) VALUES(4,'vivo xs', 1, 0);

此时session1无法看到插入数据,session2可看到插入数据

session2提交事务后,session1也无法看到插入的数据(幻读应该能看到,未演示出来,只是有幻读的可能性)

session1提交事务后方可看到插入的数据

可以这样演示:

 4、可串行化(Serializable) 不再演示

A:启动事务,查询表数据,此时数据为初始状态
B:插入一条记录,发现B此时进入了等待状态,原因是因为A的事务尚未提交,只能等待(此时,B可能会发生等待超时)
A:提交事务
B:发现插入成功
serializable完全锁定字段,若一个事务来更新数据就必须等待,直到前一个事务完成并解除锁定为止。是完整的隔离级别,会锁定对应的整个表格,因而会有效率的问题。读读操作不会加锁,涉及写操作均会锁表