InnoDB之Dirty page、Redo log

在InnoDB中,buffer pool里面的dirty page一方面可以加快数据处理速度,同时也会造成数据的不一致(RAM vs DISK)。本文介绍了dirty page是如何产生,以及InnoDB如何利用redo log如何消除dirty page产生的数据不一致。

  1. 当事务(Transaction)需要修改某条记录(row)时,InnoDB需要将该数据所在的page从disk读到buffer pool中,事务提交后,InnoDB修改page中的记录(row)。这时buffer pool中的page就已经和disk中的不一样了,我们称buffer pool中的page为dirty page。Dirty page等待flush到disk上。
    dirty_pages
  2. dirty page既然是在Buffer pool中,那么如果系统突然断电Dirty page中的数据修改是否会丢失?这个担心是很有必要的,例如如果一个用户完成一个操作(数据库完成了一个事务,page已经在buffer pool中修改,但dirty page尚未flush),这时系统断电,buffer pool数据全部消失。那么,这个用户完成的操作(导致的数据库修改)是否会丢失呢?答案是不会(innodb_flush_log_at_trx_commit=1)。这就是redo log要做的事情,在disk上记录更新。
  3. redo log在每次事务commit的时候,就立刻将事务更改操作记录到redo log。所以即使buffer pool中的dirty page在断电时丢失,InnoDB在启动时,仍然会根据redo log中的记录完成数据恢复。
  4. redo log的另一个作用是,通过延迟dirty page的flush最小化磁盘的random writes。(redo log会合并一段时间内TRX对某个page的修改)
    dirty_pages AND redo_log
  5. 正常情况下,dirty page什么时候flush到disk上?
    1).redo log是一个环(ring)结构,当redo空间占满时,将会将部分dirty page flush到disk上,然后释放部分redo log。这种情况可以通过Innodb_log_wait(SHOW GLOBAL STATUS)观察,情况发生该计数器会自增一次。
    2).当需要在Buffer pool分配一个page,但是已经满了,并且所有的page都是dirty的(否则可以释放不dirty的page),通常是不会发生的。这时候必须flush dirty pages to disk。这种情况将会记录到Innodb_buffer_pool_wait_free中。一般地,可以可以通过启动参数innodb_max_dirty_pages_pct控制这种情况,当buffer pool中的dirty page到达这个比例的时候,将会强制设定一个checkpoint,并把dirty page flush到disk中。
    3).检测到系统空闲的时候,会flush,每次64 pages。
  6. 涉及的InnoDB配置参数:innodb_flush_log_at_trx_commit、innodb_max_dirty_pages_pct;状态参数:Innodb_log_wait、Innodb_buffer_pool_wait_free。

参考文献

  1. http://mysqldump.azundris.com/archives/78-Configuring-InnoDB-An-InnoDB-tutorial.html
  2. http://dev.mysql.com/doc/refman/5.0/en/innodb.html

