1、当前读和快照读
- 快照读:读取的是记录的可见版本 (有可能是历史版本),不用加锁。简单纯粹的查询操作,属于快照读。
SELECT * FROM student WHERE id=1;
- 当前读:读取的是记录的最新版本,并且当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。特殊的查询操作、插入、更新、删除操作,属于当前读。
所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。SELECT * FROM student WHERE id=1 LOCK IN SHARE MODE; //共享锁(S) SELECT * FROM student WHERE id=1 FOR UPDATE; //排他锁(X) INSERT INTO student VALUES (1, '张三'); //排他锁(X) UPDATE student SET name='李四' WHERE id=1; //排他锁(X) DELETE FROM student WHERE id=1; //排他锁(X)
为什么将插入、更新、删除操作,都归为当前读? 可以看看下面这个更新操作,在数据库中的执行流程:
UPDATE student SET name='李四' WHERE id=1;
从图中,可以看到一个Update操作的具体流程:
- 当Update SQL被发给MySQL后,MySQL Server会根据where条件,读取第一条满足条件的记录,然后InnoDB引擎会将第一条记录返回,并加锁 (current read)。
- 待MySQL Server收到这条加锁的记录之后,会再发起一个Update请求,更新这条记录。
- 一条记录操作完成,再读取下一条记录,直至没有满足条件的记录为止。
因此,Update操作内部,就包含了一个当前读。同理,Delete操作也一样。Insert操作会稍微有些不同,Insert可能会触发Unique Key的冲突检查,也会进行一个当前读。
根据上图的交互,针对一条当前读的SQL语句,InnoDB与MySQL Server的交互,是一条一条进行的,因此,加锁也是一条一条进行的:先对一条满足条件的记录加锁,返回给MySQL Server,做一些DML操作;然后在读取下一条加锁,直至读取完毕。
2、一致性非锁定读(consistent nonlocking read)
一致性非锁定读是指InnoDB存储引擎通过多版本控制(MVCC)读取当前数据库中行数据的方式。如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB会去读取行的一个快照。
上图直观地展现了InnoDB一致性非锁定读的机制。之所以称其为非锁定读,是因为不需要等待行上排他锁的释放。快照数据是指该行的之前版本的数据,每行记录可能有多个版本,一般称这种技术为行多版本技术。由此带来的并发控制,称之为多版本并发控制(Multi Version Concurrency Control / MVCC)。InnoDB是通过undo log来实现MVCC。
在事务隔离级别RC和RR下,InnoDB默认使用一致性非锁定读。然而,对于快照数据的定义却不同:
- 在RC下,一致性非锁定读总是读取被锁定行的最新一份快照数据。
- 在RR下,读取事务开始时的行数据版本。
举个栗子:
在会话B的事务中,将student表中id为1的记录修改为id=3,但是事务同样也没有提交,这样id=1的行其实加了一个排他锁。由于InnoDB在READ COMMITTED和REPEATABLE READ事务隔离级别下使用一致性非锁定读,这时如果会话A再次读取id为1的记录,仍然能够读取到相同的数据。此时,RC和RR事务隔离级别没有任何区别。
如上图所示,当会话B提交事务后,会话A再次运行SELECT * FROM student WHERE id=1;
的SQL语句时,两个事务隔离级别下得到的结果就不一样了:
- 对于RC事务隔离级别,它总是读取行的最新版本,如果行被锁定了,则读取该行版本的最新一个快照。因为会话B的事务已经提交,所以在该隔离级别下上述SQL语句的结果集是空的。
- 对于RR事务隔离级别,总是读取事务开始时的行数据,因此,在该隔离级别下,上述SQL语句仍然会获得相同的数据。
3、InnoDB的MVCC实现
我们首先来看一下wiki上对MVCC的定义:
Multiversion concurrency control (MCC or MVCC), is a concurrency control method commonly used by database management systems to provide concurrent access to the database and in programming languages to implement transactional memory.
翻译:多版本并发控制(MCC 或 MVCC)是数据库管理系统常用的一种并发控制方法,用于提供对数据库的并发访问,并在编程语言中实现事务性内存。
由定义可知,MVCC是用于数据库提供并发访问控制的并发控制技术。与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control。
MVCC最大的好处就是:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,因为它极大的增加了系统的并发性能,这也是为什么现阶段,几乎所有的RDBMS,都支持了MVCC。
多版本并发控制仅仅是一种技术概念,并没有统一的实现标准, 其核心理念就是数据快照。不同的事务访问不同版本的数据快照,从而实现不同的事务隔离级别。虽然字面上是说具有多个版本的数据快照,但这并不意味着数据库必须拷贝数据,保存多份数据文件,这样会浪费大量的存储空间。InnoDB通过事务的undo日志巧妙地实现了多版本的数据快照。数据库的事务有时需要进行回滚操作,这时就需要对之前的操作进行undo。因此,在对数据进行修改时,InnoDB会产生undo log。当事务需要进行回滚时,InnoDB可以利用这些undo log将数据回滚到修改之前的样子。
从上边的描述中我们可以看出来,所谓的MVCC指的就是在使用RC、RR这两种隔离级别的事务在执行普通的SEELCT操作时访问记录的版本链的过程,这样子可以使不同事务的“读-写”、“写-读”操作并发执行,从而提升系统性能。
RC、RR两个隔离级别的一个很大不同就是生成ReadView的时机不同,RC在每一次进行普通 SELECT 操作前都会生成一个ReadView,而RR只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复这个ReadView就好了。此处可参考《MySQL事务 - ReadView》
总结:MVCC是一种思想,MySQL使用undolog和ReadView可见性判断去实现了MVCC。 MVCC的最大好处就是读不加锁,读写不冲突,同时还能保证事务的隔离性。