MySql中的MVCC和当前读、快照读
# MySql中的MVCC和当前读、快照读
# MVCC的作用
想到并发控制,很多人第一反应就是加锁,的确,加锁确实是解决并发问题最常见的方案。但是,其实除了加锁以外,在数据库领域,还有一种无锁的方案可以来实现并发控制,那就是大名鼎鼎的MVCC(Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来MVCC实现数据库的并发控制。
这项技术使得在InnopB的事务隔离级别下执行 一致性读 操作有了保证。换言之,就是为了查询一些正在被另-个事务更新的行,并且可以看到它们被更新之前的值,这样 在做查询的时候就不用等待另一个事务释放锁。在数据库中,对数据的操作主要有2种,分别是读和写,而在并发场景下,就可能出现以下三种情况:
读-读并发
读-写并发
写-写并发
我们都知道,在没有写的情况下发读-读并是不会出现问题的,而写-写并发这种情况比较常用的就是通过加锁的 方式实现。那么,读-写并发则可以通过MVCC的机制解决。
# 总结
MVCC(Multi-Version Concurrency Control,多版本并发控制)是MySQL中用于实现事务隔离级别的关键技术之一,特别是在InnoDB存储引擎中。MVCC允许数据库的读操作和写操作
之间不相互阻塞
,从而提高并发性能。
简单说就是,它是一种技术,用来解决——读和写并发情况中各种问题,和提高性能的。
# 快照读和当前读
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突,做到 即使有读写冲突时,也能做到 不加锁,非阻塞并发读 ,而这个读指的就是快照读
,而非 当前读
。 当前 读实际上是一种加锁的操作,是悲观锁的实现。
而MVCC本质是采用乐观锁思想的一种方式。
# 快照读
快照读又叫一致性读,读取的是快照数据。不加锁的简单的 SELECT 都属于快照读(这里有一个前提,mysql隔离级别处于RR-可重复读),即不加锁的非阻塞 读;比如,读取的是快照数据。这样:
SELECT * FROM xx_table WHERE
之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。 既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。 快照读的前提是隔离级别不是串行级别
,串行级别下的快照读会退化成当前读。
# 当前读
和快照读相对应的另外一个概念叫做当前读,当前读,读取的是记录的最新版本(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务 不能修改当前记录,会对读取的记录进行加锁
。加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:
select * from xxx_table lock in share mode; # 共享锁
select * from xx_table for update; # 排他锁
insert into xx_table values(....) # 排他锁
delete from xx_table .... # 排他锁
update xx_table set xxx = xxx where ....# 排他锁
2
3
4
5
在 MySQL 的可重复读(Repeatable Read)隔离级别下,当前读使用的锁是 Next-Key Lock(临键锁)。这种锁是 InnoDB 引擎解决幻读问题的核心机制.
在 读已提交(Read Committed) 隔离级别下,当前读 使用的锁类型主要为 行级排他锁(X锁) 或 行级共享锁(S锁),具体取决于操作类型。
# 对比总结
特性 | 快照读 | 当前读 |
---|---|---|
触发语句 | 普通SELECT | SELECT ... FOR UPDATE 、UPDATE 等 |
数据版本 | 事务开始时的快照 | 最新提交的数据 |
加锁 | 无锁 | 加行锁(X锁/S锁) |
阻塞写操作 | 否 | 是 |
适用场景 | 一致性查询(如报表) | 需要原子更新的操作(如库存扣减) |
隔离级别支持 | 所有隔离级别(行为不同) | 所有隔离级别 |
# 解决的问题
- 快照读:通过MVCC提供非阻塞的一致性视图,解决脏读、不可重复读,提升并发性能。
- 当前读:通过加锁确保操作的原子性和隔离性,解决丢失更新和幻读。
# 实际应用场景
- 快照读:适用于只读事务或需要历史数据一致性的场景(如生成报表)。
- 当前读:适用于需要基于最新数据修改的场景(如支付扣款、库存扣减)。
# MVCC实现机制
# 实现原理
- MVCC机制:通过事务版本号和Undo Log维护数据的多个版本。
- 每个事务启动时会被分配一个唯一的事务ID(Transaction ID)。
- 数据行中隐藏字段
DB_TRX_ID
记录修改该行的事务ID,DB_ROLL_PTR
指向Undo Log中旧版本数据。
- 可见性规则:事务只能看到:
- 事务ID小于当前事务ID的已提交修改。
- 自身事务的修改。
# MVCC到底解决了幻读没有
先说答案,没有解决
可重复的情况下,锁的只是当前版本的数据,也就是说是快照读,如果此时使用当前读,就可能出现幻读。
比如,A,B2个事务,B新增了一条数据,A 使用update 更新了新增的数据,之后在查询,就会发现多了一条数据。
原因核心:就是update语句用的不是快照读,for update也是一样
# 但MVCC+间隙锁解决了
间隙锁(Gap Lock)是InnoDB中的一种锁机制,用于防止其他事务在某个范围内插入新数据,从而避免幻读。
MVCC处理的是快照读的情况,通过版本链和Read View保证事务的一致性视图,这时候不会出现幻读。但当前读(比如SELECT FOR UPDATE)需要间隙锁来防止其他事务插入数据,这时候间隙锁就起作用了。当执行当前读时,InnoDB会加间隙锁,阻塞其他事务的插入,从而避免幻读。
需要明确的是两者是互补的。MVCC本身不能完全解决幻读,尤其是在当前读的情况下,必须依赖间隙锁。而间隙锁属于锁机制,和MVCC的多版本控制不同。