日志序列号(LSN)和Checkpoint
redo log 的作用是记录对数据库的操作,防止数据库崩溃时,导致的数据丢失。如果对数据行的修改已经被后台线程持久化到磁盘中的数据页中,那 redo log 中记录的对数据库的操作就是多余的。如果不维护 redo log 中冗余的操作,redo log 的大小会随着时间的推移无限增加。而数据库在重启时,会重放 redo log 中记录的操作,由于 redo log 太大,重放 redo log 中的操作时,需要很长的时间。为了解决这个问题,InnoDB 引入 LSN(Log Sequence Number),Checkpoint。
在 InnoDB 中,redo log 中的每条日志都带有 LSN(Log Sequence Number)。Checkpoint 本质上是一个 LSN,由 InnoDB 进行维护,后台线程每次把脏页进行刷盘,都会对应更新 Checkpoint LSN。redo log 中的日志被 Checkpoint LSN 分成了两个部分:
- LSN < Checkpoint LSN 的日志: 表示它们对数据库的操作已经被后台线程持久化到磁盘中的数据页中,因此这部分日志是冗余的,可以被删除了。
- LSN > Checkpoint LSN 的日志: 表示它们对数据库的操作还未被后台线程持久化到磁盘中的数据页中,不可以被删除。
redo log 使用情况
redo log 日志是一个大小固定循环使用的文件。InnoDB 根据如下的几个变量来跟踪 redo log 的使用情况
current_lsn: 全局 LSN,最大未分配的 LSN,只增不减,用于分配给最新的 redo log 日志。它根据每条 redo log 日志的大小来进行递增,例如某条 redo log 日志的大小是 500 字节,那么最新的current_lsn = current_lsn + 500checkpoint_lsn: 最新一次 checkpoint 对应的 LSNcheckpoint_age:checkpoint_age = current_lsn - checkpoint_lsn
InnoDB 通过 checkpoint_age 以及 MySQL 参数 innodb_redo_log_capacity 来判断 redo log 的使用情况,redo log 可用空间: innodb_redo_log_capacity - checkpont_age
checkpoint_age越接近innodb_redo_log_capacity参数,表示 redo log 可用空间越少checkpoint_age离innodb_redo_log_capacity参数越远,表示 redo log 可用空间越多
当 redo log 可用空间不够时,需要强制进行刷盘操作,释放磁盘空间。
脏页刷盘流程
后台线程会根据:
- redo log 的空间压力
- buffer pool 中脏页的比例
- 刷盘策略(如 adaptive flushing) 等
- …
来决定什么时候刷,刷多少页。刷盘流程如下:
- 选择一个 LSN 作为 Checkpoint LSN
- 将 Buffer Pool 所有脏页中 LSN 小于 Checkpoint LSN 的脏页进行刷盘
- 成功刷盘之后,在 redo log 中记录新的 Checkpoint 值。
脏页如何维护 LSN
InnoDB 会为每一条 redo log 分配一个 LSN,对 Buffer Pool 中的数据页进行修改时,除了会将数据页标记为脏页,还会在脏页中记录 LSN,脏页中会记录两个 LSN:
- 第一次修改数据页的 LSN
- 最新一次修改数据页的 LSN
后台线程是如何选择脏页的?
先说几个关键的数据结构,都在 Buffer Pool 中:
- LRU 链表: 按最近使用程度排数据页,主要用于淘汰冷门数据页。
- flush_list 链表: 专门挂载脏页,按第一次修改数据页的 LSN 的大小进行升序排序
- free_list: 空闲页链表
InnoDB 中有专门的线程(page_cleaner/flusher thread) 负责按策略刷脏页。
每个脏页都有一个 oldest_modification: 一个 LSN 值。flush_list 是按照 oldest_modification 升序排序的双向链表。每当发现 redo log 占用的空间太多(checkpoint age 接近容量阈值)时,后台线程就会:
- 从
flush_list头部挑选oldest_modification最小的那部分脏页 - 将挑选出来的脏页批量写磁盘
- 写成功后,将脏页从
flush_list中清除,并重置oldest_modification,从而变成干净页。 - 更新
checkpoint_lsn