在一个技术群上看到一个同学问了这样的一个问题:“mysql的redolog机制是异步把数据刷入磁盘页中,在还没刷入成功的时候进行查询是不是查询不到”,简单聊聊我的理解。
如何理解‘redolog机制’
假定,共识内的redolog并非出自mysql,而是来自innodb引擎。关于redolog机制,我们应该知道什么?
1、解决什么问题
2、数据结构
redolog机制解决了什么问题?
先说个人认为的结论:
1、carsh safe
2、随机写优化为顺序写。
众所周知,mysql的数据是持久化在磁盘中的,但是不能每一次读写都直接在磁盘上进行吧?为了提高性能,innodb增加了一个缓存池buffer poll,其次操作系统本身是有一层缓存数据结构,即为os cache,常常说page cache。基于一个共识为:缓存的性能比磁盘的性能更好上,如果每一次对数据的写入都要同步到磁盘中,在性能上会带来极大压力,而且还是对磁盘的随机写入?那么能不能有一些优化方式,将写入优化为批量写,或者闲时写等?wal(write ahead log)正式在这样的情况下被引入,它有两个目的:1、将修改放在缓存,2、将修改写入日志文件。所以它避免了缓存直接写入磁盘,其次顺序写入日志文件,比随机写的性能大大提高。
由于修改只发生在缓存,在应用或者机器发生故障,比如carsh时,通过日志文件上的内容可以对发生故障时还未写入磁盘的缓存页进行恢复,以此达到crash safe的能力。但是,要知道,innodb默认的buffer poll所管理的页是16k,而操作系统默认的page cache的页大小是4k,而对于redo log而言,它存在一个弊端,即只能修复完整的数据页,什么是完整的数据页?当在进行缓存刷盘时,数据页要么完整的被刷入到磁盘中,要么保留磁盘中原来的页的数据,即缓存中的脏页上的数据还未进行刷盘。而由于应用也与操作系统页大小不一致,在刷盘时如果发生意外,即可能导致对于buffer poll中的页只刷了一部分到操作系统,还有一部分为刷,这样的也即为不完整的页,也是redo log无法解决的问题,因为redo log记录的是数据页上的变动,所以又引入了double write技术,刷盘的双写,当出现不完整的页时通过double write log来恢复。
除此之外,由于当下的数据库一般都开启bin log模式,所以redo log与bin log之间保持一致也是一个技术难点,因为binlog归属于mysql层,redolog归属于存储引擎层,为此又引入了2pc协议来解决这个问题。设想,如果先写redolog,再写binlog,当binlog缺失时,redolog在进行恢复时没有办法将恢复的内容同步到binlog,因为它们间状态不互可知。同样的,先写binlog,再写redolog,当redolog缺失时,其他根据binlog来回放的节点的数据就发生了错误。2pc怎么解决这个问题?对于事务的commit阶段,可以粗略看成是:先写redolog prepare,再写binlog,最后再写redolog commit。如果只有prepare而没有binlog,则不做恢复,如果有prepare也有binlog,则恢复。
redolog的数据结构
4个(可配置)可重复使用的文件,从逻辑视角上看,是一个环形的数据结构。写入的逻辑指针为pos,刷盘的逻辑指针为checkpoint。
如何理解’异步把数据刷入磁盘页中’
为何说是异步把数据刷入磁盘页中呢?
buffer poll的数据放在缓存层,为的就是提高mysql的读写性能,但是数据最终都要写入到磁盘中,进行持久化,磁盘不发生损坏的情况下保证了数据的可靠性。如果同步刷盘,性能将会大打折扣,所以异步是必须的,那么另一个问题就浮出水面,什么时候进行刷盘呢?
- mysql空闲时
- redolog的pos指针追上checkpoint指针,‘redolog用完了’
- 定时的进行刷盘
- ...
另外一个问题,刷盘的时候,是刷的buffer poll上的数据,还是刷的redolog上的数据?当事务在开启时,每执行一个写语句,会做两个事:
1、修改buffer poll内存上的数据
2、将变动的物理修改写入redolog buffer
当事务进行commit时,会将redolog buffer的内容写入到redolog文件中,过程如上讲,先写prepare,再写binglog,最后写commit。而mysql的脏页刷盘,并不是从redolog上进行刷盘,而是直接将buffer poll上的内容进行刷盘,redolog只有再恢复的时候才会使用。
如果刷盘用的是buffer poll,但是在事务开启时就会将修改写入到内存上,那会不会事务还没提交就被刷盘了呢?刷盘后遇到回滚怎么办?
事务没有提交就发生刷盘,肯定不会。那遇到需要回滚时,怎么办呢?数据库事务未提交时,会将事务修改数据的镜像(即修改前的旧版本)存放到undolog中,当事务回滚时,可以利用undolog,即旧版本数据,撤销未提交事务对数据库产生的影响。
如何理解’在还没有刷成功时,进行查询是否查得到’
事务开启时,修改就已经发生在了内存,那么进行查询的时候,是不是会查到?这里会涉及到事务的隔离级别的问题,其次还会涉及到innodb中大名鼎鼎的mvcc。
为什么会有mvcc(mutile version concurrency control)?在提到这个问题时,我们先思考一个问题,mysql作为一个数据存储层面临并发问题时要如何处理?
毫无疑问是锁,可是锁的成本很高,所有的访问都加锁,数据库的整个性能都下降了,读写锁有所优化,但是还不够,那么有没有无锁的设计模式?mvcc就是一种无锁的设计方法论。由于业务场景大多数对于锁没有很高的要求,并没有一定要求严格的读写顺序,在【某种程度】上允许读到【旧值】,所以数据多版本应运而生,当然,有时候有些场景也需要按照严格的读写顺序,所以mysql也允许这么做:
select ,快照读,因为undolog中保存着历史版本的快照数据,随着事务的提交在合适的时机删除
select …. lock in share mode,加读锁
select …. for update, 加写锁
怎么实现的数据多版本?前文提到undolog,里边存放着事务修改数据的旧版本内容,当多个事务进行操作时,也就会产生多个不同版本的内容,这便构成了数据的多版本,那么另外一个问题就是,每次进行select究竟应该怎么选择合适的版本?
- 没有事务时,从这些版本中选择最新的已经提交的事务作为响应内容返回
- 起了事务时,会根据事务的隔离级别进行判断
- 读未提交:…..
- 读已提交:在每一次读时都会读取最新已提交的事务的结果,所以没有读脏的问题,但是重复读时会产生幻读
- 可重复读:在事务第一次读时会读取最新已提交的事务的结果,之后再读的结果是一样的,所以没有幻读
-可串行读:…..
前文提到的快照读,实质上应该叫:一致性快照读。而根据事务的隔离级别select的结果,实质上应该叫:一致性视图(read-view),所以不同的隔离级别,只是在构建read-view的时机不一样。无锁的读取,正是innodb能做到如何高的并发的原因之一。
所以回到话题’在还没刷成功时,进行查询是否查得到’,首先buffer poll里边是最新的值,但是这个值所属的事务是否提交并未可知,说是否刷成功实际上应该表述为事务是否提交更好理解一下,其次查询是否会被查到?明显要看隔离级别。说到这里,想到另外一个问题,当时数据已经写入到buffer poll,但是事务还未提交时,新的事务要对它进行操作,如何?修改时,会上锁,所以会被阻塞。
网友评论