海哥 发表于 2024-11-14 09:38:22

Mysql篇-三大日志

概述


[*]undo log(回滚日志):是 Innodb 存储引擎层天生的日志,实现了事件中的原子性,紧张用于事件回滚和 MVCC。
[*]redo log(重做日志):是 Innodb 存储引擎层天生的日志,实现了事件中的持久性,紧张用于掉电等故障恢复;
[*]binlog (归档日志):是 Server 层天生的日志,紧张用于数据备份和主从复制;
回滚日志(undo log)

作用


[*]保存了事件发生之前的数据的一个版本,可以用于回滚,保障原子性
[*]实现多版本并发控制下的读(MVCC)的关键因素之一,也即非锁定读,MVCC通过Read View + undolog的版本链实现,可以详细看MVCC的快照读
内容

逻辑格式的日志,在执行 undo 的时间,仅仅是将数据从逻辑上恢复至事件之前的状态,而不是从物理页面上操作实现的,这一点是不同于redo log 的。
每当 InnoDB 引擎对一条记载举行操作(修改、删除、新增)时,要把回滚时需要的信息都记载到 undo log 里,比如:

[*]在插入insert一条记载时,要把这条记载的主键值记下来,这样之后回滚时只需要把这个主键值对应的记载删掉delete就好了;
[*]在删除delete一条记载时,要把这条记载中的内容都记下来,这样之后回滚时再把由这些内容组成的记载插入insert到表中就好了;
[*]在更新一条记载时,要把被更新的列的旧值记下来,这样之后回滚时再把这些列更新为旧值就好了。
什么时间产生

事件开始之前,MySQL 会先记载更新前的数据到 undo log 日志文件里面,当事件回滚时,可以利用 undo log 来举行回滚。同时undo 也会产生 redo 来保证undo log的可靠性。
https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202404261841443.jpeg
什么时间刷盘

undo log 和数据页的刷盘计谋是一样的,都需要通过 redo log 保证持久化。产生undo日志的时间,同样会伴随类似于掩护事件持久化机制的redolog的产生。
buffer pool 中有 undo 页,对 undo 页的修改也都会记载到 redo log。redo log 会每秒刷盘,提交事件时也会刷盘,数据页和 undo 页都是靠这个机制保证持久化的,详细看下面内容。
重做日志(redo log)

作用


[*]确保事件的持久性。

[*]为了防止断电导致数据丢失的问题,当有一条记载需要更新的时间,InnoDB 引擎就会先更新内存(同时标记为脏页),然后将本次对这个页的修改以 redo log 的情势记载下来,这个时间更新就算完成了。也就是说, redo log 是为了防止 Buffer Pool 中的脏页丢失而计划的。
[*]在重启mysql服务的时间,根据redo log举行重做,从而达到事件的持久性这一特性。

[*]将写操作从「随机写」酿成了「次序写」,提升 MySQL 写入磁盘的性能。
内容

物理格式的日志,记载的是物理数据页面的修改的信息,其 redo log 是次序写入redo log file 的物理文件中去的。同时,在内存修改 Undo log 后,也需要记载undo log对应的 redo log。
redo log 和 undo log 区别:

[*]redo log 记载了此次事件完成后的数据状态,记载的是更新之后的值;
[*]undo log 记载了此次事件开始前的数据状态,记载的是更新之前的值;
什么时间产生

事件开始之后就产生redo log,redo log的落盘并不是随着事件的提交才写入的,而是在事件的执行过程中,便开始写入redo log文件中。
事件提交之前发生了崩溃,重启后会通过 undo log 回滚事件,事件提交之后发生了崩溃,重启后会通过 redo log 恢复事件,如下图:
https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202404261841327.jpeg
redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?
写入 redo log 的方式使用了追加操作, 以是磁盘操作是次序写,而写入数据需要先找到写入位置,然后才写到磁盘,以是磁盘操作是随机写。磁盘的「次序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。
什么时间刷盘

