我们都清楚日志是mysql的一个重要组成部分,记录着数据库运行期间各种状态信息。而Mysql日志又分为错误日志、查询日志、慢查询日志、二进制日志(binlog)和事务日志(redo log、undo log)。其中在我们开发中聊的比较多的就是二进制日志(binlog)和事务日志(redo log、undo log)。其实慢查询日志也是我们开发中比较常见的日志,常用于sql优化。本文主要介绍binlog、redo log、undo log三种日志。
binlog记录了数据库表结构和表数据变更,以二进制的形式保存在磁盘中;比如update/delete/insert/truncate/create操作。
binlog 是通过追加的方式进行写入的,可以通过max_binlog_size参数设置每个binlog文件的大小,又分为逻辑日志、物理日志,任何存储引擎的 mysql 数据库都会记录binlog日志。
逻辑日志:可以简单理解为记录的就是sql语句
物理日志:mysql 数据最终是保存在数据页的,物理日志记录的就是数据页变更。
主要有两个作用:复制和恢复数据
MySQL在公司使用的时候往往都是一主多从结构的,从服务器需要与主服务器的数据保持一致,这就是通过binlog来实现的
数据库的数据被干掉了,我们可以通过binlog来对数据进行恢复因为binlog记录了数据库表的变更,所以我们可以用binlog进行复制(主从复制)和恢复数据
binlog 记录过程及刷盘时机
binlog 大致记录过程是将所有未提交(uncommitted)的二进制日志写入到 binlog buffer中,等该事务提交(committed)时,然后通过刷盘时机,控制刷入 OS Buffer,控制 fsync() 进行写入 Binlog File 日记文件磁盘的过程。对于 binlog, MYSQL 是通过参数 sync_binlog 参数来控制刷盘时机,取值范围是 0-N:
0: 不去强制要求,由系统自行判断何时写入磁盘
1: 每次事务提交(committed)的时候都要将 binlog 写入磁盘
N: 每提交 N 个事务,才会将 binlog 写入磁盘
事务有 ACID 四个属性, InnoDB 是支持事务的,它实现 ACID 的机制如下:
原子性:innodb的原子性主要是通过提供的事务机制实现,与原子性相关的特性有:Autocommit 设置。
COMMIT 和 ROLLBACK 语句(通过 Undo Log实现)。
一致性:innodb的一致性主要是指保护数据不受系统崩溃影响
隔离性:innodb的隔离性也是主要通过事务机制实现,特别是为事务提供的多种隔离级别
持久性:也就是事务提交成功,那么对数据库做的修改就被永久保存下来,不可能因为任何原因再回到原来的状态 (通过 redo
Log实现)
Redo log 是重做日志,属于 InnoDB储存引擎的日志。是物理日志,日志记录的内容是数据页的更改,这个页"做了什么改动"。
假设我们有一条sql语句:
update user set name='张三' where id = '3'
MySQL执行这条SQL语句,肯定是先把id=3的这条记录查出来,然后将name字段给改掉。这没问题吧?
实际上Mysql的基本存储结构是页(记录都存在页里边),所以MySQL是先把这条记录所在的页找到,然后把该页加载到内存中,将对应记录进行修改。
现在就可能存在一个问题:如果在内存中把数据改了,还没来得及落磁盘,而此时的数据库挂了怎么办?显然这次更改就丢了。
如果每个请求都需要将数据立马落磁盘之后,那速度会很慢,MySQL可能也顶不住。所以MySQL是怎么做的呢?
MySQL引入了redo log,内存写完了,然后会写一份redo log,这份redo log记载着这次在某个页上做了什么修改。
其实写redo log的时候,也会有buffer,是先写buffer,再真正落到磁盘中的。至于从buffer什么时候落磁盘,会有配置供我们配置。
写redo log也是需要写磁盘的,但它的好处就是顺序IO(我们都知道顺序IO比随机IO快非常多)。
所以,redo log的存在为了:当我们修改的时候,写完内存了,但数据还没真正写到磁盘的时候。此时我们的数据库挂了,我们可以根据redo log来对数据进行恢复。因为redo log是顺序IO,所以写入的速度很快,并且redo log记载的是物理变化(xxxx页做了xxx修改),文件的体积很小,恢复速度很快。
存储本质特点
binlog记载的是update/delete/insert这样的SQL语句,而redo log记载的是物理修改的内容(xxxx页修改了xxx)。
所以在搜索资料的时候会有这样的说法:redo log 记录的是数据的物理变化,binlog 记录的是数据的逻辑变化
功能比较
redo log的作用是为持久化而生的。写完内存,如果数据库挂了,那我们可以通过redo log来恢复内存还没来得及刷到磁盘的数据,将redo log加载到内存里边,那内存就能恢复到挂掉之前的数据了。
binlog的作用是复制和恢复而生的。
主从服务器需要保持数据的一致性,通过binlog来同步数据。
如果整个数据库的数据都被删除了,binlog存储着所有的数据变更情况,那么可以通过binlog来对数据进行恢复。
redo log事务开始的时候,就开始记录每次的变更信息,而binlog是在事务提交的时候才记录。
如果写redo log和binlog都成功了,那这次事务算是成功了。
但是如果我写其中的某一个log,失败了,那会怎么办?现在我们的前提是先写redo log,再写binlog,我们来看看:
如果写redo log失败了,那我们就会回滚redo log不再写binlog。
如果写redo log成功了,写binlog,写binlog写一半了,但失败了怎么办?那我们会将无效的binlog给删除(因为binlog会影响从库的数据,所以需要做删除操作)
简单来说:MySQL需要保证redo log和binlog的数据是一致的,如果不一致,那就乱套了。
如果redo log写失败了,而binlog写成功了。那假设内存的数据还没来得及落磁盘,机器就挂掉了。那主从服务器的数据就不一致了。(从服务器通过binlog得到最新的数据,而主服务器由于redo log没有记载,没法恢复数据)
如果redo log写成功了,而binlog写失败了。那从服务器就拿不到最新的数据了。那么如何解决这个问题呢?
MySQL通过两阶段提交来保证redo log和binlog的数据是一致的。
阶段1:InnoDBredo log 写盘,InnoDB 事务进入 prepare 状态
阶段2:binlog 写盘,InooDB 事务进入 commit 状态
undo log主要有两个作用:回滚和多版本控制(MVCC)
在数据修改的时候,不仅记录了redo log,还记录undo log,如果因为某些原因导致事务失败或回滚了,可以用undo log进行回滚
undo log主要存储的也是逻辑日志,比如我们要insert一条数据了,那undo log会记录的一条对应的delete日志。我们要update一条记录时,它会记录一条对应相反的update记录。
这也应该容易理解,毕竟回滚嘛,跟需要修改的操作相反就好,这样就能达到回滚的目的。因为支持回滚操作,所以我们就能保证:“一个事务包含多个操作,这些操作要么全部执行,要么全都不执行”。【原子性】
因为undo log存储着修改之前的数据,相当于一个前版本,MVCC实现的是读写不阻塞,读的时候只要返回前一个版本的数据就行了。
在操作系统中,用户空间下的缓冲区数据一般情况下是无法直接写入磁盘的,中间必须经过操作系统内核空间缓冲区( OS buffer)
因此,redo log、undo log每次先写入log buffer中,然后通过刷盘时机,将buffer 的数据写入file,写入 log file 实际上是先写入 OS Buffer,然后再通过系统调用 fsync() 将其刷到 redo log file中,大致过程:
mysql 通过参数 innodb_flush_log_at_trx_commit 来控制刷盘时机,取值是0、1和2三种值。
0(延迟写): 事务提交时并不会立即将 redo log buffer 中日志写入到 os buffer中,而是每秒写入 os buffer 并调用 fsync() 写入到 redo log file 中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据
1(实时写,实时刷):事务每次提交都会将 redo log buffer 中的日志写入 os buffer 并调用 fsync() 刷到 redo log file 中。这种方式即使系统崩溃也不会丢失任何数据,是最安全的,同时也是默认值
2(实时写,延迟刷):每次提交都仅写入到 os buffer,然后是每秒调用 fsync() 将 os buffer 中的日志写入到 redo log file