本文概览:子啊“可重复读”的mysql事务隔离级别下分析线上一个bug。
1 问题
正常的代码,通过select…for update上锁。
引入bug,在上面代码流程中添加了一个防重查询。发现在原来的操作中无法进行防重了
问题复现流程如下,在STEP7中没有进行防重
2 问题分析
2.1 事务隔离属性&实现
1、隔离属性
SQL标准的事务隔离级别包括:读未提交(read uncommitted)、 读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。:
- 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一 致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
- 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突 的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
- 若隔离级别是“读未提交”, 则V1的值就是2。这时候事务B虽然还没有提交,但是结果已经被 A看到了。因此,V2、V3也都是2。
- 若隔离级别是“读提交”,则V1是1,V2的值是2。事务B的更新在提交后才能被A看到。所以, V3的值也是2。
- 若隔离级别是“可重复读”,则V1、V2是1,V3是2。之所以V2还是1,遵循的就是这个要求: 事务在执行期间看到的数据前后必须是一致的。
- 若隔离级别是“串行化”,则在事务B执行“将1改成2”的时候,会被锁住。直到事务A提交后, 事务B才可以继续执行。所以从A的角度看, V1、V2值是1,V3的值是2。
2、事务隔离实现
mysql事务隔离是通过视图实现的。在数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
- 在“可重复读”隔离 级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
- 在“读提交”隔离级 别下,这个视图是在每个SQL语句开始执行的时候创建的。
- “读未提交”隔离 级别下直接返回记录上的最新值,没有视图概念;
- “串行化”隔离级别下直接用加锁的方式来避 免并行访问。
2.2 问题定位
mysql的默认事务隔离为“可重复读”,Oracle是“读提交”。
通过如下一个例子来分析,发现在“8”步中获取到数据为空,这个原因是在STEP2中获取了视图,那么在这个事务中会一直使用这个视图,所以在8中没有获取到数据。
为了验证,我们在STEP9提交事务,然后STEP10发现有了数据。所以把上图中STEP2去掉