实际上, 执行一个事件的过程中,产生的 redo log 也不是直接写入磁盘的,因为这样会产生大量的 I/O 操作,而且磁盘的运行速度远慢于内存。
redo log有一个缓存区 Innodb_log_buffer,Innodb_log_buffer 的默认巨细为 16M,每当产生一条 redo log 时,会先写入到 redo log buffer,后续再持久化到磁盘。
https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202404261841782.png
然后会通过以下三种方式将innodb log buffer的日志革新到磁盘:

[*]MySQL 正常关闭时;
[*]当 redo log buffer 中记载的写入量大于 redo log buffer 内存空间的一半时,会触发落盘;
[*]InnoDB 的后台线程每隔 1 秒,将 redo log buffer 持久化到磁盘。
[*]每次事件提交时都将缓存在 redo log buffer 里的 redo log 直接持久化到磁盘。
因此redo log buffer的写盘,并不一定是随着事件的提交才写入redo log文件的,而是随着事件的开始,逐步开始的。
即使某个事件还没有提交,Innodb存储引擎仍然每秒会将redo log buffer革新到redo log文件。
这一点是必须要知道的,因为这可以很好地解释再大的事件的提交(commit)的时间也是很短暂的。
redolog的文件

两个 redo 日志的文件名叫 :ib_logfile0 和 ib_logfile1。
redo log文件组是以循环写的方式工作的, InnoDB 存储引擎会先写 ib_logfile0 文件,当 ib_logfile0 文件被写满的时间,会切换至 ib_logfile1 文件,当 ib_logfile1 文件也被写满时,会切换回 ib_logfile0 文件;相称于一个环形。
https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202404261841807.png

[*]write pos 和 checkpoint 的移动都是顺时针方向;
[*]write pos ~ checkpoint 之间的部分(图中的赤色部分),用来记载新的更新操作;
[*]check point ~ write pos 之间的部分(图中蓝色部分):待落盘的脏数据页记载;
因此,如果 write pos 追上了 checkpoint,就意味着 redo log 文件满了,这时 MySQL 不能再执行新的更新操作,也就是说 MySQL 会被壅闭
二进制日志(binlog)

作用


[*]用于复制,在主从复制中,从库利用主库上的binlog举行重放,实现主从同步。
[*]用于数据库的基于时间点的还原,即备份恢复
内容

binlog 有 3 种格式范例,分别是 STATEMENT(默认格式)、ROW、 MIXED,区别如下:

[*]STATEMENT:每一条修改数据的 SQL 都会被记载到 binlog 中(相称于记载了逻辑操作,以是针对这种格式, binlog 可以称为逻辑日志),主从复制中 slave 端再根据 SQL 语句重现。但 STATEMENT 有动态函数的问题,比如你用了 uuid 或者 now 这些函数,你在主库上执行的结果并不是你在从库执行的结果,这种随时在变的函数会导致复制的数据不一致;
[*]ROW:记载行数据最终被修改成什么样了(这种格式的日志,就不能称为逻辑日志了),不会出现 STATEMENT 下动态函数的问题。但 ROW 的缺点是每行数据的变革结果都会被记载,比如执行批量 update 语句,更新多少行数据就会产生多少条记载,使 binlog 文件过大,而在 STATEMENT 格式下只会记载一个 update 语句而已;
[*]MIXED:包含了 STATEMENT 和 ROW 模式,它会根据不同的情况自动使用 ROW 模式和 STATEMENT 模式;
注意:不同的日志范例在主从复制下除了有动态函数的问题,同样对对更新时间也有影响。一般来说,数据库中的update_time都会设置成ON UPDATE CURRENT_TIMESTAMP,即自动更新时间戳列。在主从复制下,
如果日志格式范例是STATEMENT,由于记载的是sql语句,在salve端是举行语句重放,那么更新时间也是重放时的时间,此时slave会偶尔间延迟的问题;
如果日志格式范例是ROW,这是记载行数据最终被修改成什么样了,这种从库的数据是与主服务器完全一致的。
什么时间产生

