在任何一种数据库中,都会有各种各样的日志,记录着数据库工作的过程,可以帮助数据库管理员追踪数据库曾经发生过的各种事件
MySQL日志主要包括六种:
- 重做日志(redo log)
- 回滚日志(undo log)
- 归档日志(binlog)(二进制日志)
- 错误日志(errorlog)
- 慢查询日志(slow query log)
- 一般查询日志(general log)
- 中继日志(relay log)
本文我们重点介绍前3种日志。redo log保证了事务的持久性,undo log可以实现事务回滚,保证了事务的原子性和隔离性,bin log保证服务器可以基于时间点回复数据,还用于主从复制
1.redo log是什么?为什么需要redo log?
我们知道,InnoDB存储引擎是以页为单位来进行增删改查这些操作的。在访问页面之前,需要到磁盘中把页加载到内存中的Buffer Pool中,但是内存中的数据在宕机或者系统突然崩溃的时候是会丢失的,事务又需要保证持久性,redo log就是用来保证持久性的。
- 作用:Buffer Pool用于存储数据库中的数据页,包括表数据和索引数据。当查询需要访问某个数据页时,MySQL首先会在Buffer Pool中查找,如果数据页已经在内存中,则直接返回数据,避免了频繁的磁盘读写操作。
- LRU算法:Buffer Pool采用LRU(Least Recently Used)算法来管理内存中的数据页。当内存空间不足时,MySQL会根据LRU算法淘汰最近最少使用的数据页,以腾出空间给新的数据页。
2.为什么不把事务修改过的数据页立即刷新到磁盘?
- 刷新一个完整的数据页太浪费了。有时我们仅仅修改了某个页面的一个字节,但是InnoDB是以页为单位来进行磁盘I/O的,一页的默认大小是16KB,这样太浪费了。
- 随机I/O刷新起来比较慢。事务修改的页面可能并不相邻,这意为着将这些页面刷新到磁盘时,需要进行很多随机I/O。随机I/O比顺序IO慢,尤其是对比传统IO。
- redo log是把修改过的内容记录下来。比如sql语句是
update user set age = 20 where id = 1,那么在日志里面写入的可能是将第0号表空间的第100号页面中偏移量为1000处的值更新为2
事务在提交时把上述内容刷新到磁盘就可以了。redo log占用的空间不是很大,而且使用顺序IO
3.redo log的写入过程是怎样的?
InnoDB为了解决磁盘速度过慢的问题而引入了Buffer Pool。同理,写入redo日志时也不能直接写到磁盘中,在数据库服务器内部有一片连续的内存空间,称为redo log buffer(redo 日志缓冲区),简称为log buffer,这片空间被划分为若干连续的redo log block,日志缓冲区是顺序写入的,先写前面的block,写满后继续写下一个。log buffer 中有一个指针 buf_free,来标识该位置之前都是填满的 block,该位置之后都是空闲区域。
InnoDB 引擎会在适当的时候,把内存中 redo 日志缓冲区 持久化到磁盘,具体的刷盘策略:
- 在事务提交时需要进行刷盘,通过修改参数
innodb_flush_log_at_trx_commit设置:- 0:表示当提交事务时,并不将缓冲区的 redo 日志写入磁盘,而是等待后台线程每秒刷新一次
- 1:在事务提交时将缓冲区的 redo 日志同步写入到磁盘,保证一定会写入成功(默认值)
- 2:在事务提交时将缓冲区的 redo 日志异步写入到磁盘,不能保证提交时肯定会写入,只是有这个动作。日志已经在操作系统的缓存,如果操作系统没有宕机而 MySQL 宕机,也是可以恢复数据的
- 写入 redo log buffer 的日志超过了总容量的一半,就会将日志刷入到磁盘文件,这会影响执行效率,所以开发中应避免大事务
- 服务器关闭时
- 并行的事务提交(组提交)时,会将将其他事务的 redo log 持久化到磁盘。假设事务 A 已经写入 redo log buffer 中,这时另外一个线程的事务 B 提交,如果 innodb_flush_log_at_trx_commit 设置的是 1,那么事务 B 要把 redo log buffer 里的日志全部持久化到磁盘,因为多个事务共用一个 redo log buffer,所以一次 fsync 可以刷盘多个事务的 redo log,提升了并发量。
服务器启动后 redo 日志文件的磁盘空间不变,所以磁盘中的日志文件是被循环使用的,采用循环写数据的方式,写完尾部重新写头部,并且也是顺序IO,比随机IO快。
4.讲解undo log
undo log 用于保证事务原子性和隔离性,当事务对数据库进行修改时,InnoDB 会先记录对应的 undo log,如果事务执行失败或调用了 rollback 导致事务回滚,InnoDB 会根据 undo log 的内容做与之前相反的操作:
对于每个 insert,回滚时会执行 delete
对于每个 delete,回滚时会执行 insert
对于每个 update,回滚时会执行一个相反的 update,把数据修改回去
在对一个数据行修改前,会把这个数据行的隐藏列trx_id和roll_pointer的旧值写到undo log对应的属性中,这样当前记录的roll_pointer会指向当前undo_log的记录,当前undo_log的记录的roll_pointer会指向旧的 undo log 记录,形成一个版本链。
5.讲解bin log
binlog(二进制日志)也可以记录写操作并用于数据的恢复,保证数据不丢失。
MySQL的Binlog有三种录入格式,分别是Statement格式、Row格式和Mixed格式。它们的主要区别如下:
- Statement格式:将SQL语句本身记录到Binlog中。记录的是在主库上执行的SQL语句,从库通过解析并执行相同的SQL来达到复制的目的。简单、易读,节省存储空间。但是,在某些情况下,由于执行计划或函数等因素的影响,相同的SQL语句在主从库上执行结果可能不一致,导致复制错误。(SQL语句以二进制编码的形式写入binlog)
- Row格式:记录被修改的每一行数据的变化。不记录具体的SQL语句,而是记录每行数据的变动情况,如插入、删除、更新操作前后的值。保证了复制的准确性,不受SQL语句执行结果的差异影响,适用于任何情况。但是,相比Statement格式,Row格式会占用更多的存储空间。
- Mixed格式:Statement格式和Row格式的结合,MySQL自动选择适合的格式,大多数情况下使用Statement格式进行记录,但对于无法保证安全复制的情况,如使用非确定性函数、存储过程等,会自动切换到Row格式进行记录。结合了两种格式的优势,既减少了存储空间的占用,又保证了复制的准确性。
小结:
- Statement格式适用于简单的SQL语句,对存储空间要求较高
- Row格式适用于需要精确复制的场景
- Mixed格式是综合考虑两种格式的优势而出现的折中方案。
6.redolog和binlog对比
- 作用不同:redo log 是用于 crash recovery (故障恢复),保证 MySQL 宕机也不会影响持久性;binlog 是用于 point-in-time recovery 的,保证服务器可以基于时间点恢复数据,此外 binlog 还用于主从复制
- 层次不同:redo log 是 InnoDB 存储引擎实现的,而 binlog 是MySQL的 Server 层实现的,同时支持 InnoDB 和其他存储引擎
- 内容不同:redo log 是物理日志,内容基于磁盘的 Page;binlog 的内容是二进制的,根据 binlog_format 参数的不同,可能基于SQL 语句、基于数据本身或者二者的混合(日志部分详解)
- 写入时机不同:binlog 在事务提交时一次写入;redo log 的写入时机相对多元
binlog 为什么不支持崩溃恢复?
- binlog 记录的是语句,并不记录数据页级的数据(哪个页改了哪些地方),所以没有能力恢复数据页
- binlog 是追加写,保存全量的日志,没有标志确定从哪个点开始的数据是已经刷盘了,而 redo log 只要在 checkpoint_lsn 后面的就是没有刷盘的。