InnoDB的MVCC机制
在讲解InnoDB的MVCC机制之前,我们应该了解MySQL所支持的事务,以及各个事务级别的区别和每一个事务级别所存在的问题。
1. 事务
事务必须保证ACID,而ACID表示原子性、一致性、隔离性和持久性
1.1 事务的隔离级别
事务可以通过start transaction
语句开始一个事务,然后要么使用commit
提交事务将所修改的数据持久保存,要么使用rollback
撤销所有修改
1.1.2 READ UNCOMMITTED (未提交读 RU)
在READ UNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读。
1.1.3 READ COMMITTED (提交读 RC)
大多数的数据库系统的默认隔离级别都是READ COMMITTED(MySQL 不是)。READ COMMITTED满足前面提到的隔离级别的简单定义:一个事务开始时,只能“看见” 已提交的事务所做的修改。换句话说,一个事务从开始知道提交之前,所做的任何修改对其他事务都是不可见的。这个级别也叫不可重复读,因为在同一事务内执行两次相同的查询,可能会得到不一样的结果。
例子: 当事务的隔离级别在RC级别的时候,事务A和事务B同时对数据D操作,当事务A开始的时候,读取的数据D保存下来了,这是事务B也在修改数据D,并且先于事务A提交。这是事务A再读数据D的时候,就会出现前后不一致情况,这就是所谓的不可重复读。
1.1.4 REPEATABLE READ (可重复读 RR)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
例子:mysql的默认事务隔离级别是RR级别的,同样是上述例子,当时不同的是当事务A和事务B开始的时候,都保存一份自己的快照,每一份快照中都有数据D的值,所以这样在同一事务中,无论重读读多少次都是正确的。
例子:在RR级别中,可能出现幻读。同样是上述例子,事务A和事务B同时查询数据D,事务A发现数据D为空,就想插入数据,但是这是事务B已经插入了数据D并且已经提交。这时事务A的提交就会出错。这是因为事务A的写操作是当前读操作。
1.1.5 SERIALIZABLE (可串行化 S)
这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。
隔离级别 | 脏读可能性 | 不可重复可能性 | 幻读可能性 | 加锁读 |
---|---|---|---|---|
READ UNCOMMITTED | Yes | Yes | Yes | No |
READ COMMITTED | No | Yes | Yes | No |
REPEATABLE READ | No | No | Yes | No |
SERIALIZABLE | No | No | No | Yes |
2. MVCC机制
InnoDB的一致性的非锁定读就是通过在MVCC实现的,Mysql的大多数事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了多版本并发控制(MVCC)。MVCC的实现,是通过保存数据在某一个时间点的快照来实现的。因此每一个事务无论执行多长时间看到的数据,都是一样的。所以MVCC实现可重复读。
- 快照读:select语句默认,不加锁,MVCC实现可重复读,使用的是MVCC机制读取undo中的已经提交的数据。所以它的读取是非阻塞的
- 当前读:select语句加S锁或X锁;所有的修改操作加X锁,在select for update 的时候,才是当地前读。
RR隔离级别下的快照读,不是以begin开始的时间点作为snapshot建立时间点,而是以第一条select语句的时间点作为snapshot建立的时间点。
2.1. MVCC依赖数据
行记录隐藏字段
- db_row_id,行ID,用来生成默认聚簇索引(聚簇索引,保存的数据在物理磁盘中按顺序保存,这样相关数据保存在一起,提高查询速度)
- db_trx_id,事务ID,新开始一个事务时生成,实例内全局唯一
- db_roll_ptr,undo log指针,指向对应记录当前的undo log
- deleted_bit,删除标记位,删除时设置
undo log
-
用于行记录回滚,同时用于实现MVCC
图片1.png
2.2 操作方式
- update
- 行记录数据写入undo log,事务的回滚操作就需要undo log
- 更新行记录数据,当前事务ID写入db_trx_id,undo log指针写入db_roll_ptr
- delete
- 和update一样,只增加deleted_bit设置
- insert
- 生成undo log
- 插入行记录数据,当前事务ID写入db_trx_id, db_roll_ptr为空
这样设计使得读操作很简单,性能很好,并且也能保证只会读到符合标准的行,不足之处是每行记录都需要额外的储存空间,需要做更多的行检查工作,以及额外的维护工作
2.3 MVCC如何实现RR
- RR定义:在一个事务内同一快照读执行任意次数,得到的数据一致;且只能读到第一次执行前已经提交的数据或本事务内更改的数据
- 原理:对符合查询条件的记录进行可见性判断(就是那些数据本事务可以看见,那些数据看不见)
- read view:记录当前处于活动状态的所有事务ID,RR级别下,第一次快照读时创建,RC级别下,每次快照读均会创建新的
- 缺点: 可能出现幻读
3 总结
在事务隔离级别为RC和RR级别下, InnnoDB存储引擎使用的才是多版本并发控制。然而,对于快照数据的定义却不相同。在RC事务隔离级别下,对于快照数据(undo端数据),总是读取被锁定行的最新的一份快照数据。而在RR事务隔离级别下,对于快照数据,多版本并发控制总是读取事务开始时的行数据。