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