文章目录
- 什么是binlog
- 使用binlog进行恢复的流程
- 什么是redolog
- 缓冲池
- redolog
- redolog结构
- 两阶段提交
什么是binlog
binlog是二进制格式的文件,用于记录用户对数据库的修改,可以作用于主从复制过程中数据同步以及基于时间点的恢复(PITR)。
一般我们会将binlog用于以下几个方面:
- 数据库增量备份与恢复:在使用备份还原数据后,可以使用 binlog 中记录的内容对备份时间点(简称备份点)后的数据进行恢复。因为 binlog 会还会记录下更改操作的时间,所以 binlog 可以恢复到某一具体时间点的数据。这就为我们删库后提供了除跑路以外的第二个选项:使用 binlog 恢复数据。
- 主从复制:MySQL 从服务器可以通过订阅 binlog 实现对主服务器的增量复制。
- 通过对 binlog 中的数据进行审计,判断是否存在安全问题,比如 SQL 注入。
记录的内容包括:写操作语句(增删改)、表结构更改(create table、alter table、drop table)、数据的
开始和提交(begin和commit操作)
使用binlog进行恢复的流程
- 先通过最新的备份恢复数据库的数据,并记录下备份文件备份的时间点。
- 在 binlog 中找到这个时间点,提取这个时间点以后的数据用于实现对备份点后数据的恢复(这个特性被称为 Point in Time,简称 PIT)。
什么是redolog
缓冲池
首先要明确一个缓冲池的概念,在读取数据的时候,首先会把数据放到缓冲中,下次在读取的额时候会先去缓冲池中获取(减少磁盘IO,提高性能),如果没有的话,在往数据库中查询。在修改数据的时候如果缓冲池中不存在所需要的数据页,就从磁盘读取,否则就在缓冲池中把数据页进行修改。
这里会有一个问题,在缓冲池中修改了数据页,和磁盘上的数据会造成短暂的不一致的情况,这个情况叫做脏页,把不一致的内容刷新的磁盘这个操作叫做刷脏页。
什么时候刷脏页:
- 每 10 秒必刷新一次
- 脏页太多时(默认占比超过 innodb_max_dirty_pages_pct 配置的值时刷新)
- redo log 空间不足时
- 数据库关闭时
redolog
上文中提到数据如果在缓冲池中被修改,会有短暂的时间和数据库中不一致,造成脏数据,那么当缓冲池中的数据被修改之后,这个时候服务器出现宕机,就可能会造成数据的丢失,那么这个时候可以使用WAL(write-Ahead Logging)日志先行,也就是事务提交前先写入日志,在修改页,这里的日志指的就是redolog
redolog结构
xx表空间,xx页,xx位置,xx值
由于 redo log 是顺序写(顺序 IO),因此能有效提升 IO 效率;又因为每次事务提交前会先写 redo log,因此可以保障更新的数据不丢失。
一旦脏页刷新,磁盘上对应的 redo log 就会失效,所以 redo log 用完后,可以再回头使用,这样更节省空间。直到需要刷 redo log buffer 时发现接下来的 redo log 对应的脏页未被刷新,此时会强制刷新脏页。缓冲池的好处我们前面已经讲过,所以 redo log 弄了个类似作用的 redo log buffer。在写 redo log 时会先写 redo log buffer,并在以下时机将 redo log 刷新到磁盘
- 每秒刷新一次
- 事务提交
- redo log buffer设于空间小于1/2
redolog结构
如果脏页没刷完,数据库宕机了,那么必然是需要使用 redo log 来恢复数据的。那么 redo log 应该从哪开始恢复数据呢?为解决这个问题 InnoDB 为 redo log 记录了序列号,这被称为 LSN(Log Sequence Number),可以理解为偏移量,越新的日志 LSN 越大。InnoDB 用检查点(checkpoint_lsn)指示未被刷脏页的 redo log 数据从这里开始,用 lsn 指示下一个应该被写入日志的位置。不过由于有 redo log buffer 的缘故,实际被写入磁盘的位置往往比 lsn 要小。
两阶段提交
为什么要两阶段提交呢?直接提交不行吗?
我们可以假设不采用两阶段提交的方式,而是采用“单阶段”进行提交,即要么先写入 redo log,后写入 binlog;要么先写入 binlog,后写入 redo log。这两种方式的提交都会导致原先数据库的状态和被恢复后的数据库的状态不一致。
先写入 redo log,后写入 binlog:
在写完 redo log 之后,数据此时具有crash-safe能力,因此系统崩溃,数据会恢复成事务开始之前的状态。但是,若在 redo log 写完时候,binlog 写入之前,系统发生了宕机。此时 binlog 没有对上面的更新语句进行保存,导致当使用 binlog 进行数据库的备份或者恢复时,就少了上述的更新语句。从而使得id=2这一行的数据没有被更新。
先写入 binlog,后写入 redo log:
写完 binlog 之后,所有的语句都被保存,所以通过 binlog 复制或恢复出来的数据库中 id=2 这一行的数据会被更新为 a=1。但是如果在 redo log 写入之前,系统崩溃,那么 redo log 中记录的这个事务会无效,导致实际数据库中id=2这一行的数据并没有更新。
简单说,redo log 和 binlog 都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致
总体来说:
更新语句的执行是 Server 层和引擎层配合完成,数据除了要写入表中,还要记录相应的日志。
执行器先找引擎获取 ID=2 这一行。ID 是主键,存储引擎检索数据,找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
执行器生成这个操作的 binlog,并把 binlog 写入磁盘。
执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
从上图可以看出,MySQL 在执行更新语句的时候,在服务层进行语句的解析和执行,在引擎层进行数据的提取和存储;同时在服务层对 binlog 进行写入,在 InnoDB 内进行 redo log 的写入。
不仅如此,在对 redo log 写入时有两个阶段的提交,一是 binlog 写入之前prepare状态的写入,二是 binlog 写入之后commit状态的写入。