示例


1


2




​mysql> ​​​​create​​ ​​table​​ ​​T(ID ​​​​int​​ ​​primary​​ ​​key​​​​, c ​​​​int​​​​);​


​mysql> ​​​​update​​ ​​T ​​​​set​​ ​​c=c+1 ​​​​where​​ ​​ID=2;​


Redo

InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1 GB,那么总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写。

MySQL:Redo & binlog_数据

  • write pos是当前记录的位置,一边写一边后移,写到第 3 号文件末尾后就回到 0 号文件开头。checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
  • write pos 和 checkpoint 之间空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。
  • 有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。

在 InnoDB 存储引擎目录下会有两个名为 ib_logfile0 和 ib_logfile1 的文件。每个 InnoDB 存储引擎至少有 1 个重做日志文件组,每个文件组下至少有 2 个重做日志文件。在日志组中每个重做日志文件的大小一致,并以循环写入的方式运行。

参数

  • innodb_log_file_size:指定每个重做日志文件的大小
  • innodb_log_files_in_group:指定了日志文件组中重做日志文件的数量,默认为 2
  • innodb_mirrored_log_groups:指定了日志镜像文件组的数量,默认为 1
  • innodb_log_group_home_dir:指定了日志文件组所在路径,默认为 ./,表示在 MySQL 数据库的数据目录下。
  • innodb_flush_log_zt_trx_commit:表示在提交操作时,处理重做日志的方式
  • 0:当提交事务时,并不将事务的重做日志写入磁盘上的日志文件,而是等待主线程每秒的刷新。
  • 1:在执行 commit 时,将重做日志缓冲同步写到磁盘,即伴有 fsync 的调用
  • 2:将重做日志异步写到磁盘,即写到文件系统的缓存中。
binlog

redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。

作用

  • 恢复(recovery):某些数据的恢复需要二进制日志
  • 复制(replication):其原理与恢复类似,通过复制和执行二进制日志使一台远程的 MySQL 数据库与一台 MySQL 数据库进行实时同步。
  • 审计(audit):永和可以通过二进制日志中的信息来进行审计,判断是否有对数据库进行注入的攻击

参数

  • log-bin[=name]:启用二进制日志。默认二进制日志文件名为主机名,后缀名为二进制日志的序列号,所在路径为数据库所在的目录。
  • max_binlog_size:指定了单个二进制日志文件的最大值,如果超过该值,则产生二进制日志文件,后缀名 +1,默认大小为 1G。
  • binlog_cache_size:binlog 缓冲的大小由 binlog_cache_size 决定,默认大小为 32K。binlog_cache_size 是基于会话的,如够用时会把缓冲日志写入一个临时文件中。通过 SHOW GLOBAL STATUS 命令查看 binlog_cache_use(使用缓冲写二进制日志的次数)、binlog_cache_disk_use(记录使用临时文件写二进制日志的次数) 的状态,判断是否合适
  • sync_binlog=[N]:表示每写缓冲多少次就同步到磁盘。如果将 N 设为 1,则写操作不使用操作系统的缓冲来写二进制日志。
  • binlog-do-db:表示需要写入哪些库的日志
  • binlog-ignore-db:表示需要忽略哪些库的日志
  • log-slave-update:如果当前数据库是复制中的 slave 角色,则它不会从 master 取得并执行的二进制日志写入自己的二进制日志文件中去。如果需要,则需要设置。
  • binlog_format:
  • STATEMENT:二进制日志文件记录的是日志的逻辑 SQL 语句。
  • ROW:记录表的更改情况。
  • MIXED:MySQL 默认采用 STATEMENT 格式进行二进制日志文件的记录,但是在一些情况下会使用 ROW 格式
  • NDB
  • 使用 UUID()、USER()、CURRENT_USER()、FOUND_ROWS()、ROW_COUNT() 等不确定函数。
  • 使用了 INSERT DELAY 语句
  • 使用了用户定义函数(UDF)
  • 使用了临时表
区别
  1. redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
  2. redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
  3. redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
两阶段提交

为了让两份日志之间的逻辑一致

流程

MySQL:Redo & binlog_日志文件_02

  1. 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
  2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
  3. 引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
  4. 执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
  5. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。

原因

可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。

  1. 先写 redo log 后写 binlog 假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。 但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。 然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。
  2. 先写 binlog 后写 redo log 如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。但是 binlog 里面已经记录了“把 c 从 0 改成 1”这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。

机制

MySQL:Redo & binlog_日志文件_03

 

double 由两部分组成,一部分是内存中 doublewrite buffer,大小为 2MB,另一部分是物理磁盘上共享表空间中连续的 128 个页,即 2 个区,大小同样为 2 MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是会通过 memcpy 函数将脏页先复制到内存中的 doublewrite buffer,之后通过 doublewrite buffer 再分两次,每次 1 MB 顺序地写入共享表空间的物理磁盘上,然后马上调用 fsync 函数,同步磁盘,避免缓冲写带来的问题。在完成 doublewrite 页的写入后,再将 doublewrite buffer 中的页写入各个表空间文件中,此时的写入则是离散的。

如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB 存储引擎可以从共享表空间中的 doublewrite 中找到该页的一个副本,将其复制到表空间文件,再应用重做日志。

参数推荐设置

  • innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。
  • sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。