前置
读未提交 | 读已提交 | 可重复读 | 串行 | |
脏读:读到了未提交的数据 | v | x | x | x |
重复读:同一个事务内对某个特定数据查询的结果不一致(针对update操作) | v | v | x | x |
幻读:同一个事务内对某些数据查询的结果集不一致(针对insert操作) | v | v | x(如果不更新的话是不会幻读的) | x |
名词解释
MVCC | 多版本并发控制 |
活跃事务集合 | 已开始但是没有提交的事务 |
traceId | 事务id 递增 |
版本号 | 每条数据都会有对应的一个版本号其实就是更新这条数据的事务id |
快照读 | mysql 默认的读取方式 select,会产生一个活跃事务的快照 |
当前读 | select ... for update 通过该方式获取的数据是最新的数据不存在数据的隔离,并且该方式会对查询范围内数据加上一个间隙锁,其他事务无法修改插入删除 |
间隙锁 | 当显示使用for update 查询时会对查询的记录行添加范围锁使用唯一索引时会转化为行锁,不使用索引则添加表锁 |
INNODB 事务隔离的实现
假设目前存在2个事务 A和B
A事务开启后 开始查询今天的订单表数据,此时的查询=快照读,会获取当前的的活跃事务id集合(快照)
此时B事务开启并且插入了一条数据到订单表后提交
A事务再次查询今天的订单数据,此时会获取每条订单数据对应的版本号 有以下判断
如果改数据的版本号小于活跃事务id集合里面的最小id 那么该版本已提交,该数据对于当前事务是可见的;
如果该数据的版本号大于活跃事务id集合里面的最大id 那么该版本是一个新开启的事务,对当前事务是不可见的;
如果该数据的版本号大于最小id 小于最大id 那么再判断该版本是否存在于集合中,如果存在说明该版本在当前事务开启时是处于未提交状态,所以是不可见的;如果不存在则说明该版本在产生快照的时刻是已经提交的,所以是可见的
当数据的版本对于当前事务是不可见的时候,会去从undolog 中获取更早的版本号再次比对直到该数据可见或者没有更早的版本号
最终 的结果是 A事务无法查询到刚刚B事务插入的数据
但是此时A事务进行一次批量的更新
如把今天的订单统一改为异常订单状态
此时B事务插入的数据同样会被修改
A事务再次查询今日订单数据则可以查到B事务刚刚插入的数据
原因是:
事务中的update delete 操作都是先读后操作 该读指的是当前读 也就读取最新的数据然后更新 更新完后该数据的版本号就变成了当前的事务id,该数据在当前事务中是可见的.
为了避免先读后改在并发时会出现数据的覆盖,所以在每次更新或者和删除时事务需要获取到该数据的行锁,等待事务结束后释放
如果该更新或者删除操作没有加索引那么会获取表锁,所以事务中的任何更新操作都要记得加上索引