事件提交的时间,一次性将事件中的sql语句(一个事物大概对应多个sql语句)按照一定的格式记载到binlog中。
binlog 文件是记载了所有数据库表结构变更和表数据修改的日志,不会记载查询类的操作,比如 SELECT 和 SHOW 操作。
这里与redo log很明显的差异就是binlog 是追加写,写满一个文件,就创建一个新的文件继续写,不会覆盖以前的日志,保存的是全量的日志。redo log 是循环写,日志空间巨细是固定,全部写满就从头开始,保存未被刷入磁盘的脏页日志。
也就是说,如果不小心整个数据库的数据被删除了,只能使用 bin log 文件恢复数据。因为redo log循环写会擦除数据。
主从复制的实现

MySQL 的主从复制依赖于 binlog ,也就是记载 MySQL 上的所有变革并以二进制情势保存在磁盘上。复制的过程就是将 binlog 中的数据从主库传输到从库上。
这个过程一般是异步的,也就是主库上执行事件操作的线程不会等候复制 binlog 的线程同步完成。
https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202404261841428.jpeg
MySQL 集群的主从复制过程如下:

[*]写入 Binlog:MySQL 主库在收到客户端提交事件的请求之后,会先写入 binlog,再提交事件,更新存储引擎中的数据,事件提交完成后,返回给客户端“操作乐成”的相应。
[*]同步 Binlog:从库会创建一个专门的 I/O 线程,连接主库的 log dump 线程,来吸收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制乐成”的相应。
[*]回放 Binlog:从库会创建一个用于回放 binlog 的线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。
什么时间刷盘

在刷盘机遇上与redolog不一样,redolog即使事件没提交,也可以每隔1秒就刷盘。但是一个事件的 binlog 是不能被拆开的,因此无论这个事件有多大(比如有很多条语句),也要保证一次性写入。如果一个事件的 binlog 被拆开的时间,在备库执行就会被当做多个事件分段自行,这样就破坏了原子性,是有问题的。
bin log日志与redo log类似,也有对应的缓存,叫 binlog cache。事件提交的时间,再把 binlog cache 写到 binlog 文件中。
https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202404261841371.png

[*]图中的 write,指的就是指把日志写入到 binlog 文件,但是并没有把数据持久化到磁盘,因为数据还缓存在文件系统的 page cache 里,write 的写入速度还是比较快的,因为不涉及磁盘 I/O。
[*]图中的 fsync,才是将数据持久化到磁盘的操作,这里就会涉及磁盘 I/O,以是频繁的 fsync 会导致磁盘的 I/O 升高。
MySQL提供一个 sync_binlog 参数来控制数据库的 binlog 刷到磁盘上的频率:

[*]sync_binlog = 0 的时间,表示每次提交事件都只 write,不 fsync,后续交由操作系统决定何时将数据持久化到磁盘;
[*]sync_binlog = 1 的时间,表示每次提交事件都会 write,然后立刻执行 fsync;
[*]sync_binlog =N(N>1) 的时间,表示每次提交事件都 write,但累积 N 个事件后才 fsync。
显然,在MySQL中系统默认的设置是 sync_binlog = 0,也就是不做任何强制性的磁盘革新指令,这时间的性能是最好的,但是风险也是最大的。因为一旦主机发生非常重启,还没持久化到磁盘的数据就会丢失。
而当 sync_binlog 设置为 1 的时间,是最安全但是性能消耗最大的设置。因为当设置为 1 的时间,即使主机发生非常重启,最多丢失一个事件的 binlog,而已经持久化到磁盘的数据就不会有影响,不外就是对写入性能影响太大。
如果能容少量事件的 binlog 日志丢失的风险,为了提高写入的性能,一般会 sync_binlog 设置为 100~1000 中的某个数值。
两阶段提交

