MySQL- 基础概念篇 - 日志
MySQL- 基础概念篇 - 日志
只要我们写的是 DML 语句(insert,update,delete,create)等等,那么我们在数据库服务端执行的时候就会涉及到 redo log(重做日志) 和 binlog(归档日志) 两个日志文件的变动。
redo log
MySQL 里如果每一次的更新操作都需要写进磁盘,然后磁盘也要找到对应的那条记录,然后再更新,整个过程 IO 成本、查找成本都很高。为了解决这个问题,MySQL 的设计者就用了类似酒店掌柜粉板的思路来提升更新效率。而粉板和账本配合的整个过程,其实就是 MySQL 里经常说到的 WAL 技术,WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志(这个也是写磁盘,只不过是顺序写入,很快),再写磁盘,也就是先写粉板,等不忙的时候再写账本。
为什么叫 redolog 呢,就是,把之前的修改保存下来,然后在合适的时候再做一次,
具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log(粉板)里面(严格来说,这里应该分为两步,先写在 redolog buffer 这个内存中,再写到 redolog 磁盘文件中 ,具体请看 MySQL-基础概念篇-1-日志#redolog buffer ),并更新内存(脏页),这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做,这就像打烊以后掌柜做的事。如果今天赊账的不多,掌柜可以等打烊后再整理。但如果某天赊账的特别多,粉板写满了,又怎么办呢?这个时候掌柜只好放下手中的活儿,把粉板中的一部分赊账记录更新到账本中,然后把这些记录从粉板上擦掉,为记新账腾出空间。
InnoDB 的 redo log 是固定大小的,比如可以配置为一组 4 个文件,每个文件的大小是 1GB,那么这块“粉板”总共就可以记录 4GB 的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
write pos 是当前记录的位置,一边写一边顺时针移动,直到回到原点。
checkpoint 是当前要擦除的位置,一开始跟 write pos 同时都在起点,也是顺时针运动,擦除记录前要把记录更新到数据文件。
write pos 和 checkpoint 之间的是“粉板”上还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示“粉板”满了,这时候不能再执行新的更新,得停下来先擦掉一些记录,把 checkpoint 推进一下。
有了 redo log,InnoDB 就可以保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为 crash-safe。
什么是 crash-safe
比如,先写了 redo log,然后又写了 binlog,刚刚要提交事务的时候,断电了,或者数据库挂了,然后我们重启了数据库,此时可以通过 redo log 将之前因为断电未提交的数据写入到数据所在的磁盘中。看起来好像是事务提交了一样,这就是 crash-safe 的牛逼之处。,
redolog buffer
参考博客:https://cloud.tencent.com/developer/article/1922672
redo log 包括两部分:一个是内存中的日志缓冲(redolog buffer),另一个是磁盘上的日志文件(redo log file)。MySQL 每执行一条 DML 语句,先将记录写入 redo log buffer,后续某个时间点(比如事务提交的时候)再一次性将多个操作记录写到 redo log file。这种先写日志,再写磁盘的技术就是 MySQL 里经常说到的 WAL(Write-Ahead Logging)技术。
这里再详细讨论一下 redolog buffer 同步到 redolog file 的时机,
这个时机并不只有事务提交的时候,事务不提交也是可能会写到 redolog file 的。因为 redolog buffer 是所有线程共用的,别的线程提交照样会把所有 redolog buffer 中的数据写入 redo log file。redo log file 不用保证事务的顺序性,而 binlog 要保证事务的顺序性,所以大事务提交是 binlog 写会阻塞后续的所有提交,但 redo log file 写不会阻塞其他事务写。
还有一种情况是内存不够用了的时候,试想如果一个大事务牵涉到几万行修改后才提交,redo log buffer 放得下吗?所以,redolog buffer 主要作用是暂存和批量写入磁盘,不用想的太复杂,从名字也看的出来,buffer 嘛。
这个问题我们会在后面第 22 篇文章《MySQL 有哪些“饮鸩止渴”的提高性能的方法?》中再详细展开)。
redo log 文件
WAL 技术中的,先将记录写入到日志里, 当空闲时, 再将记录写入磁盘。 中的日志,也就是 redo log 日志,其实也是磁盘文件,
为什么写 redo log 就不慢呢?首先, 根据索引去磁盘查数据是随机 io,而写入 redo log 是顺序 io 。
每次更新数据的时候直接读写磁盘,此时的 io 是随机 io,即,随机读和随机写,这个随机读写是相对连续读写而言的,连续读写是指在磁盘上连续的地址上读写,而随机读写还需要先移动磁头到指定位置(磁道寻址),因此多了一个耗时操作,因此效率不如顺序读写。
因为随机 io 的效率是很低的,redolog 避免了每次更新都要通过磁盘随机 IO 定位到记录位置,而是将改动以顺序 IO 写到 redo log。
之后我们可以根据磁盘中的 redolog 文件,使用组提交来批量更新到数据所在的磁盘。
类比,其实很多组件都是这么做的。 比如 Redis 的 Pipeline。以减少网络 IO。批量请求。
这里可以把 MySQL 和 Redis 做一下类比。 MySQL 的 redo log 是 WAL,先写日志(redolog 也在磁盘,不过是顺序 IO),后再找机会把数据写回到数据磁盘。 Redis 是 AOF,因为他是内存数据库,所以他先做存储(直接更新写入内存),然后再持久化到 AOF 日志文件。 这两者本质上都是想先以性能消耗最低的方式完成记录操作,MySQL 是把随机 IO 优化成顺序 IO 优先记录;Redis 基于内存数据库先天优势,直接写入内存,后面再持久化到 AOF。
redo log 的磁盘空间大小是固定的,而且是循环写的(因为本质上 redolog 是一种缓存日志),空间固定就会用完,用完之后如果还需要记录则只能阻塞写入过程,先把空间里的日志刷到数据磁盘再写;(参考 MySQL-实践篇-4-redo日志的flush操作+还未动手实践)binlog 是可以追加写入的。“追加写”是指 binlog 文件 写到一定大小后会切换到下一个, 并不会覆盖以前的日志。
redo log:属于 innodb,4 个文件共 4GB 大 小,环形,磁盘地址有序,负责事务。crash-safe 能力。
binlog:属于 mysql server 层,无大 小限制, 会无限创建 binlog 文件,负责归档恢复。
redolog 一般设置多大
现在的磁盘一般都几 TB,我们可以直接将 redo log 设置为 4 个文件、每个文件 1GB 。
innodb_log_file_size 该参数指定了每个 redo 日 志文件的大小, 在 MySQL 5.7.21 这个版本中的 默认值为 48MB,
innodb_log_files_in_group 该参数指定 redo 日志文件的个数, 默认值为 2, 最大值为 100。
redo 日志有分几十种类型的。redo 做的事情,简单讲就是记录页的变化(WAL 将页变化的乱序写转换成了顺序写)。页是分多种的,比如 B+ 树索引页(主键 / 二级索引)、undo 页(数据的多版本 MVCC)、以及后面学到的 change buffer 页等等,这些页被 redo 记录后,就可以不着急刷盘了。change buffer 记录索引页的变化;但是 change buffer 本身也是要持久化的,而它持久化的工作和其他页一样,交给了 redo 日志来帮忙完成;redo 日志记录的是 change buffer 页的变化。 change buffer 持久化文件是 ibdata1,公共表空间,索引页持久化文件是 t.ibd 每张表单独的表空间,需要专门开启才会启用这个表空间
binlog
MySQL 整体来看,其实就有两块:一块是 Server 层,它主要做的是 MySQL 功能层面的事情;还有一块是引擎层,负责存储相关的具体事宜。上面我们聊到的粉板 redo log 是 InnoDB 引擎特有的日志,而 Server 层也有自己的日志,称为 binlog(归档日志)。
注意,Server 层的日志是 binlog,引擎层的日志是 redo log,redo log 比 binlog 更加底层
这两种日志有以下三点不同。
- redo log 是 InnoDB 引擎特有的;别的引擎没有。binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
- redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;是一种很底层的日志。binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如 直接把 SQL 语句记下来 “给 ID=2 这一行的 c 字段加 1 ”。
- redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
一个数据库引擎,确实不应该占用太多的存储空间,引擎应当是轻量的,高效的。
binlog 的类型
binlog 有三种模式:statement、row、mix
- statement 格式的话是记 sql 语句,
- row 格式会记录行的内容,记两条,更新前和更新后都有。
- mixed 是前两种的混合种,可以根据语句的不同,而自动选择适合的日志格式.
如何查看 binlog: https://zhuanlan.zhihu.com/p/33504555
- 搜索 log 名称:
show variables like '%log_bin%'
; - 查看 log 内容:
show binlog events in 'binlog.000009'
或 使用命令行工具:mysqlbinlog binlog.000009
undo log
用于读取历史版本的数据
在基础概念事务篇提到过。 MySQL-基础概念篇-2-事务
两阶段提交
mysql> update T set c=c+1 where ID=2;
执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存(buffer_pool MySQL-基础概念篇-1-日志#update 的详细过程)中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。引擎将这行新数据更新到内存中,同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。执行器生成这个操作的 binlog,并把 binlog 写入磁盘。执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交(commit)状态,更新完成。
redolog 先是创建了一次,然后修改了一次,改了两次,这就叫两阶段提交。
两阶段提交就是让这两个日志的状态保持逻辑上的一致。
redolog 和 binlog 都有各自的用处,所以我们设计出两阶段提交,保证两种日志的数据一致性,只要两种日志的数据是一致的,通过 binlog 来实现的功能比如数据的主从同步就不会出问题
事务提交成功才会有一条日志,不成功没有日志,所以一个事务只会有一条 binlog 日志。
update 的详细过程
update 语句操作的详细执行过程如下:先把数据查询到 buffer_pool(非唯一索引可以不用先查询,此时就可以使用 change buffer,即先把要改动的操作存在 change_buffer 中,然后等空闲的时候或者其他操作再 merge,参考 MySQL-实践篇-1-普通索引和唯一索引+还未动手实践),查询出来之后,这一页数据就在 buffer_pool 中,MySQL 会先把 buffer_pool 里面的这页数据更新到最新同时写 redo log(这里应该指的是写到 redolog buffer,后续会在特定的时间比如事务提交的时候写到 redolog file 中,写到 redolog file 是顺序 IO),然后就更新结束了,此时在 buffer_pool 里面的页就是脏页,后续会刷新到磁盘中。
buffer_pool 的数据刷到磁盘中我们后续会学习,参考 MySQL-实践篇-4-redo日志的flush操作+还未动手实践
详细描述一下两阶段提交中发生异常之后的恢复过程
如果在也写入 redo log 处于 prepare 阶段之后、写 binlog 之前,发生了崩溃(crash),由于此时 binlog 还没写,redo log 也还没提交,所以崩溃恢复的时候,这个事务会回滚。这时候,binlog 还没写,所以也不会传到备库。到这里,大家都可以理解。
大家出现问题的地方,主要集中在 binlog 写完,redo log 还没 commit 前发生 crash,那崩溃恢复的时候 MySQL 会怎么处理?
我们先来看一下崩溃恢复时的判断规则。
1.如果 redo log 里面的事务是完整的,也就是已经有了 commit 标识,则直接提交;
2.如果 redo log 里面的事务只有完整的 prepare,则判断对应的事务 binlog 是否存在并完整:
a. 如果是,则提交事务;
b. 否则,回滚事务。
这里提到了一个问题,就是 binlog 的完整性,一个事务的 binlog 是有完整格式的
binlog 有两种格式:statement 和 row
statement 格式的 binlog,最后会有 COMMIT;
row 格式的 binlog,最后会有一个 XID event。
(这两种格式都有 xid,与 redolog 相关联)
也可以通过 binlog checksum 判断完整 性。
redo log 和 binlog 都有一个共同的字段 xid,当 系统崩溃恢复时,顺序扫描 redo log,如果 redo log 有 commit 标记,直接提交事务,如果 redo log 只有 prepare 标记,那就用 xid 去 binlog 中查,有 查到就提交, 没有查到就回滚。
对于 InnoDB 引擎来说,如果 redo log 提交完成了,事务就不能回滚(如果这还允许回滚,就可能覆盖掉别的事务的更新)。而如果 redo log 直接提交,然后 binlog 写入的时候失败,InnoDB 又回滚不了,数据和 binlog 日志又不一致了。所以
MySQL 提交的时候的两阶段提交,实际上是给 redolog 一个回滚的机会,只有 binlog 提交成功了,才提交 redolog,否则就会回滚
这是引入了 innodb 才导致的复杂度
设计的灵活性增加了复杂性。如果不是把引擎部分拆分出去,作为插件引入,是不需要这么多的日志的,自然也就不需要保证这么多日志的一致性,但同时不能灵活的支持这么多种引擎了。
为什么需要两阶段提交
其实,并不仅仅是为了提升 IO 性能才设计的 WAL。如果仅仅是为了提升性能,那为了 WAL 所采取的一系列措施也太得不偿失了。 WAL 的出现是为了实现关系型数据库的原子性和持久性。(即 crash safe)
实现原子性和持久性的最大困难是“将内存中的数据写入磁盘”这个操作并不是原子性的,不仅有“写入”与“未写入”状态,还客观存在“正在写”的中间状态。 由于“正在写”的中间状态与数据库崩溃都不可能消除,所以如果不做额外保障的话,将内存中的数据写入磁盘,并不能保证原子性与持久性。(即 crash safe)
所以可能出现以下情形:
1:未提交事务时,写入后崩溃(比如修改三个数据,程序还没修改完,但数据库已经将其中一个或两个数据的变动写入磁盘,此时出现崩溃)
2:已提交事务时,写入前崩溃(程序已经修改完三个数据,但数据库还未将全部三个数据的变动都写入磁盘,此时出现崩溃)
由于写入中间状态与崩溃都是无法避免的,为了保证原子性和持久性,只能在崩溃恢复后采取补救措施,这种能力就被称为“崩溃恢复”(即 crash safe)。 为了能够实现崩溃恢复,采取了写日志的方式,写日志成功后再去写磁盘(其实就是在中间加了一层的那种思想),这种事务实现方式被称为“提交日志(CommitLogging),目前阿里的 OceanBase 就采用这种方式,但是 Commit Logging 存在一个巨大缺陷:所有对数据的真实修改都必须发生在事务提交之后,即成功写入日志之后。在此之前,即使磁盘 IO 有足够的空闲,即使某个事务修改的数据量非常庞大,占用了大量的内存缓冲区,都不允许在事务提交前写入磁盘,因此这种方式对数据库性能的提升十分不利。 基于 Commit Logging 的问题就,提出了“提前写入日志”(Write-Ahead Logging)的日志改进方案,“提前写入”就是允许在事务提交之前写入变动数据的意思。而对于提前写入磁盘,在数据库崩溃后需要回滚的数据,给出的解决办法是增加另外一种被称为 Undo Log 的日志类型,当变动数据写入磁盘前,必须先记录 Undo Log,以便在事务回滚或者崩溃恢复时根据 Undo Log 对提前写入的数据变动进行擦除。
(在事务提交之前,redo log 都只存在于 redo log buffer 这块指定的内存区域中。注意是内存区域,还没写入到 redolog 的对应的磁盘文件中。
写入到 redolog 的对应的磁盘文件中的时机是在事务提交的 commit 阶段,所以我还是没明白这个在事务提交之前写入变动数据是啥意思,TODO)
最终,对于以上两种场景,MySQL 通过两阶段提交(binlog 和 redo log 的配合)解决
为什么需要两份日志才能 crash-safe
redolog 的功能就是记录同步到数据所在磁盘的进度,binlog 是记录所有数据记录的,crash 重放的时候根据 redo log 找到没有同步到数据所在磁盘的数据段,然后根据 binlog 判断哪些是事务提交完毕需要恢复的,如果只有 binlog,是无法判断当前哪部分 redo log 数据还没有被同步到磁盘上的,比如有 2 个事务 transaction0 和 transaction1 都提交了,有两条 redolog,而且都还没有同步到数据所在磁盘,此时也有两条 binlog,但是只看 binlog,是不知道这两个事务的提交有没有同步到数据所在磁盘的,
简而言之,在崩溃之后恢复的时候会检查 redolog,如果只有 binlog,就是没法记录哪些更改已经刷盘了,哪些更改还没有刷盘,
这里有一点需要注意,就是事务提交的时候,只是写入了 redolog 和 binlog,后续会在某个时刻将 redolog 中的修改同步到数据所在的磁盘,也就是解耦了事务提交和将修改写入数据所在的磁盘这两个操作,
内存的数据有没有落盘主要看 redolog 有没有日志数据,因为它一旦完成了实际的同步操作,会移动 check point 指针,这样就能标识出日志中哪些是已经实际做完同步操作的,哪些是还没做完同步操作的。只要 redolog 日志数据没有被 checkpoint 擦完,就代表还有数据在内存里,所以只要崩溃恢复了,就会去检查 redolog 的 checkpoint 和 write pos 之间的日志,即检查 redolog 中的同步进度,继续将状态为 commit 的(或者 binlog 已经写入了)的 redolog 写入数据所在的磁盘,类似于断点续传,这是很细力度的进度控制。 但是如果只有 binlog,那么控制的颗度就会大打折扣,最终导致恢复的失败。
比如有两个事务要提交 transaction0 和 transaction1,transaction0 提交了,写入了 redolog,但是 redolog 还没同步到数据所在的磁盘,然后 transaction1,提交,但是在写入 binlog 的时候断电,即 binlog 写入失败。此时 transaction0 的 redolog 依然没有同步到数据所在的磁盘
此时如果根据 redolog 来恢复,会挨个检查 redolog,判断状态是否为 commit,如果是就同步到数据所在的磁盘,如果是 prepare,就检查对应的 binlog。如果 binlog 是完整的,则提交到数据所在的磁盘,如果不是完整的,就放弃同步,此时 transaction0 的提交是不会丢失的,
但是如果只通过 binlog 来恢复,此时 transaction0 的 binlog 已经提交,不会再重试,但是实际上,transaction0 对应的 redolog 还没有同步到数据所在的磁盘,也就是 transaction0 的提交丢失了。
本质上还是因为 binlog 是追加写的无法确定从哪个点开始的数据是已经刷盘了,而 redolog 只要在 checkpoint 后面的肯定是没有刷盘的,所以只需要重放一遍即可,如果 binlog 也能准确知道哪个点开始数据没有刷盘,那么也可以像 redolog 一样重放一遍即可,并不存在所说的 transaction0 数据会丢。
redolog 本质上是一种缓存,所以只给固定大小的存储空间,并实时减少(刷盘)
只要一个日志可以吗?
只用 redo log,不要 binlog 可以吗?
回答:如果只从崩溃恢复的角度来讲是可以的。你可以把 binlog 关掉,这样就没有两阶段提交了,但系统依然是 crash-safe 的。
但是一般我们不这么干,因为 binlog 有着 redo log 无法替代的功能。
1、数据归档 ,结合全库备份和 binlog,我们可以将数据库恢复到任意指定时刻,这是 redolog 做不到的,redo log 是循环写,写到末尾是要回到开头继续写的。这样历史日志没法保留,redo log 也就起不到归档的作用。
2、主从复制 ,高可用的基础,主从数据库通过 binlog 同步数据
3.很多其他的系统依赖 MySQL 的 binlog 功能,比如阿里的 canal
其实本质上来说他们的概念就不一样,作用也不一样
本质上是因为两者记录的东西不同,
binlog 记录对数据的更改
redolog 记录这些更新操作同步到磁盘的进度。
附 :
重启时直接会使用 redo 刷盘机制,检验一下它的状态,如果 commit,但是还没刷盘就就将它刷入数据库磁盘,如果 prepare 则检验 binlog 是否写入,如果写入则将它标记成 commit 并同上操作,如果 binlog 没有写入,直接放弃不做操作。因为一开始更新是只更新内存和同步到 redolog(redolog 在磁盘上,所以不怕断电和内存崩掉),数据页还没同步到数据库磁盘,而且 commit 后也不一定会同步,得看设置的同步原理,何时进行刷脏。 然后如果使用 binlog 不管恢复还是从库同步恢复直接用就行了,前面借助 redolog 已经校准了数据,和 binlog 是对得上的。
不考虑 mysql 现有的实现,假如现在重新设计 mysql,只用一个 binlog 是否可以实现 cash_safe 能力呢?答案是可以的,只不过 binlog 中也要加入 checkpoint,数据库故障重启后,binlog checkpoint 之后的 sql 都重放一遍。但是这样做让 binlog 耦合的功能太多,而且 binlog 毕竟是服务端日志不是存储引擎的日志,redolog 是存储引擎的日志,可以实现对数据页的记录,而 binlog 则只能记录 SQL 或者修改前后的数据,用 binlog 进行存储引擎的“重做”,可能会产生重复操作。
1.既然 redo log 自己就能 crash safe,那完全可以不需要 binlog,binlog 有什么用呢?
解答:如果你是在单机 MySQL 下,确实没问题,甚至你都可以把 binlog 关掉,MySQL 是支持 binlog 关闭的。但如果是主从的 MySQL,我们知道主从间数据的同步通过 binlog,主服务器通过发 binlog 给从服务器来保证数据的同步(为什么不用 redo log?因为 redo log 是 InnoDB 自己的,并非所有存储引擎都有 redo log,但都会有 binlog)。因此多机下就就需要 binlog,且如果我们的 binlog 与 redo log 不同步,比如即使 binlog 不存在也会同步更新到磁盘,那么由于 binlog 不存在,也即从服务器没有收到 binlog,从服务器是没有这条更新的,但主服务器 redo log 是存在的,如果根据 redo log 恢复,此时 redo log 与 binlog 不一致,那么会造成主从数据的不一致性。
- 既然 binlog 与 redo log 是一致的,那恢复的时候直接用 binlog 不就可以了,为什么用 redo log 来恢复?
因为实际干活的人是存储引擎,到底这个数据有没有同步到磁盘(后续将 redo log 与磁盘同步我们统一称为刷盘),binlog 是不知道的。拿上述两阶段提交举例子:先是记录 redo 状态 prepare,然后记录 binlog,再是将 redo 改为 commit。如果以 binlog 作为恢复的判断依据,那如果在记录完 binlog 但未将 redo 改为 commit 时发生 crash,此时表面上看这条记录已经执行成功了,但实际根本还没刷到磁盘的,执行成功是存储引擎告诉它的成功,到底是不是真的成功只有存储引擎自己知道(譬如 InnoDB 并不会真的修改磁盘,只是记录下日志就返回成功了),因此 binlog 不能作为数据恢复的依据。
两阶段提交对我们的设计的启发
其实把 Mysql 的两阶段提交也可以看成两个分布式服务处理两个不同事情,redo log 在 Innodb 引擎内操作的,binlog 是在 server 层操作的,
我们就可以把引擎层和 server 层看成两个分布式服务,那他们要分别进行两个相关联的操作,就意味着要实现分布式事务,而两阶段提交,就是其中的一种解决方案。
经过抽象就是,ab 两个分布式服务要进行事务操作,a 先 prepare 提交,提交之后通知 b 进行提交,b 成功提交之后再通知 a 进行 commit 提交,如果 a commit 提交成功,则表示事务完整提交,否则如果 a 只有 prepare 提交,那就去 b 查询是否成功提交,如果成功提交,a 自己再补充一个 commit 提交,表示事务提交成功,如果 b 查询结果是没有成功提交,则表示事务提交失败,a 的 prepare 提交需要回滚。
注意,上面这种两阶段提交的方式,跟 分布式事务八股文 中的两阶段提交完全是两个东西
其次 MySQL 的主从同步也是一个分布式系统的同步方式。当多台机器上的某个业务需要同步时,首先我们要约定同步的内容(比如 binlog),然后保证,所有接受了同步内容的机器,其业务的状态都与同步的内容一致。
日志的写入磁盘的时机
通常来讲,在事务提交之前,redo log 都只存在于 redo log buffer 这块指定的内存区域中。注意是内存区域,还没写入到 redolog 的对应的磁盘文件中。
写入到 redolog 的对应的磁盘文件中的时机是在事务提交的 commit 阶段或者其他时机比如别的事务提交的时候或者 redolog buffer 满了,刚写入的时候的 redolog file 的时候,日志的状态都是 prepare,等当前事务提交之后,binlog 写入成功,再改成 committed 状态(两阶段提交)
至于将 redolog 中的修改同步到数据所在的磁盘,则是另一回事儿了,这部分操作,请看 MySQL-实践篇-4-redo日志的flush操作+还未动手实践
在当前,我们只看事务提交阶段,对 redolog 磁盘文件的写入
这里我们首先要明确两个概念和两个参数:
write:刷盘
fsync:持久化到磁盘
write(刷盘) 指的是 MySQL 从 buffer pool 中将内容写到系统的 page cache 中,并没有持久化到系统磁盘上。这个速度其实是很快的。
fsync 指的是从系统的 cache 中将数据持久化到系统磁盘上。这个速度可以认为比较慢,而且也是 IOPS 升高的真正原因。
MySQL 支持三种将 redo log buffer 写入 redo log file 的时机,可以通过 innodb_flush_log_at_trx_commit 参数配置,各参数值含义如下:
参数:
innodb_flush_logs_at_trx_commit(该参数针对 redo log)
取值 0:每次提交事务都只把 redo log 留在 redo log buffer 中,即内存中
取值 1:每次提交事务都将 redo log 持久化到磁盘上,也就是 write+fsync
取值 2:每次都把 redo log 写到系统的 page cache 中,也就是只 write,不 fsync
sync_binlog(该参数针对 binlog)
取值 0:每次提交都将 binlog 从 binlog cache 中 write 到磁盘上,而不 fsync 到磁盘
取值 1:每次提交事务都将 binlog fsync 到磁盘上
取值 N:每次提交事务都将 binlog write 到磁盘上,累计 N 个事务之后,执行 fsync
更详细的分析请看下面的文章:
https://cloud.tencent.com/developer/article/1647930
innodb_flush_log_at_trx_commit 这个参数设置成 1 的时候,表示每次事务的 redo log buffer 都直接持久化到磁盘。这个参数我建议你设置成 1,这样可以保证 MySQL 异常重启之后数据不丢失。
sync_binlog 这个参数设置成 1 的时候,表示每次事务的 binlog 都持久化到磁盘。这个参数我也建议你设置成 1,这样可以保证 MySQL 异常重启之后 binlog 不丢失。
redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
redo log 是物理日志,记录的是“在某个数据页上做了什么修改”(注意,记录的是做了什么修改,而不是 " 这个数据修改后最新值为什么 ");binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
binlog 记录的是 SQL 语句的含义,要做什么,是逻辑概念
redolog 记录的是 SQL 语句对应的对数据的操作,是物理概念
其他用户的解释:
SQL 是面向用户的语义化命令,你可以理解为高级编程语言。高级编程语言最终会被执行去完成磁盘上数据的操作。我理解 redo log 记录的是磁盘上数据的物理变化,binlog 记录的是当时所执行的高级编程语言。
redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
一些常见问题
怎样让数据库恢复到半个月内任意一秒的状态?
通过定期的整库备份加上 binlog 的操作重新执行语句可以保证数据的安全性。因为 binlog 记录的是数据的逻辑操作(原始 sql 语句),而 redolog 是数据的物理操作日志,并且非 innodb 的引擎没有 redolog。因此重做的时候会使用 binlog 进行回放
当需要恢复到指定的某一秒时,比如某天下午两点发现中午十二点有一次误删表,需要找回数据,那你可以这么做:
首先,找到最近的一次全量备份,如果你运气好,可能就是昨天晚上的一个备份,从这个备份恢复到临时库;
然后,从备份的时间点开始,将备份的 binlog 依次取出来,重放到中午误删表之前的那个时刻。
这样你的临时库就跟误删之前的线上库一样了,然后你可以把表数据从临时库取出来,按需要恢复到线上库去。