日志

1、undo log(回滚日志):是 Innodb 存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和 MVCC

2、redo log(重做日志):是 Innodb 存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复

3、bin log (归档日志):是 Server 层生成的日志,主要用于数据备份和主从复制

Buffer Pool

MySQL启动时向操作系统申请的一片连续内存空间,默认128MB。

按默认的16KB大小划分出一个个页, Buffer Pool中的页就叫做缓存页,包括:索引页、数据页、Undo页(undo log就记录在这里面)、插入缓存页、自适应哈希索引、锁信息

undo log作用

1、实现事务回滚,保障事务的原子性

2、MVCC通过ReadView + undo log实现。undo log为每条记录保存多份历史数据,MySQL在执行快照读(普通 select 语句)时,会根据事务的 Read View 信息,顺着 undo log 版本链找到满足其可见性的记录

Buffer Pool作用

1、读数据时,如果数据存在Buffer Pool中,客户端就会直接读 Buffer Pool中的数据,否则再去磁盘取。

2、改数据时,如果数据存在Buffer Pool中就直接修改Buffer Pool中数据所在页,然后将页设为脏页(该页的内存数据和磁盘上的数据已经不一致),为减少磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选一个合适时机将脏页写入磁盘。

BufferPool中脏页何时刷盘

  • redo log满了的情况下
  • Buffer Pool空间不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;
  • MySQL认为空闲时,后台线程会定期将适量的脏页刷入到磁盘;
  • MySQL正常关闭前,会把所有脏页刷入到磁盘;

管理pool中缓存页:为每个页都创一个控制块放在pool前面,包括:缓存页的表空间、页号、缓存页地址、链表节点

管理空闲页:空闲链表

管理脏页:flush链表,链表的节点也是控制块,区别在于链表元素都是脏页。

解决预读失效导致缓存命中率降低:让预读的页留在 Buffer Pool 里的时间要尽可能短,让真正被访问的页才移动到 LRU 链表头,从而保证真正被读取的热数据留在 Buffer Pool里的时间尽可能长。MySQL改进LRU算法,将LRU划分成old区和young区,预读的页就只需加到old区域的头部,当页被真正访问的时候,才将页插入 young 区域的头部。如果预读页一直没被访问就会从old区移除,这样就不会影响young区域中的热点数据。

解决出现BufferPool污染而导致缓存命中率下降

BufferPool污染:某SQL扫描了大量的数据,在Buffer Pool空间比较有限的情况下可能会将Buffer Pool里的所有页都替换出去,导致大量热数据被淘汰。等这些热数据又被再次访问时由于缓存未命中就会产生大量磁盘IO。

解决:同时满足「被访问」与「在 old 区域停留时间超过 1 秒」两个条件,才会被插入到 young区头部

redo log作用

1、undo log放在内存中掉电易失,所以需保存物理日志redo log,实现事务持久性

2、WAL将写操作从随机写变成顺序写,提升MySQL写入磁盘的性能

WAL:MySQL写操作并不立刻写到磁盘上,而是先写日志(顺序追加),然后在合适时间再写到磁盘上

redo log buffer何时刷盘

  • MySQL正常关闭
  • 当 redo log buffer 中记录的写入量大于buffer内存空间一半时
  • InnoDB的后台线程每隔 1 秒,将redo log buffer持久化到磁盘
  • 每次事务提交时(由innodb_flush_log_at_trx_commit控制)

innodb_flush_log_at_trx_commit控制事务提交刷盘策略

  • 0 :每次事务提交时还是将 redo log 留在 redo log buffer 中(事务提交时不会主动触发写入磁盘操作),后续刷盘通过调 write() 写到操作系统Page Cache,然后调用 fsync() 持久化到磁盘。MySQL进程的崩溃会导致上一秒所有事务数据丢失
  • 1(默认):每次事务提交时将redo log直接持久化到磁盘
  • 2:每次事务提交时都只把redo log写到redo log文件,写入到操作系统的文件缓存而不意味着写入到磁盘(操作系统文件系统中有个Page Cache专门用来缓存文件数据),后续通过调 fsync()持久化到磁盘。较0情况下更安全,MySQL进程崩溃不会丢失数据,只有在操作系统崩溃或系统断电情况下上一秒所有事务数据才可能丢失

redo log文件写满了怎么办

InnoDB有一个重做日志文件组,由两个redo log组成(ib_logfile0和ib_logfile1),大小一致,以循环写方式工作,相当于一个环形。每次脏页刷到磁盘后redo log记录就没用了就会被擦除腾出空间(write pos表示写位置,checkpoint表示要擦除位置)。write pos追上checkpoint意味着文件满了,MySQL会阻塞等待checkpoint往后移动,才会恢复正常。

undo log和redo log区别

  • undo log记录了此次事务「开始前」的数据状态,记录的是更新前的值
  • redo log记录了此次事务「完成后」的数据状态,记录的是更新后的值

事务提交前发生崩溃,重启后会通过 undo log 回滚事务;事务提交后发生崩溃,重启后会通过 redo log 恢复事务

binlog作用

用于主从复制(异步)。记录了所有数据库表结构变更和表数据修改的日志,不会记录查询类操作(SELECT、HOW)

binlog三种格式

  • STATEMENT(默认):记录每条修改数据的SQL(相当于记录了逻辑操作),主从复制中slave端再根据 SQL 语句重现。但有动态函数问题,比如用了uuid或now这些函数,在主库上执行结果并不是在从库执行的结果,会导致复制数据不一致
  • ROW:记录行数据最终被改成什么样了,不会出现STATEMENT下动态函数的问题。但缺点是每行数据的变化结果都会被记录,比如执行批量 update 语句,更新多少行数据就会产生多少条记录使binlog文件过大,而STATEMENT格式下只会记一个update语句
  • MIXED:包含STATEMENT和ROW模式,会根据不同情况自动使用ROW和 STATEMENT

binlog何时刷盘

每个线程有自己 binlog cache,但最终都写到同一个 binlog 文件。MySQL提供sync_binlog参数来控制binlog刷到磁盘上的频率:

  • 0(默认):每次提交事务都只 write不fsync,后续交由操作系统决定何时将数据持久化到磁盘
  • 1 :每次提交事务都write然后马上执行fsync
  • N(N>1) :每次提交事务都write但累积N个事务后才fsync

为什么有binlog还要有redo log

开始MySQL里没有InnoDB引擎,自带引擎是MyISAM,但它没有崩溃恢复的能力,binlog日志只能用于归档。后来InnoDB引入MySQL使用redo log实现崩溃恢复的能力

redo log和binlog区别

1、用途不同:binlog用于备份恢复、主从复制;redo log用于掉电等故障恢复

2、适用对象不同:binlog是MySQL Server层实现的日志,所有存储引擎都可使用;而redo log是Innodb存储引擎实现的日志;

3、写入方式不同:binlog是追加写,写满一个文件就创建一个新文件继续写,不会覆盖以前的日志,保存全量日志;redo log是循环写,日志空间大小固定,保存未被刷入磁盘的脏页日志

4、文件格式不同:binlog有3种格式(STATEMENT、ROW、 MIXED),而redo log是物理日志,记录的是在某个数据页做了什么修改

两阶段提交的问题

MySQL为避免出现binlog和redo log两份日志间逻辑不一致问题,使用「两阶段提交」来解决(分布式事务一致性协议,可保证多个逻辑操作不会出现半成功状态),会导致磁盘 I/O 次数高、锁竞争激烈

组提交

引入binlog组提交机制,多个事务提交时会将多个binlog刷盘操作合成一个,从而减少磁盘I/O次数。prepare阶段不变,只针对commit阶段拆为三个过程:

  • flush阶段:多个事务按进入顺序将binlog从cache写入文件(不刷盘)
  • sync 阶段:对binlog文件做fsync操作(多个事务的binlog合一次刷)
  • commit 阶段:各个事务按顺序做InnoDB commit操作

每个阶段都有一个队列且有锁保护(锁粒度减小),第一个进入队列的事务会成为leader,完成后leader通知队内其他事务操作结束