MySql 三大日志
binlog-mysql-server层
redolog-innodb-存储引擎层
redolog-崩溃恢复
比如 MySQL
实例挂了或宕机了,重启时,InnoDB
存储引擎会使用redo log
恢复数据,保证数据的持久性与完整性。
每条 redo 记录由“表空间号+数据页号+偏移量+修改数据长度+具体修改的数据”组成
MySQL
中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到Buffer Pool
中。- 后续的查询都是先从
Buffer Pool
中找,没有命中再去硬盘加载,减少硬盘IO
开销,提升性能。 - 更新表数据的时候,也是如此,发现
Buffer Pool
里存在要更新的数据,就直接在Buffer Pool
里更新。 - 然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(
redo log buffer
)里,接着刷盘到redo log
文件里。
InnoDB 内部维护了一个缓冲池,用于减少对磁盘数据的直接IO操作,并配合 redo log、内部的 change buffer 来实现异步的落盘,保证程序的高效执行。redo log 大小固定,采用循环写write pos 表示当前正在记录的位置,会向后记录, checkpoint 表示数据落盘的边界,也就是 checkpoint 与 write pos中间是已记录的,当 write pos写完 id_logfile_3后,会回到id_logfile_0循环写,而追上 checkpomnit 后则需要先等数据进行落盘,等待 checkponit向后面移动一段距离再写。redo log存储的内容是对数据页的修改逻辑。
刷盘时机
InnoDB
存储引擎为 redo log
的刷盘策略提供了 innodb_flush_log_at_trx_commit
参数,它支持三种策略:
- 0 :设置为 0 的时候,表示每次事务提交时不进行刷盘操作。如果MySQL挂了或宕机可能会有1秒数据的丢失
- 1 :设置为 1 的时候,表示每次事务提交时都将进行刷盘操作(默认值)。如果事务执行期间MySQL挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。
- 2 :设置为 2 的时候,表示每次事务提交时都只把 redo log buffer 内容写入 page cache。如果仅仅只是MySQL挂了不会有任何数据丢失,但是宕机可能会有1秒数据的丢失。
innodb_flush_log_at_trx_commit
参数默认为 1 ,也就是说当事务提交时会调用 fsync
对 redo log 进行刷盘
另外,InnoDB
存储引擎有一个后台线程,每隔1
秒,就会把 redo log buffer
中的内容写到文件系统缓存(page cache
),然后调用 fsync
刷盘。
除了后台线程每秒1
次的轮询操作,还有一种情况,当 redo log buffer
占用的空间即将达到 innodb_log_buffer_size
一半的时候,后台线程会主动刷盘。
日志文件组
redolog以日志文件组 环形链表的方式存在
在个日志文件组中还有两个重要的属性,分别是 write pos、checkpoint
- write pos 是当前记录的位置,一边写一边后移
- checkpoint 是当前要擦除的位置,也是往后推移
binlog 逻辑日志,记录内容是语句的原始逻辑
MySQL
数据库的数据备份、主备、主主、主从都离不开binlog
,需要依靠binlog
来同步数据,保证数据一致性。
binlog
会记录所有涉及更新数据的逻辑操作,并且是顺序写。(append的速度比较快)
记录格式
binlog
日志有三种格式,可以通过binlog_format
参数指定。
-
statement
-
row
-
mixed
指定
statement
,记录的内容是SQL
语句原文,比如执行一条update T set update_time=now() where id=1
,
指定为row
,记录的内容不再是简单的SQL
语句了,还包含操作的具体数据
update_time=now()
变成了具体的时间update_time=1627112756247
,条件后面的@1、@2、@3 都是该行数据第 1 个~3 个字段的原始值(假设这张表只有 3 个字段)。
折中方案 mixed
MySQL
会判断这条SQL
语句是否可能引起数据不一致,如果是,就用row
格式,否则就用statement
格式
写入机制
- 事务执行过程中先把日志写到
binlog cache
, - 事务提交的时候,再把
binlog cache
写到binlog
文件中。
因为一个事务的binlog
不能被拆开,无论这个事务多大,也要确保一次性写入,所以系统会给每个线程分配一个块内存作为binlog cache
。
我们可以通过binlog_cache_size
参数控制单个线程 binlog cache 大小,如果存储内容超过了这个参数,就要暂存到磁盘(Swap
)。
刷盘
- 上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快
- 上图的 fsync,才是将数据持久化到磁盘的操作
write
和fsync
的时机,可以由参数sync_binlog
控制,默认是0
。
为0
的时候,表示每次提交事务都只write
,由系统自行判断什么时候执行fsync
。
为了安全起见,可以设置为1
,表示每次提交事务都会执行fsync
,就如同 redo log 日志刷盘流程 一样
最后还有一种折中方式,可以设置为N(N>1)
,表示每次提交事务都write
,但累积N
个事务后才fsync
。
两段提交——将redo log
的写入拆成了两个步骤prepare
和commit
,这就是两阶段提交。
redo log
(重做日志)让InnoDB
存储引擎拥有了崩溃恢复能力。
binlog
(归档日志)保证了MySQL
集群架构的数据一致性。
举例 证明为什么要有prepare阶段
- **先写redo log后写binlog:**假设在redolog写完,binlog还没有写完的时候,mysql进程异常重启。由于我们前面说过的,redolog写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行c的值是1。但是由于binlog没写完就crash了,这时候binlog里面就没有记录这个语句。因此,之后备份日志的时候,存起来的binlog里面就没有这条语句。然后就会发现,如果需要用这个binlog来恢复临时库的话,由于这个语句的binlog丢失,这个临时库就会少了一次更新,恢复出来的这一行c的值就是0,与原库的值不同
- **先写binlog 后写redolog:**如果在binlog写完 之后crash,由于redolog还没写,崩溃恢复以后这个事务无效,所以这一行c的值是0.但是binlog里面已经记录了“把c从0改成1 ”这个日志。所以,在之后用binlog来恢复的时候就多了一个事务出来,恢复出来的这一行c的值就是1,与原库的值不同。
undolog (回滚日志-保存历史版本状态)
undo log 主要有两个作用:
-
当事务回滚时用于将数据恢复到修改前的样子
-
另一个作用是 MVCC ,当读取记录时,若该记录被其他事务占用或当前版本对该事务不可见,则可以通过 undo log 读取之前的版本数据,以此实现非锁定读
在 InnoDB 存储引擎中 undo log 分为两种: insert undo log 和 update undo log:
- insert undo log :指在 insert 操作中产生的 undo log。因为 insert 操作的记录只对事务本身可见,对其他事务不可见,故该 undo log 可以在事务提交后直接删除。不需要进行 purge 操作
- update undo log :update 或 delete 操作中产生的 undo log。该 undo log可能需要提供 MVCC 机制,因此不能在事务提交时就进行删除。提交时放入 undo log 链表,等待 purge线程 进行最后的删除