前言:
其实自己已经看过很多关于InnoDB的文章,关于InnoDB为什么可以实现crash safe,以及底层如何更新,redo log ,undo log这一大堆的东西,看完之后很混乱,很容易迷糊,所以总结这一篇! 特别有必要,希望总结完以后自己对这些知识的理解能上一层楼
这些总结的知识点,真的是心血的积累,翻了很多博客,很多文章,看了很多视频。希望可以帮到大家,帮到自己!
1.Mysql日志有哪几种?
MySQL的日志主要有:二进制日志、通用查询日志、慢查询日志、错误日志、事务日志等。
1.重做日志(redo log)
-
作用:
-
防止在发生故障的时间点,尚有脏页
-
未写入磁盘(事务提交之后,数据写入磁盘之前宕机),在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
-
内容:
-
物理格式的日志,记录的是物理数据页面的修改信息,其redo log是顺序写入redo log file的物理文件中去的。
-
生成时机:
-
事务开始之后就产生redo log,redo log的落盘并不是随着事务的提交才写入的,而是在事务的执行过程中,便开始写入redo log文件中。
-
释放时机:当对应事务的脏页写入到磁盘之后,redo log的使命也就完成了,重做日志占用的空间就可以重用(被覆盖)。
为什么要引入 redo log?redo log 可以保证持久化又可以保证数据库的性能,相比于直接刷盘,redo log 有以下两个优势:
-
redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。
-
redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。
InnoDB 引擎是怎么做的?当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log 里面,并更新内存,这个时候更新就算完成了。当数据库宕机重启的时候,会将 redo log 中的内容恢复到数据库中,再根据 undo log和 binlog 内容决定回滚数据还是提交数据。
缓冲区 Innodb_log_buffer
-
Master Thread 每秒一次执行刷新 Innodb_log_buffer 到重做日志文件
-
每个事务提交时会刷新。
-
当重做日志缓存可用空间 少于一半时,重做日志缓存被刷新到重做日志文件。
因此重做日志的写盘,并不一定是随着事务的提交才写入重做日志文件的,而是随着事务的开始,逐步开始的。
关于redo log buffer的一些问题
如上图所示,redo log分为两部分:
-
内存中的redo log Buffer是日志缓冲区,这部分数据是容易丢失的
-
磁盘上的redo log file是日志文件,这部分数据已经持久化到磁盘,不容易丢失
SQL操作数据库之前,会先记录重做日志,为了保证效率会先写到日志缓冲区中(redo log Buffer),再通过缓冲区写到磁盘文件中进行持久化,既然有缓冲区说明数据不是实时写到redo log file中的,那么假如redo log写到缓冲区后,此时服务器断电了,那redo log岂不是会丢失?
在MySQL中可以自已控制log buffer刷新到log file中的频率,通过innodb_flush_log_at_trx_commit参数可以设置事务提交时log buffer如何保存到log file中,innodb_flush_log_at_trx_commit参数有3个值(0、1、2),表示三种不同的方式
-
为1表示事务每次提交都会将log buffer写入到os buffer,并调用操作系统的fsync()方法将日志写入log file,这种方式的好处是就算MySQL崩溃也不会丢数据,redo log file保存了所有已提交事务的日志,MySQL重新启动后会通过redo log file进行恢复。但这种方式每次提交事务都会写入磁盘,IO性能较差(就算半路断电了,之前未完成的可以要求重试去完成)
-
为0表示事务提交时不会将log buffer写入到os buffer中,而是每秒写入os buffer然后调用fsync()方法将日志写入log file,这种方式在MySQL系统崩溃时会丢失大约1秒钟的数据
-
为2表示事务每次提交仅将log buffer写入到os buffer中,然后每秒调用fsync()方法将日志写入log file,这种方式在MySQL崩溃时也会丢失大约1秒钟的数据
2.回滚日志(undo log)
-
作用:
-
保存了事务发生之前数据的一个版本,可以用于回滚。事务回滚时,会根据 undo log 将数据从逻辑上恢复至事务之前的状态。
-
内容:
-
逻辑格式的日志,在执行undo 的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log的。
-
生成时机:
-
事务开始之前,将当前事务版本生成 undo log,undo 也会产生 redo 来保证 undo log 的可靠性。每一次写操作都会生成 undo log,比如 update 操作,会生成一条更新前的记录信息,比如主键ID,需要更新的字段以及字段对应的旧值。对于 DELETE 操作,在 delete mark 操作时,会获取该条记录最近一次的 undo log 记录,生成的新的 undo log 记录了旧记录的 trx_id(事务编号) 和 old roll_pointer(上一个 undo log 的位置) ,这样回滚时,就可以直接找到删除之前的 undo log 进行回滚操作。
-
释放时机:
-
当事务提交之后,undo log并不能立马被删除,而是放入待清理的链表,由 purge 线程判断是否有其他事务在使用 undo 段中表的上一个事务之前的版本信息,决定是否可以清理 undo log 的日志空间。
补充
undo log是回滚日志,用来回滚行记录到某个版本,undo log一般是逻辑日志,根据行的数据变化进行记录
undo log跟redo log一样也是在SQL操作数据之前记录的,也就是SQL操作先记录日志,再进行操作数据
如上图所示,SQL操作之前会先记录redo log、undo log到日志缓冲区,日志缓冲区的数据会记录到os buffer中,再通过调用fsync()方法将日志记录到log file中
undo log记录的是逻辑日志,可以简单的理解为:当insert一条记录时,undo log会记录一条对应的delete语句;当update一条语句时,undo log记录的是一条与之操作相反的语句
当事务需要回滚时,可以从undo log中找到相应的内容进行回滚操作,回滚后数据恢复到操作之前的状态
undo日志还有一个用途就是用来控制数据的多版本(MVCC),在《InnoDB存储引擎中的锁》一文中讲到MVCC是通过读取undo日志中数据的快照来进行多版本控制的
undo log是采用段(segment)的方式来记录的,每个undo操作在记录的时候占用一个undo log segment。
另外,undo log也会产生redo log,因为undo log也要实现持久性保护
补充:
MySQL中是如何实现事务提交和回滚的?
-
为了保证数据的持久性,数据库在执行SQL操作数据之前会先记录redo log和undo log
-
redo log是重做日志,通常是物理日志,记录的是物理数据页的修改,它用来恢复提交后的物理数据页
-
undo log是回滚日志,用来回滚行记录到某个版本,undo log一般是逻辑日志,根据行的数据变化进行记录
-
redo/undo log都是写先写到日志缓冲区,再通过缓冲区写到磁盘日志文件中进行持久化保存
-
undo日志还有一个用途就是用来控制数据的多版本(MVCC)
简单理解就是:
redo log是用来恢复数据的,用于保障已提交事务的持久性
undo log是用来回滚事务的,用于保障未提交事务的原子性
3.二进制日志(binlog)
- 作用:
-
用于复制,在主从复制中,从库利用主库上的 binlog 进行重播,实现主从同步。
-
用于数据库的基于时间点的还原。
-
内容:
-
逻辑格式的日志,可以简单认为就是执行过的事务中的sql语句。
-
生成时机:
-
事务提交的时候,一次性将事务中的sql语句(一个事物可能对应多个sql语句)按照一定的格式记录到binlog中。
-
这里与redo log很明显的差异就是redo log并不一定是在事务提交的时候才刷新到磁盘,redo log是在事务开始之后就开始逐步写入磁盘。
-
释放时机:
-
binlog的默认是保持时间由参数expire_logs_days配置,也就是说对于非活动的日志文件,在生成时间超过expire_logs_days配置的天数之后,会被自动删除。
MySQL通过两阶段提交过程来完成事务的一致性的,也即 redo log 和 binlog 的一致性的,理论上是先写 redo log,再写 binlog,两个日志都提交成功(刷入磁盘),事务才算真正的完成。
2.InnoDB的一些常见问题(日志问题)
1.关于更新语句时的操作(一个经典问题,来自于一条sql语句的查询过程那篇文章)
更新语句
以上就是一条查询 sql 的执行流程,那么接下来我们看看一条更新语句如何执行的呢?sql 语句如下:
update tb_student A set A.age='19' where A.name=' 张三 ';
我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志啦,这就会引入日志模块了,MySQL 自带的日志模块式 binlog(归档日志) ,所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 redo log(重做日志),我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
•先查询到张三这一条数据,如果有缓存,也是会用到缓存。•然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。•执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。•更新完成。
这里肯定有同学会问,为什么要用两个日志模块,用一个日志模块不行吗?
这是因为最开始 MySQL 并没与 InnoDB 引擎( InnoDB 引擎是其他公司以插件形式插入 MySQL 的) ,MySQL 自带的引擎是 MyISAM,但是我们知道 redo log 是 InnoDB 引擎特有的,其他存储引擎都没有,这就导致会没有 crash-safe 的能力(crash-safe 的能力即使数据库发生异常重启,之前提交的记录都不会丢失),binlog 日志只能用来归档。
并不是说只用一个日志模块不可以,只是 InnoDB 引擎就是通过 redo log 来支持事务的。那么,又会有同学问,我用两个日志模块,但是不要这么复杂行不行,为什么 redo log 要引入 prepare 预提交状态?这里我们用反证法来说明下为什么要这么做?
•先写 redo log 直接提交,然后写 binlog,假设写完 redo log 后,机器挂了,binlog 日志没有被写入,那么机器重启后,这台机器会通过 redo log 恢复数据,但是这个时候 bingog 并没有记录该数据,后续进行机器备份的时候,就会丢失这一条数据,同时主从同步也会丢失这一条数据。•先写 binlog,然后写 redo log,假设写完了 binlog,机器异常重启了,由于没有 redo log,本机是无法恢复这一条记录的,但是 binlog 又有记录,那么和上面同样的道理,就会产生数据不一致的情况。
如果采用 redo log 两阶段提交的方式就不一样了,写完 binglog 后,然后再提交 redo log 就会防止出现上述的问题,从而保证了数据的一致性。那么问题来了,有没有一个极端的情况呢?假设 redo log 处于预提交状态,binglog 也已经写完了,这个时候发生了异常重启会怎么样呢? 这个就要依赖于 MySQL 的处理机制了,MySQL 的处理过程如下:
•判断 redo log 是否完整,如果判断是完整的,就立即提交。•如果 redo log 只是预提交但不是 commit 状态,这个时候就会去判断 binlog 是否完整,如果完整就提交 redo log, 不完整就回滚事务。
这样就解决了数据一致性的问题。