25 responses to “InnoDB之Dirty page、Redo log”

  1. […] 涉及到的概念:Buffer Pool简称BP,Dirty Page,Log file,Flush,innodb tablespace。 […]

  2. Anonymous

    我想问下 flush到disk的时候!~ LSN是怎么记录的,就比喻发生在第二种情况 dirty page满了,它flush到disk的时候是会把LSN记录到data file里的,假设此时并没有事务发生时,也就是说log buffer没有数据记录到redo log里 ,此时data file的LSN 比redo log的LSN 小还是一样!~ 有没有情况data file的LSN会比redo log的LSN大的情况

  3. 数据库始终是以“日志先行”的方式运行的,所以redo log里面最后一条LSN始终是最大的。Buffer Pool中的Page记录了两个LSN,一个是已经Flush到data file中最后一个LSN,还一个是修改了该Page的最后一个LSN。

  4. slevin

    每当插入一条数据,如果未提交,innodb会不会将当前行设为新增行的版本号?如果要提交才会增加的话,那它未提交前数据是存在哪里呢?

  5. slevin

    呵呵!~发现问题有点小白了!~未提交的数据肯定是存在内存里,没提交它肯定不会把当前行设为新增行的版本号

  6. 问题没有“小白”啊,不过猜想应该是在内存中的。

    第一,没提交的事务是没有写到redo中的,如果不是在内存而是在磁盘的话,崩溃恢复会非常麻烦,即使崩溃时未flush的dirty page不多,InnoDB仍然需要扫描每一个数据页,看看有没有没有提交的事务的数据,如果数据量大,这个过程是很痛苦的。事实上,也并不是这样,如果崩溃时事务量不大,无论数据量大小恢复都是很快的。

    第二,因为如果事务隔离级别是uncommit read的话,这样的数据其他的事务也能读取,说明这个数据是放在公共的地方的,所以猜想内存中的数据页应该已经写入了这些数据和这些数据的事务ID。

    猜想,猜想而已。

    数据行的版本号就是事务ID号,事务开始了就有事务ID,写到磁盘也不是没有可能,只是这样做的话,回滚和崩溃恢复是代价很大,事实上,回滚和崩溃恢复并不经常发生。

    再想想怎么去求证

  7. slevin

    我一直在想读提交为什么会产生幻读和重复读,google百度也搜不到什么好的答案,而在innodb的REPEATABLE-READ级别,由于在MVCC和锁的控制,使它不会产生幻读,由于REPEATABLE-READ级别每次去读取数据都要去比较版本,所以会有CPU的开销,而uncommit read级别,我估计它只是在事务的开始去比较版本,在事务发生的过程中确不会,降低一点开销,所以会产生幻读和重复读,呵呵!~我也是猜想而已!~要等高手研究源码才知道

  8. slevin

    刚刚测试了把!~上面那段话说错了,应该是不锁记录的情况下是看不到幻读的.

  9. slevin

    MVCC只支持读提交和重复读提交级别,MVCC针对select 在高性能mysql是这样定义的
    innodb对比当前事务的版本号和找到行的版本号/删除号,当满足如下条件时,行被select出来:

    1、 行版本号不大于事务版本号。这确保了该行在事务开始时已存在,或者由当前事务创建、更新;

    2、 行删除号不存在,或者删除号大于事务版本号。这确保事务开始前行未被删除;

    在事务发生过程中,别的事务插入了一条数据,并且提交,在MVCC机制里,select会不会读取行版本大于事务版本的,而在读提交级别,select却可以读到大于事务版本的行,是不是MVCC的机制在读提交的定义不同?

  10. @slevin 这里你可能混淆了MVCC的概念,MVCC指的是数据库(DBMS)实现多版本控制的一种机制。InnoDB有自己的MVCC机制(上面你也提到了)。在MVCC这种机制下,InnoDB实现了自己的事务隔离级别:read uncommit | read commit | reapeatable read | serializable。你上面提到是的在MVCC机制下reapeatable read的实现,当然也就和commit read有所不同。

  11. slevin

    那MVCC在commit read实现是怎样的呢?为什么在读提交级别,select可以读到大于该事务版本号的行。

  12. 因为InnoDB的每条记录上都有事务ID,所以当前事务始终可以根据事务ID来判断,这个记录是在自己之前或者之后开始的。

  13. slevin

    你的意思是说read commit级别可以读到大于自己事务ID的记录,而reapeatable read不可以咯!不过这也感觉到矛盾啦!为什么要让read commit级别读到大于自己事务ID的记录呢,符合SQL事务级别标准吗,还是可以增加并发和性能?

  14. […] 涉及到的概念:Buffer Pool简称BP,Dirty Page,Log file,Flush,innodb tablespace。 […]

  15. […] 事实上,并不是每次关闭InnoDB都很慢的。Why?InnoDB较之MyISAM,一个重要特性是InnoDB会在内存中开辟一个Buffer Pool来存储最近访问的数据块/索引块,使得下次再次访问这个块时速度能够很快。当InnoDB对需要修改数据块的时候,会先记录修改日志,然后直接对Buffer_Pool中的数据块的操作。记录日志是顺序写,对数据块的操作是内存操作,这让InnoDB在很多场景下有这很好的速度优势。 […]

  16. Anonymous

    当update mark成dirty后,这个dirty页面的值是update以后的值还是update之前的值呢?如果是之前的值,那当要再次读取数据的时候,data还在log file里面没有flush到data file里面,那select到哪去读取数据?如果是update之后的值,那么是不是commit后这个page应该是clean的而不是dirty呢?如果是clean的,又在什么情况下,dirty page的比例会达到max呢? 因为如果是那样,所有的dirtypage都会直接变成clean after commit.

  17. 回复楼上:情况比想象的更简单,在InnoDB中update,其实是把原来的记录mark成delete,然后insert一条新纪录,并记录相应的Transaction ID,所以Dirty页面里面值有两条记录; SELECT的时候,会根据当前的Transaction ID和记录上标记的ID对比,确定是否读对应的记录。参考InnoDB MVCC

  18. Anonymous

    谢谢楼主的解释 我现在才弄明白BP中的flush到data file是根logfile没有关系的,logfile只是起到一个backup,recovery和什么时候flush dirty page的作用

  19. […] 涉及到的概念:Buffer Pool简称BP,Dirty Page,Log file,Flush,innodb tablespace。 […]

  20. 王洪泽

    想问一下,如果脏页被flush到磁盘后; 那其中的被mark成delete的记录应该不存在了。那这个脏页是否会被重置为clean? 不会被交还给os? 只能通过内部的lru移除该页?

  21. 1. 被mark成delete的记录,并不应该立刻删除的,(至少需要等待该事务之前所有的事务都完成)
    2. 被mark成delete的记录,InnoDB有一个专门的线程做后续的清除操作
    3. “脏页”在重新flush到disk了,就认为是clean的了。OS不会处理,也不会被移除LRU。“脏页”并不表示该也无效了,只是表示该内存页与磁盘上数据页有不一致;当恢复一致后,该也又是clean了。

  22. 王洪泽

    谢谢,我觉得应该也是这么处理的,不过没时间看mysql的代码验证;现在也看不太懂 🙂

  23. Anonymous

    想问下innodb_flush_log_at_trx_commit =1,这个参数,如果等于1了,是立即把log_buffer刷新到log_file里,然后再写入磁盘里,还是只刷新log_file,数据要等到脏页的控制参数innodb_max_dirty_pages_pct超过这个值后,再写入磁盘?

  24. @楼上,只刷日志的。数据的刷新是另外的线程做,不是同步的。脏页的刷新有很多的触发条件,innodb_max_dirty_pages_pct就是其中的一个触发条件。

  25. liu,guosshun

    认为你在5.1) 这里参数引用错误了:
    Innodb_log_waits
    The number of times that the log buffer was too small and a wait was required for it to be flushed before continuing.

    我也想知道等待redolog 的次数,但是这个参数是等待redolog buffer 的.

Leave a Reply

Your email address will not be published. Required fields are marked *