MySQL实现事务、崩溃恢复、集群的主从复制,底层都离不开日志,所以日志是MySQL的精华所在。只有了解MySQL日志,才算是彻底搞懂MySQL。
日志是mysql数据库的重要组成部分,记录着数据库运行期间各种状态信息。mysql日志主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。
我们重点需要关注的是MySQL的三大日志系统:Redo Log(重做日志)、Undo Log(恢复日志)、Bin Log(二进制日志文件)。
事务的四大特性里面有一个是持久性,具体来说就是只要事务提交成功,那么对数据库做的修改就被永久保存下来了,不可能因为任何原因再回到原来的状态。那么mysql是如何保证一致性的呢?最简单的做法是在每次事务提交的时候,将该事务涉及修改的数据页全部刷新到磁盘中。但是这么做会有严重的性能问题,主要体现在两个方面:
因为Innodb是以页为单位进行磁盘交互的,而一个事务很可能只修改一个数据页里面的几个字节,这个时候将完整的数据页刷到磁盘的话,太浪费资源了
一个事务可能涉及修改多个数据页,并且这些数据页在物理上并不连续,使用随机IO写入性能太差
因此mysql设计了redo log,具体来说就是只记录事务对数据页做了哪些修改,这样就能完美地解决性能问题了(相对而言文件更小并且是顺序IO)。
作用概述:Redo Log就是用来保证服务崩溃后,仍能把事务中变更的数据持久化到磁盘上。
MySQL事务中持久性就是使用Redo Log实现的。
你可能会问,为什么需要写Redo Log Buffer和Redo Log FIle?直接持久化到磁盘不好吗?
直接写磁盘会有产生严重的性能问题:
- InnoDB在磁盘中存储的基本单元是页,可能本次修改只变更一页中几个字节,但是需要刷新整页的数据,就很浪费资源。
- 一个事务可能修改了多页中的数据,页之间又是不连续的,就会产生随机IO,性能更差。
这种方案叫做WAL(Write-Ahead Logging),预写日志,就是先写日志,再写磁盘。
redo log包含了两个层面:
redolog的整体流程:
还没提交就在写log。redolog的优先级跟高。
注意:redo log buffer刷盘到redo logfile的过程并不是真正的刷盘,只是刷入到文件缓存中(这个是操作系统提高文件写入效率的优化)。
但是这种就会出现交给系统,刷盘不及时,宕机造成数据丢失。
写入Redo Log Buffer之后,并不会立即持久化到Redo Log FIle,需要等待操作系统调用fsync()操作,才会刷到磁盘上。
具体什么时候可以把Redo Log Buffer刷到Redo Log FIle中,可以通过innodb_flush_log_at_trx_commit参数配置决定。
参数值 | 含义 |
---|---|
0(延迟写) | 提交事务后,不会立即刷到OS Buffer中,而是等一秒后刷新到OS Buffer并调用fsync()写入Redo Log FIle,可能会丢失一秒钟的数据。 |
1(实时写 | 每次提交事务,都会刷新到OS Buffer并调用fsync()写到Redo Log FIle,性能较差 |
2(延迟刷新) | 每次提交事务只刷新到OS Buffer,一秒后再调用fsync()写入Redo Log FIle。 |
InnoDB 的Redo Log File是固定大小的。可以配置为每组4个文件,每个文件的大小是 1GB,那么Redo Log File可以记录4GB的操作。
采用循环写入覆盖的方式,write pos记录开始写的位置,向后移动。checkpoint记录将要擦除的位置,也是向后移动。write pos到checkpoint之间的位置,是可写区域,checkpoint到write pos之间的位置是已写区域。
由bin log和redo log的区别可知:
为了保证事务的原子性(既事务中的操作要么全部做完,要么都不做)。如果事务执行中突发异常,如数据库出错、操作系统宕机等,亦或者程序员要在事务执行过程中结束当前事务的执行,如何保证事务的原子性呢?
这就需要在对一条记录做变更(增删改,不包括查)时,都要把能回滚的内容记录下来以备不时之需,回滚的时候只需要对数据库进行一个相反的操作即可。
DELETE
这个主键的内容;undo log
记录了每个操作的逆操作,可以逻辑恢复数据(注意:类似git 操作,不是物理上的恢复,既数据结构和页可能变化了);MVCC
:在InnoDB
中MVCC
的实现是通过undo log
来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo log
读取之前的行版本信息,以此实现非锁定读取。在InnoDB中,undo log分为两种:
insert
undo log:是指在insert
操作中产生的undo log。因为insert
操作的记录,只对当前事务本身可见,对其他事务不可见(这是事务隔离性的要求),因此这种undo log
可以在事务提交后直接删除。不需要进行purge
操作。undate
undo log:是对delete
和update
操作产生的undo log。该undo log可能需要提供MVCC
机制使用,因此不能在事务提交时就进行删除,提交时放入undo log链表
,等待purge
线程进行最后的删除。MySQL处于性能考虑,数据会优先从磁盘加载到Buffer Pool
中,在更新Buffer Pool
中数据之前,会优先将数据记录到undo log
中。
记录undo log
日志,MySQL不会直接去往磁盘中的xx.ibdata
文件写数据,而是会写在undo_log_buffer
缓冲区中,因为工作线程直接去写磁盘太影响效率了,写进缓冲区后会由后台线程去刷写磁盘。
现在我们已经明白了undo log
日志是如何生成,并且作用于事务回滚的,那这些数据是什么时候删除呢?
insert undo log
,因为insert
操作的记录,只对事务本身可见,对其他事务不可见。故该undo log
在事务提交后就没有用,就会直接删除。update undo log
,该undo log
需要支持MVCC
机制,因此不能在事务提交时就进行删除。提交时放入undo log
链表,有专门的purge
线程进行删除。即binary log
,二进制日志文件,也叫作变更日志(update log
),是MySQL
中比较重要的日志,和运维息息相关。它记录了所有更新数据库的语句(如DDL
和 DML
语句)并以二进制的形式保存在磁盘中,但是不包含没有修改任何数据的语句(如数据查询语句select、show等)。
bin log
是逻辑日志,记录的是执行语句的逻辑,和redis
的AOP
日志类似,会按顺序记录所有涉及更新数据的逻辑操作。
MySQL
可以通过bin log
恢复某一时刻的误操作的数据,是DBA
常打交道的日志。MySQL
的数据备份、集群高可用、读写分离都是基于bin log
的重放实现的。binlog日志有三种格式:statement
、row
和mixed
,对比如下:
格式 | 含义 | 优点 | 缺点 |
---|---|---|---|
statement | 每一条会修改数据的sql都会记录在binlog中,基于SQL语句的复制,记录的是更新数据操作的SQL语句,这些语句同步时会被其他节点执行,如update T set time=NOW() where id = 1; | 不需要记录每一行的变化,减少了binlog日志量,节约了IO, 提高了性能。 | 由于记录的只是执行语句,为了这些语句能在slave上正确运行,因此还必须记录每条语句在执行的时候的一些相关信息,以保证所有语句能在slave得到和在master端执行的时候相同的结果。另外mysql的复制,像一些特定函数的功能,slave与master要保持一致会有很多相关问题。 |
row | 5.1.5版本的MySQL才开始支持 row level 的复制,它不记录sql语句上下文相关信息,仅保存哪条记录被修改。 | binlog中可以不记录执行的sql语句的上下文相关的信息,仅需要记录那一条记录被修改成什么了。所以row的日志内容会非常清楚的记录下每一行数据修改的细节。而且不会出现某些特定情况下的存储过程,或function,以及trigger的调用和触发无法被正确复制的问题。 | 每条数据的更改被详细记录,如整表删除,alter表等操作涉及的数据行都会记录,ROW格式会产生大量日志。 |
mixed | 混合模式,5.1.8版本开始,以上两种格式的混合版,对于DDL只对SQL语句进行记录,对DML操作则会进行判断,如果判断会造成主从不一致,就会采用row格式记录,反之则用statement格式记录。 | 既节省空间,又提高数据库性能,保证数据同步时的一致性。 | 无法对误操作数据进行单独恢复。 |
注:将二进制日志格式设置为ROW时,有些更改仍然使用基于语句的格式,包括所有DDL语句,例如CREATE TABLE, ALTER TABLE,或 DROP TABLE。
日志由一组二进制日志文件(Binlog),加上一个索引文件(index);Binlog是一个二进制文件集合,每个Binlog以一个4字节的魔数开头,接着是一组Events。