事务的隔离级别如下所示:
隔离级别 | 脏读 | 可重复读 | 幻象读 |
RU | 可能 | 可能 | 可能 |
RC | 不可能 | 可能 | 可能 |
RR | 不可能 | 不可能 | 可能 |
Serializable | 不可能 | 不可能 | 不可能 |
脏读:当前事务可能读到其它事务未提交的数据。
可重复读:当前事务内,多次读取同一份数据是一致的,不受其它事务的update,delete影响。
幻象读:当前事务内,读到其它事务insert的数据。
数据库在实现这些隔离级别时,底层是通过锁机制来支持的。
在锁对象类型上,锁的类型分为表锁,行锁,Gap锁等。
在锁读写方式上,锁分为读锁(S),写锁(X)等。
RC隔离级别下,支持读已提交事务的数据。因为读取不加锁,写入,修改,删除是加行锁的。故不支持可重复读,有幻象读。
RR隔离级别下,支持可重复读。因为读取时,会在行上加行锁读锁(S);写入,修改,删除时加行锁写锁(X),故支持可重复读,有幻象读。
但是MySQL对此作了一定的优化,通过引入Gap锁,禁止在行区间范围内的写入操作,解决了幻象读的问题。故MySQL上RR隔离级别不会有幻象读。
Serializable隔离级别下,支持可重复读,无幻象读。因为读取时,会在表上加表锁读锁(S),编辑时,会在表上加表锁写锁(X),故支持可重复读,不会存在幻象读。
一般应用使用RC级别,在写入,修改,删除场景下,MySQL在where命中索引时,是会在命中行上加行锁。但是如果无法命中索引时,存储引擎返回MySQL Server时,会在表的所有记录上加上行锁。同样的情况,RR隔离级别下,会在表的所有行之间加上Gap锁。目前MySQL为了性能优化,会在MySQL Server过滤数据,在不满足过滤条件的记录上会解除行锁。但是不会释放Gap锁。
MySQL的MVCC协议的镜像读&当前读
MVCC协议保证了并发下的查询性能&一致性。该协议会使用乐观锁机制,基于事务版本号,在每一个行记录上加上了2个隐藏列,分别记录创建时的事务版本号,删除时的事务版本号。
select时,会查询<=当前事务版本号的记录,过滤有删除版本号的记录。
insert时,会记录当前事务版本号。
delete时,会记录当前事务版本号为删除版本号。
update时,会先删除原始记录,在原始记录上记录删除版本号;然后新增新记录,在新纪录上记录新增版本号。
基于这个机制,那一般的select查询,都是镜像读,因为可能读取到历史版本,不一定是最新的版本。
特殊的select比如:
select * from xxx where ? lock in share mode(共享锁模式下的当前读。这种情况下,允许其它事务读当前记录,不允许写当前记录。)
select * from xxx where ? for update;(排它锁模式下的当前读。这种情况下,不允许其它事务读写当前记录)
与update,delete,insert等操作,都是当前读。当前读保证读取到记录的最新版本数据。
参考资料: