type
status
date
slug
summary
tags
category
icon
password
😀
这里写文章的前言: 一个简单的开头,简述这篇文章讨论的问题、目标、人物、背景是什么?并简述你给出的答案。
可以说说你的故事:阻碍、努力、结果成果,意外与转折。
 

📝 MySql日志

分为 undo log(回滚日志), redo log(重做日志),binlog(归档日志)
  • undo log(回滚日志):innodb存储引擎层生成的日志,实现了事务中的原子性,主要用于事务回滚和MVCC
  • redo log(重做日志):innodb存储引擎层生成的日志,实现了事务中的持久性,主要用于掉电等故障恢复
  • binlog(归档日志):是Server层生成的日志,主要用于数据备份和主从复制

undo log

每次执行事务的过程中,都记录下回滚时需要的信息到一个日志里,那么在事务执行中发生了MYSQL崩溃后,就不用担心无法回滚到事务之前的数据,我们可以通过这个日志回滚到事务之前的数据 undo log 这个机制,它保证了事务的ACID中的原子性 每当innodb引擎对一条记录进行操作(增加,修改,删除)时,需要把回滚时需要的信息都记录到undo log里:
  • 在插入一条记录时,把这条记录的主键值记录下来,这样回滚时,只需要把这个主键值对应的记录删掉即可
  • 在删除一条记录时,把这条记录的内容都记下来,这样回滚时,再把这些内容组成记录插入到表中
  • 更新一条记录时,要把被更新的列的旧值记下来,这样回滚时,再把这些列更新为旧值即可
在回滚时,就读取undo log里的数据,然后做原先相反操作。比如当delete一条记录时,undo log中会把记录中的内容都记下来,然后执行回滚操作的时候,就读取undo log里的数据,然后进行insert操作 undo log 还有一个作用,通过 ReadView + undo log 实现 MVCC(多版本并发控制) 对于 读提交 与 可重复读 隔离级别的事务来说,它们的快照读(普通select语句)是通过ReadView + undo log来实现的,区别在于
  • 读提交:每个select都会生成一个新的ReadView,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务
  • 可重复读:事务启动的时候,生成一个ReadView,整个事务期间都在用这个Read View,这样就保证了在事务期间读取到的数据都是事务启动前的记录
这两个隔离级别实现是通过 事务的 ReadView 里的字段,记录中的两个隐藏列 (trx_id 和 roll_pointer)的对比,如果不满足可见性,就会顺着 undo log 版本链里找到满足其可见性的记录,从而控制并发事务访问同一条记录的行为,这就叫 MVCC(多版本并发控制)
notion image
 
 

redo log

Buffer Pool 提高了独写效率,但是Buffer Pool是基于内存的,而内存总是不可靠的,万一断电重启,还没来得及落盘的脏页数据就会丢失 为了防止断电导致数据丢失的问题,当有一条记录需要更新的时候,innodb引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的形式记录下来,这时候更新算是完成 InnoDB会在适当的时候,由后台线程将缓存在Buffer Pool的脏页刷新到磁盘里,这就是WAL(Write-Ahead Logging)技术 WAL:MYSQL的写操作并不是立马写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。 WAL优点:MYSQL的写操作从磁盘的随机写变成了顺序写,提升语句的执行性能。 redo log是物理日志,记录了某个数据页做了什么修改,比如对XXX表空间中的YYY数据页ZZZ偏移量的地方做了AAA更新,每当执行一个事务就会产生这样的一条或者多条物理日志 在提交事务的时候,只要先将 redo log持久化到磁盘,可以不需要等待将缓存在 Buffer Pool 里的脏页数据持久化到磁盘。当系统奔溃的时候,虽然脏页数据没有持久化,但是redo log持久化了,接着MYSQL重启,可以根据 redo log的内容,将所有的数据恢复到最新状态 写入 redo log的方式是追加写,所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写入磁盘,所以磁盘是随机写。 磁盘的顺序写 比 随机写 要高效很多,因此 redo log 写入磁盘的开销更小
notion image
 

redo log buffer

执行一个事务的过程中,产生的redo log也不是直接写入磁盘的,因为这样会产生大量的IO操作,而且磁盘的运行速度远慢于内存。所以 redo log 也有自己的缓存, redo log buffer,每产生一条 redo log时,会先写入到 redo log buffer redo log buffer 默认大小 16 MB,可以通过 innodb_log_buffer_size 参数动态调整大小,增大它的大小可以让 MYSQL处理 大事务 是不必写入磁盘,进而提升写 IO 性能

redo log刷盘机制

缓存在 redo log buffer 里的 redo log 还是在内存中,它是什么时候刷盘的?
  • MYSQL正常关闭时
  • 当 redo log buffer 中记录的写入量大于 redo log buffer 内存空间的一半时,会触发落盘
  • InnoDB的后台线程每隔1秒,将redo log buffer 持久化到磁盘
  • 每次事务提交的时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘(这个策略可由innodb_flush_log_at_trx_commit 参数控制)
单独执行一个更新语句的时候,InnoDB引擎会自己启动一个事务,在执行更新语句的过程中,生成的redo log先写入到redo log buffer中,然后等事务提交的时候,再将缓存在redo log buffer中的redo log按照顺序写到磁盘 innodb_flush_log_at_trx_commit 含义:
  1. 设置为0时,表示每次事务提交,还是将 redo log 留在 redo log buffer 中,该模式下在事务提交时不会主动触发写入磁盘的顺序
  1. 设置为1时,表示每次事务提交时,都将缓存在 redo log buffer 里的redo log直接持久化到磁盘,这样可以保证MYSQL异常重启之后数据不丢失
  1. 设置为2时,表示每次事务提交时,都只缓存在 redo log buffer 里的 redo log 写到 redo log 文件,写入到 redo log 并不意味着写入到磁盘,因为操作系统的文件中有个Page Cache。Page Cache是专门用来缓存文件数据的,所以写入 redo log 文件意味着写入到操作系统的文件缓存
notion image

redo log写满

InnoDB存储引擎有1个重做日志组(redo log Group),重做日志文件组由2个redo log文件组成,这两个redo日志文件名叫: ib_logfile0 和 ib_logfile1,重做日志是以循环写的方式工作的,从头开始写,写到尾部就又回到头部,相当于一个环形,比如 InnoDB存储就会写io_logfile0,当io_logfile0被写满了,会切换到 ib_logfile1,当io_logfile1也被写满时,会切换到ib_logfile0文件
redo log是为了防止Buffer Pool中的脏页丢失而设计的,那么如果随着系统运行,Buffer Pool的脏页刷新到磁盘中,那么 redo log对应的记录也就没用了,这时候我们擦除这些旧记录,腾出空间记录新的更新操作 redo log是循环写的方式,相当于一个环形,InnoDB用write pos表示redo log当前记录写到的位置,用checkpoint表示当前要擦除的位置
  • write pos 和 checkpoint 的移动都是顺时针方向
  • write pos ~ checkpoint 之间的部分,红色部分,用来记录新的更新记录
  • checkpoint ~ write pos 之间的部分,蓝色部分,待落盘的脏数据页记录
当 write pos 追上了 checkpoint , 意味着 redo log 文件满了,这时mysql不能再执行新的更新操作,也就是说mysql会被阻塞,此时会停下来将 Buffer Pool中脏页的数据刷新到磁盘中,然后redo log那些记录可以被擦除,接着对旧的redo log进行擦除,等擦除旧记录腾出了空间,checkpoint就会往后移动,然后mysql恢复正常运行,继续执行新的更新操作
notion image
 

undo log 和 redo log区别

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

binlog

  • statement模式: 每一条会修改数据的sql都会记录在binlog中. 不需要记录每一行的变化,减少 binlog 日志量,节省IO,提高性能. 由于sql的执行是有上下文的,因此在保存的时候需要保存相关信息,同时还有一些使用了函数之类的使用的语句无法被复制和记录
  • row级别: 不记录sql语句上下文信息,仅保存那条记录被修改.记录单元为每一行的改动,基本是可以全部记录下来的但是由于很多操作,会导致大量的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大
  • mixed: 这种方案,普通操作使用的 statement记录,当无法使用 stetement的时候,使用row

主从复制原理

MySql主从复制依赖binlog,也就是记录mysql上所有变化并以二进制形式保存在磁盘上。复制过程就是将binlog中的数据从主库传输到从库上 这个过程是异步的,也就是从库上执行事务操作的线程不会等待binlog的线程完成同步 这个过程一般是异步的,也是就主库上执行事务操作的线程不会等待复制binlog的线程完成同步
  • 写入binlog: 主库写binlog日志,提交事务,并更新本地存储数据
  • 同步binlog: 把binlog复制到所有的从库上,每个从库把binlog写到暂存日志中
  • 回放binlog: 回放binlog,并更新存储引擎中的数据
notion image
 

执行流程

  • mysql主库在接受客户端提交事务的请求之后,会先写入binlog,再提交事务,更新存储引擎中的数据,事务提交完成后,返回客户端 "操作成功" 的响应
  • 从库会创建一个专门的IO线程,连接从库的log dump线程,来接受主库的binlog日志,再把binlog信息写入relay log的中继日志里,再返回给主库"复制成功"的响应
  • 从库会创建一个用于回放binlog的线程,去读relay log中继日志,然后放回binlog更新存储引擎中的数据,最终实现主从的数据一致性
notion image

🤗 总结归纳

📎 参考文章

 
💡
有关文章的问题,欢迎您在底部评论区留言,一起交流~