上一章,提到了MVCC,但只是一笔带过。这一章较为详细的同读者一起了解MVCC是如何实现可重复读的。同时,也学习一下存储过程,了解mysql为何又高效,又保证安全。文章的阅读,一次性可能没办法全看懂(当然也是我的问题,因为我不知道怎么安排顺序最好),或许需要先看MVCC原理部分,回头再看定义会更好,也仅仅是一种建议,我个人对枯燥的文字不是很感兴趣。
MVCC(Multi Version Concurrency Control的简称)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 准确的说,MVCC多版本并发控制指的是 “维持一个数据的多个版本,使得读写操作没有冲突” 这么一个概念。快照读就是MySQL为我们实现MVCC理想模型的其中一个具体非阻塞读功能。快照读本身也是一个抽象概念。MVCC模型在MySQL中的具体实现则是由 3个隐式字段,undo日志 ,Read View 等去完成的。
与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control)。MVCC最大的优势:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能。
多版本控制: 指的是一种提高并发的技术。最早的数据库系统,只有读读之间可以并发,读写,写读,写写都要阻塞。引入多版本之后,只有写写之间相互阻塞,其他三种操作都可以并行。
在内部实现中,InnoDB通过undolog可以找回数据的历史版本。找回的数据历史版本可以提供给用户读(按照隔离级别的定义,有些读请求只能看到比较老的数据版本),也可以在回滚的时候覆盖数据页上的数据。在InnoDB内部中,会记录一个全局的活跃读写事务数组,其主要用来判断事务的可见性。
每行记录除了自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
undo log主要分为两种:
undo日志版本链是指一行数据被多个事务依次修改过后,在每个事务修改完后,Mysql会保留修改前的数据undo回滚日志,并且用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链
事务进行快照读操作的时候生产的读视图(Read View),在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID,当每个事务开启时,都会被分配一个ID, 这个ID是递增的,所以最新的事务,ID值越大。
当某个事务执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前事务能够看到哪个版本的数据。
Read View遵循可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护)。
如果DB_TRX_ID跟Read View的属性做了某些比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较。
即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本
在select1执行第一条查询语句select name from account where id = 1时,日志版本链如下。
此时的read-view是:[100, 200],300。100是数组中最小的事务id,100和200是未提交的事务id,此时最大的事务id是300, 由这三个事务id构成了此时的read-view。
根据上面的比对规则,首先会从undo日志的最新记录开始查询,查询当前这个事务到底应该读到哪一条数据记录。此时最新的记录的事务id是300,300不在活跃的视图数组中,根据比较规则,这个版本是已经提交了的事务生成的,可见。因此此时select1查询到的结果是lilei300
当select1执行第二次查询时,日志版本链为
此次查询的事务在之前已经生成了read-view之后这个事务也没有进行修改操作,则它的read-view依然保持不变。 即此时的read-view: [100. 200] ,300.。
第三次查询时,undo版本链为
此次查询的事务在之前已经生成了read-view之后这个事务也没有进行修改操作,则它的read-view依然保持不变。 即此时的read-view: [100. 200] ,300.。
数据库中的数据实际上最终都是要存放在磁盘文件上的,数据库执行增删改操作的时候,不可能直接更新磁盘上的数据。磁盘进行随机读写操作,速度相当慢,一个大磁盘文件的随机读写操作,要几百毫秒,每秒只能处理几百个请求。
Innodb维护了一个缓存区域叫做Buffer Pool,用来缓存数据和索引在内存中。Buffer Pool可以用来加速数据的读写,在对数据库执行增删改操作的时候,实际上主要都是针对内存里的Buffer Pool中的数据进行的,如下图所示。
如果Buffer Pool越大,那么Mysql就越像一个内存数据库。