在MySQL数据库和InnoDB存储引擎中,有很多种文件,如:参数文件、日志文件、socket文件、pid文件、MySQL表结构文件、存储引擎文件。
本节重点关注日志文件,MySQL的复制、事务等重要功能都和日志文件相关。日志文件主要包括错误日志文件
、二进制日志文件
、慢查询日志文件
、查询日志文件
、重做日志文件
等。其中重做日志文件是InnoDB引擎文件。
1、日志文件介绍
1.1、错误日志(error log)
错误日志文件对MySQL的启动、运行、关闭过程进行了记录,是定位MySQL问题的第一把钥匙。
1.2、慢查询日志(slow query log)
慢查询日志是用来记录执行时间超过 long_query_time 这个变量定义的时长的查询语句。通过慢查询日志,可以查找出哪些查询语句的执行效率很低,以便进行优化。
1.3、一般查询日志(general log)
一般查询日志记录了所有对MySQL数据库请求的信息,无论请求是否正确执行。
默认情况下,general log 是关闭的,开启通用查询日志会增加很多磁盘 I/O, 所以如非出于调试排错目的,不建议开启通用查询日志。
1.4、二进制日志(bin log)
关于二进制日志,它记录了数据库所有执行的DDL和DML语句(除了数据查询语句select、show等),以事件形式记录并保存在二进制文件中。
- log_bin:指定binlog是否开启及文件名称。
- server_id:指定服务器唯一ID,开启binlog 必须设置此参数。
- binlog_format:指定binlog模式,建议设置为ROW。
- max_binlog_size:控制单个二进制日志大小,当前日志文件大小超过此变量时,执行切换动作。
- expire_logs_days:控制二进制日志文件保留天数,默认值为0,表示不自动删除,可设置为0~99。
二进制日志文件是非常重要的日志文件,建议开启,二进制日志主要有这么几个作用:
恢复
:利用二进制文件恢复数据,原理是取出日志的操作记录,重新执行
复制
:和恢复原理类似,一般分为主库和从库。审计
: 通过观察二进制文件,可以判断是否存在对数据库有危险性的操作。
1.5、重做日志(redo log)
对于InnoDB,重做日志至关重要,因为它们记录了对于InnoDB存储引擎的事务日志。
当实例或介质失败时,如数据库由于所在主机断电导致实例失败,InnoDB存储引擎就会恢复到断电前的时刻,以此来保证数据的完整性。
InnoDB引擎对数据的更新,是先将更新记录写入redo log日志,然后会在系统空闲的时候或者是按照设定的更新策略再将日志中的内容更新到磁盘之中。这就是所谓的预写式技术(Write Ahead logging)。这种技术可以大大减少IO操作的频率,提升数据刷新的效率。
写入重做日志文件也不是直接写,而是先写入一个重做日志缓冲,然后按照一定的条件顺序写入日志文件。
redo log日志的大小是固定的,为了能够持续不断的对更新记录进行写入,在redo log日志中设置了两个标志位置,checkpoint和write_pos,分别表示记录擦除的位置和记录写入的位置。redo log日志的数据写入示意图可见下图。
当write_pos
标志到了日志结尾时,会从结尾跳至日志头部进行重新循环写入。所以redo log的逻辑结构并不是线性的,而是可看作一个圆周运动。write_pos
与checkpoint
中间的空间可用于写入新数据,写入和擦除都是往后推移,循环往复的。
当write_pos
追上checkpoint
时,表示redo log日志已经写满。这时不能继续执行新的数据库更新语句,需要停下来先删除一些记录,执行checkpoint
规则腾出可写空间。
checkpoint规则:checkpoint触发后,将buffer中脏数据页和脏日志页都刷到磁盘。
同样是记录事务日志,和bin log有什么不同呢?
- bin log会记录所有与数据库有关的日志记录,包括InnoDB、MyISAM等存储引擎的日志,而redo log只记InnoDB存储引擎的日志。
- 记录的内容不同,bin log记录的是关于一个事务的具体操作内容,即该日志是逻辑日志。而redo log记录的是关于每个页(Page)的更改的物理情况。
- 写入的时间不同,bin log仅在事务提交前进行提交,也就是只写磁盘一次。而在事务进行的过程中,却不断有redo ertry被写入redo log中。
- 写入的方式也不相同,redo log是循环写入和擦除,bin log是追加写入,不会覆盖已经写的文件。
1.6、回滚日志(undo log)
提到了redo log,这里在简单了解一下回滚日志(undo log)。
回滚日志同样也是InnoDB引擎提供的日志,顾名思义,回滚日志的作用就是对数据进行回滚。当事务对数据库进行修改,InnoDB引擎不仅会记录redo log,还会生成对应的undo log日志;如果事务执行失败或调用了rollback,导致事务需要回滚,就可以利用undo log中的信息将数据回滚到修改之前的样子。
但是undo log不redo log不一样,它属于逻辑日志。它对SQL语句执行相关的信息进行记录。当发生回滚时,InnoDB引擎会根据undo log日志中的记录做与之前相反的工作。比如对于每个数据插入操作(insert),回滚时会执行数据删除操作(delete);对于每个数据删除操作(delete),回滚时会执行数据插入操作(insert);对于每个数据更新操作(update),回滚时会执行一个相反的数据更新操作(update),把数据改回去。undo log由两个作用,一是提供回滚,二是实现MVCC。
2、更新语句执行
接下来结合一条更新语句的执行,来进一步理解bin log和redo log这两种重要日志。
update t set c=c+1 where ID=2;
2.1、更新语句执行流程
我们来看执行器和InnoDB引擎在执行这个简单的update语句时的内部流程。
- 执行器先找引擎取ID=2这一行。ID是主键,引擎直接用树搜索找到这一行。如果ID=2这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
- 执行器拿到引擎给的行数据,把这个值加上1,比如原来是N,现在就是N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
- 引擎将这行新数据更新到内存中,同时将这个更新操作记录到redo log里面,此时redo log处于prepare状态。然后告知执行器执行完成了,随时可以提交事务。
- 执行器生成这个操作的binlog,并把binlog写入磁盘。
- 执行器调用引擎的提交事务接口,引擎把刚刚写入的redo log改成提交(commit)状态,更新完成。
update语句的执行流程图如下:
2.2、两阶段提交
这条语句的执行过程中,redo log的写入分成了两个步骤分成了prepare和commit两个阶段进行提交,这就是所谓的两阶段提交
。为什么要用两阶段提交呢?
先用通俗的说法:
比如小明去超市里买一瓶可乐:
- 小明:老板给我来瓶可乐!透心凉心飞扬的那个。
- 老板:机器扫一下可乐,告诉小明这瓶可乐2块5,给钱(记录 redo log,事务处于prepare状态)
- 收钱放入钱箱(记录 binlog,事务实际是否完成的根本依据,处于待标记commit阶段)
- 然后让小明把可乐拿走(redo log 状态标为 commit,表示该事务逻辑闭环)。到这里,代表一笔交易结束。
- 等算账前再把这一天卖东西的交易信息一起同步到数据库。
可见,如果收钱之前(prepare阶段,步骤3)交易被打断,回过头来处理此次交易,发现只有记了小黑板但没有收钱,则交易失败,删掉小黑板上的记录(回滚);
如果收了钱后(commit阶段 或 待commit阶段,步骤4 || 5)交易被打断,然后回过头发现系统上有记录(prepare)而且钱箱有本次收入(bin log),则说明本次交易有效,补充修改commit状态,更新到库存中。
用正式一点的语言来描述。
如果不用两阶段提交,要么就是先写完redo log再写binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题。
仍然用前面的update语句来做例子。假设当前ID=2的行,字段c的值是0,再假设执行update语句过程中在写完第一个日志后,第二个日志还没有写完期间发生了crash,会出现什么情况呢?
- 先写redo log后写binlog。假设在redo log写完,binlog还没有写完的时候,MySQL进程异常重启。由于我们前面说过的,redo log写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行c的值是1。
但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。
然后你会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这个临时库就会少了这一次更新,恢复出来的这一行c的值就是0,与原库的值不同。
- 先写binlog后写redo log。如果在binlog写完之后crash,由于redo log还没写,崩溃恢复以后这个事务无效,所以这一行c的值是0。但是binlog里面已经记录了“把c从0改成1”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不同。
可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。
你可能会说,这个概率是不是很低,平时也没有什么动不动就需要恢复临时库的场景呀?
其实不是的,不只是误操作后需要用这个过程来恢复数据。当你需要扩容的时候,也就是需要再多搭建一些备库来增加系统的读能力的时候,现在常见的做法也是用全量备份加上应用binlog来实现的,这个“不一致”就会导致你的线上出现主从数据库不一致的情况。
简单说,redo log和binlog都可以用于表示事务的提交状态,而两阶段提交就是让这两个状态保持逻辑上的一致。
2.3、日志落盘
保证事务成功,日志必须落盘,这样,数据库crash后,就不会丢失某个事务的数据了
- innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log 都直接持久化到磁盘。这样可以保证 MySQL 异常重启之后数据不丢失。
- sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这样可以保证 MySQL 异常重启之后 binlog 不丢失。