事件提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,大概出现半乐成的状态,这样就造成两份日志之间的逻辑不一致。如下:

[*]如果在将 redo log 刷入到磁盘之后, MySQL 突然宕机了,而 binlog 还没有来得及写入。那么呆板重启后,这台呆板会通过redo log恢复数据,但是这个时间binlog并没有记载该数据,后续举行呆板备份的时间,就会丢失这一条数据,同时主从同步也会丢失这一条数据。
[*]如果在将 binlog 刷入到磁盘之后, MySQL 突然宕机了,而 redo log 还没有来得及写入。由于 redo log 还没写,崩溃恢复以后这个事件无效,而 binlog 里面记载了这条更新语句,在主从架构中,binlog 会被复制到从库,从库执行了这条更新语句,那么就与主库的值不一致性;
两阶段提交把单个事件的提交拆分成了 2 个阶段,分别是「准备(Prepare)阶段」和「提交(Commit)阶段」
详细过程

https://seven97-blog.oss-cn-hangzhou.aliyuncs.com/imgs/202404261841899.png
事件的提交过程有两个阶段,就是将 redo log 的写入拆成了两个步骤:prepare 和 commit,中间再穿插写入binlog,详细如下:

[*]prepare 阶段:将 XID(内部 XA 事件的 ID) 写入到 redo log,同时将 redo log 对应的事件状态设置为 prepare,然后将 redo log 持久化到磁盘(innodb_flush_log_at_trx_commit = 1 的作用);
[*]commit 阶段:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘(sync_binlog = 1 的作用),接着调用引擎的提交事件接口,将 redo log 状态设置为 commit,此时该状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘乐成,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事件已经执行乐成;
总的来说就是,事件提交后,redo log酿成prepare 阶段,再写入binlog,返回乐成后redo log 进入commit 阶段。
总结三个日志的详细流程

当优化器分析出本钱最小的执行计划后,执行器就按照执行计划开始举行更新操作。
详细更新一条记载 UPDATE t_user SET name = 'xiaolin' WHERE id = 1; 的流程如下:

[*]检查在不在buffer Pool。 执行器负责详细执行,会调用存储引擎的接口,通过主键索引树搜索获取 id = 1 这一行记载:

[*]如果 id=1 这一行所在的数据页原来就在 buffer pool 中,就直接返回给执行器更新;
[*]如果记载不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记载给执行器。

[*]检查是否已经是要更新的值。执行器得到聚簇索引记载后,会看一下更新前的记载和更新后的记载是否一样:

[*]如果一样的话就不举行后续更新流程;
[*]如果不一样的话就把更新前的记载和更新后的记载都看成参数传给 InnoDB 层,让 InnoDB 真正的执行更新记载的操作;

[*]开启事件,记载undo log,并记载修改undo log对应的redo log:开启事件, InnoDB 层更新记载前,首先要记载相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要天生一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不外在内存修改该 Undo 页面后,需要记载对应的 redo log。
[*]标记为脏页,并写入redo log:InnoDB 层开始更新记载,会先更新内存(同时标记为脏页),然后将记载写到 redo log 里面,这个时间更新就算完成了。为了淘汰磁盘I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的机遇将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。
[*]至此,一条记载更新完了。
[*]记载binlog:在一条更新语句执行完成后,然后开始记载该语句对应的 binlog,此时记载的 binlog 会被保存到 binlog cache,并没有革新到硬盘上的 binlog 文件,在事件提交时才会统一将该事件运行过程中的所有 binlog 革新到硬盘。
[*]事件提交,redo log和binlog刷盘。
面试题专栏

Java面试题专栏已上线,欢迎访问。

[*]如果你不知道简历怎么写,简历项目不知道怎么包装;
[*]如果简历中有些内容你不知道该不该写上去;
[*]如果有些综合性问题你不知道怎么答;
那么可以私信我,我会尽我所能帮助你。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: Mysql篇-三大日志