四种隔离级别概述

1.未提交读(read-uncommitted)

在一个事务中,可以读取到其他事务未提交的数据变化,这种读取其他会话还没提交的事务,叫做脏读现象,在生产环境中切勿使用。

2.已提交读(read-committed)

在一个事务中,可以读取到其他事务已经提交的数据变化,这种读取也就叫做不可重复读,因为两次同样的查询可能会得到不一样的结果。

3.可重复读(repetable-read)

MySQL默认隔离级别,在一个事务中,直到事务结束前,都可以反复读取到事务刚开始时看到的数据,并一直不会发生变化,避免了脏读、不可重复读现象,但是它还是无法解决幻读问题。

4.可串行化(serializable)

这是最高的隔离级别,它强制事务串行执行,避免了前面说的幻读现象,简单来说,它会在读取的每一行数据上都加锁,所以可能会导致大量的超时和锁争用问题。

上边的叙述,我想很多人都很熟悉,但是针对叙述中所说的查询,大家是否有一个清晰的认识呢?是select还是select 。。。for update?还是都可以?

或者换种问法,针对3.可重复读的叙述(在事务结束前,查询看到的数据是不会变化的)是如何查询?是普通的select...还是select...for update,还是都可以?

带着这个问题,我们做一下分析。

需要懂的概念

当前读:

  select...lock in share mode (共享读锁)
  select...for update
  update , delete , insert

  当前读, 读取的是最新版本, 并且对读取的记录加锁, 阻塞其他事务同时改动相同记录,避免出现安全问题

  例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。

 快照读

  单纯的select操作,不包括上述 select ... lock in share mode, select ... for update。    

  Read Committed隔离级别:每次select都生成一个快照读。

  Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。

分析

通过上边的介绍,我们猜想,可重复读叙述中的查询,应该是针对快照读来说的。而不是当前读。

针对这个结论我们可以测试一下。

测试

打开Navicat,右键数据库运行两个sql命令行。

输入以下命令,查看当前事务级别,如果要是查询全局的就加上global.。

#mysql8以下版本
select @@global.tx_isolation;
#mysql8以上版本
select @@global.transaction_isolation

一般默认是REPEATABLE-READ可重复读。

那就先测试可重复读。

首先将在两个窗口中,设置自动提交false。通过命令查看当前的自动提交状态。

show variables like 'autocommit';

默认是开启的,就是自动提交事务。

通过以下命令来设置为false。

set autocommit=0;

在两个窗口都修改为自动提交false后。开始测试,设两个窗口分别为A和B。他们共同操作t_deviceinfo表

同时开启A\B的事务。

begin;

首先让A修改t_deviceinfo表id为107的DeviceName值。

update t_deviceinfo set DeviceName='111' where ID=107;

然后让B查看这个id的值,看是否改变。按照概述中第3点,是不会改变的,

在B中执行查看语句,使用快照读方式,

select * from t_deviceinfo where ID=107;

结果发现确实没有改变。

那继续在B中执行查看语句,使用当前读方式。

select * from t_deviceinfo where ID=107 for update;

点击回车后,发现程序处于等待。这是因为获取锁处于阻塞,需要等到锁释放才能查询,那么我们就将A commit来释放锁。

commit;

然后B再执行当前读。发现查询结果是A修改后的结果。

在B中再执行快照度,发现查询结果是没有变化,还是A修改前的结果,满足概述中第3条的理论。

这样看来。概述中说的查询是使用快照读,这块我们要有清晰的认识。

接下来看下读已提交是不是这样。

通过以下命令来修改隔离级别。

set session transaction isolation level read committed;

同样执行上边的操作,发现结论是一样的。

总结

通过上边的分析,我们知道,select for update 方式,不论在哪种隔离级别都会获取到最新的数据。而select的方式,是根据事务隔离级别的不同,查询结果也是不一样的。

为什么会不一样呢?是因为在不同的事务级别,快照读的生成时机是不一样的:

  • Read Committed隔离级别:每次select都生成一个快照读
  • Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读。