55 - MySQL
66---
77
8-
9-
108> 本文来自公号程序猿阿星投稿,JavaGuide 对其做了补充完善。
119
1210## 前言
1311
1412` MySQL ` 日志 主要包括错误日志、查询日志、慢查询日志、事务日志、二进制日志几大类。其中,比较重要的还要属二进制日志 ` binlog ` (归档日志)和事务日志 ` redo log ` (重做日志)和 ` undo log ` (回滚日志)。
1513
16- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /01.png )
14+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /01.png )
1715
1816今天就来聊聊 ` redo log ` (重做日志)、` binlog ` (归档日志)、两阶段提交、` undo log ` (回滚日志)。
1917
2321
2422比如 ` MySQL ` 实例挂了或宕机了,重启时,` InnoDB ` 存储引擎会使用` redo log ` 恢复数据,保证数据的持久性与完整性。
2523
26- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /02.png )
24+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /02.png )
2725
2826` MySQL ` 中数据是以页为单位,你查询一条记录,会从硬盘把一页的数据加载出来,加载出来的数据叫数据页,会放入到 ` Buffer Pool ` 中。
2927
3331
3432然后会把“在某个数据页上做了什么修改”记录到重做日志缓存(` redo log buffer ` )里,接着刷盘到 ` redo log ` 文件里。
3533
36- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /03.png )
34+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /03.png )
3735
38- > 图片笔误提示:第4步 “清空 redo log buffe 刷盘到 redo 日志中”这句话中的 buffe 应该是 buffer。
36+ > 图片笔误提示:第 4 步 “清空 redo log buffe 刷盘到 redo 日志中”这句话中的 buffe 应该是 buffer。
3937
4038理想情况,事务一提交就会进行刷盘操作,但实际上,刷盘的时机是根据策略来进行的。
4139
5351
5452另外,` InnoDB ` 存储引擎有一个后台线程,每隔` 1 ` 秒,就会把 ` redo log buffer ` 中的内容写到文件系统缓存(` page cache ` ),然后调用 ` fsync ` 刷盘。
5553
56- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /04.png )
54+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /04.png )
5755
5856也就是说,一个没有提交事务的 ` redo log ` 记录,也可能会刷盘。
5957
6058** 为什么呢?**
6159
6260因为在事务执行过程 ` redo log ` 记录是会写入` redo log buffer ` 中,这些 ` redo log ` 记录会被后台线程刷盘。
6361
64- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /05.png )
62+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /05.png )
6563
6664除了后台线程每秒` 1 ` 次的轮询操作,还有一种情况,当 ` redo log buffer ` 占用的空间即将达到 ` innodb_log_buffer_size ` 一半的时候,后台线程会主动刷盘。
6765
6866下面是不同刷盘策略的流程图。
6967
7068#### innodb_flush_log_at_trx_commit=0
7169
72- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /06.png )
70+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /06.png )
7371
7472为` 0 ` 时,如果` MySQL ` 挂了或宕机可能会有` 1 ` 秒数据的丢失。
7573
7674#### innodb_flush_log_at_trx_commit=1
7775
78- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /07.png )
76+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /07.png )
7977
8078为` 1 ` 时, 只要事务提交成功,` redo log ` 记录就一定在硬盘里,不会有任何数据丢失。
8179
8280如果事务执行期间` MySQL ` 挂了或宕机,这部分日志丢了,但是事务并没有提交,所以日志丢了也不会有损失。
8381
8482#### innodb_flush_log_at_trx_commit=2
8583
86- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /09.png )
84+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /09.png )
8785
8886为` 2 ` 时, 只要事务提交成功,` redo log buffer ` 中的内容只写入文件系统缓存(` page cache ` )。
8987
9795
9896它采用的是环形数组形式,从头开始写,写到末尾又回到头循环写,如下图所示。
9997
100- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /10.png )
98+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /10.png )
10199
102100在个** 日志文件组** 中还有两个重要的属性,分别是 ` write pos、checkpoint `
103101
@@ -110,11 +108,11 @@ tag:
110108
111109` write pos ` 和 ` checkpoint ` 之间的还空着的部分可以用来写入新的 ` redo log ` 记录。
112110
113- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /11.png )
111+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /11.png )
114112
115113如果 ` write pos ` 追上 ` checkpoint ` ,表示** 日志文件组** 满了,这时候不能再写入新的 ` redo log ` 记录,` MySQL ` 得停下来,清空一些记录,把 ` checkpoint ` 推进一下。
116114
117- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/03 /12.png )
115+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide /12.png )
118116
119117### redo log 小结
120118
155153
156154可以说` MySQL ` 数据库的** 数据备份、主备、主主、主从** 都离不开` binlog ` ,需要依靠` binlog ` 来同步数据,保证数据一致性。
157155
158- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/04/01 .png )
156+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/01-20220305234724956 .png )
159157
160158` binlog ` 会记录所有涉及更新数据的逻辑操作,并且是顺序写。
161159
@@ -169,13 +167,13 @@ tag:
169167
170168指定` statement ` ,记录的内容是` SQL ` 语句原文,比如执行一条` update T set update_time=now() where id=1 ` ,记录的内容如下。
171169
172- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/04/02 .png )
170+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/02-20220305234738688 .png )
173171
174172同步数据时,会执行记录的` SQL ` 语句,但是有个问题,` update_time=now() ` 这里会获取当前系统时间,直接执行会导致与原库的数据不一致。
175173
176174为了解决这种问题,我们需要指定为` row ` ,记录的内容不再是简单的` SQL ` 语句了,还包含操作的具体数据,记录内容如下。
177175
178- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/04/03 .png )
176+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/03-20220305234742460 .png )
179177
180178` row ` 格式记录的内容看不到详细信息,要通过` mysqlbinlog ` 工具解析出来。
181179
199197
200198` binlog ` 日志刷盘流程如下
201199
202- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/04/04 .png )
200+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/04-20220305234747840 .png )
203201
204202- ** 上图的 write,是指把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快**
205203- ** 上图的 fsync,才是将数据持久化到磁盘的操作**
@@ -208,15 +206,15 @@ tag:
208206
209207为` 0 ` 的时候,表示每次提交事务都只` write ` ,由系统自行判断什么时候执行` fsync ` 。
210208
211- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/04/05 .png )
209+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/05-20220305234754405 .png )
212210
213211虽然性能得到提升,但是机器宕机,` page cache ` 里面的 binlog 会丢失。
214212
215- 为了安全起见,可以设置为` 1 ` ,表示每次提交事务都会执行` fsync ` ,就如同** redo log 日志刷盘流程** 一样。
213+ 为了安全起见,可以设置为` 1 ` ,表示每次提交事务都会执行` fsync ` ,就如同 ** redo log 日志刷盘流程** 一样。
216214
217215最后还有一种折中方式,可以设置为` N(N>1) ` ,表示每次提交事务都` write ` ,但累积` N ` 个事务后才` fsync ` 。
218216
219- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/04/06 .png )
217+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/06-20220305234801592 .png )
220218
221219在出现` IO ` 瓶颈的场景里,将` sync_binlog ` 设置成一个比较大的值,可以提升性能。
222220
@@ -232,33 +230,33 @@ tag:
232230
233231在执行更新语句过程,会记录` redo log ` 与` binlog ` 两块日志,以基本的事务为单位,` redo log ` 在事务执行过程中可以不断写入,而` binlog ` 只有在提交事务时才写入,所以` redo log ` 与` binlog ` 的写入时机不一样。
234232
235- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/05/01 .png )
233+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/01-20220305234816065 .png )
236234
237235回到正题,` redo log ` 与` binlog ` 两份日志之间的逻辑不一致,会出现什么问题?
238236
239237我们以` update ` 语句为例,假设` id=2 ` 的记录,字段` c ` 值是` 0 ` ,把字段` c ` 值更新成` 1 ` ,` SQL ` 语句为` update T set c=1 where id=2 ` 。
240238
241239假设执行过程中写完` redo log ` 日志后,` binlog ` 日志写期间发生了异常,会出现什么情况呢?
242240
243- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/05/02 .png )
241+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/02-20220305234828662 .png )
244242
245243由于` binlog ` 没写完就异常,这时候` binlog ` 里面没有对应的修改记录。因此,之后用` binlog ` 日志恢复数据时,就会少这一次更新,恢复出来的这一行` c ` 值是` 0 ` ,而原库因为` redo log ` 日志恢复,这一行` c ` 值是` 1 ` ,最终数据不一致。
246244
247- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/05/03 .png )
245+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/03-20220305235104445 .png )
248246
249247为了解决两份日志之间的逻辑一致问题,` InnoDB ` 存储引擎使用** 两阶段提交** 方案。
250248
251249原理很简单,将` redo log ` 的写入拆成了两个步骤` prepare ` 和` commit ` ,这就是** 两阶段提交** 。
252250
253- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/05/04 .png )
251+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/04-20220305234956774 .png )
254252
255253使用** 两阶段提交** 后,写入` binlog ` 时发生异常也不会有影响,因为` MySQL ` 根据` redo log ` 日志恢复数据时,发现` redo log ` 还处于` prepare ` 阶段,并且没有对应` binlog ` 日志,就会回滚该事务。
256254
257- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/05/05 .png )
255+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/05-20220305234937243 .png )
258256
259257再看一个场景,` redo log ` 设置` commit ` 阶段发生异常,那会不会回滚事务呢?
260258
261- ![ ] ( https://cdn.jsdelivr.net/gh/18702524676/CND5/image/mysql/05/06 .png )
259+ ![ ] ( https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/github/javaguide/06-20220305234907651 .png )
262260
263261并不会回滚事务,它会执行上图框住的逻辑,虽然` redo log ` 是处于` prepare ` 阶段,但是能通过事务` id ` 找到对应的` binlog ` 日志,所以` MySQL ` 认为是完整的,就会提交事务恢复数据。
264262
0 commit comments