本文为极客时间《Mysql实战45讲》的学习笔记,并结合《高性能Mysql》,梳理了索引相关的知识点,总结了一些常见问题,并记录了一些比较实用的方法。
三种常见的日志类型
RedoLog 重做日志
RedoLog是重要日志,是InnoDB用来做事务持久化的日志。他主要记录了事务在某个数据页上具体做了什么。它可以实现事务的crash-safe
结构
- Write pos 是当前记录的位置,一边写一边后移,写到ib_logfile_3 号文件末尾后就回到 ib_logfile_0 号文件开头。
- checkpoint 是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
- write pos 和 checkpoint 之间的是还空着的部分,可以用来记录新的操作。如果 write pos 追上 checkpoint,表示日志满了,这时候不能再执行新的更新,得停下来先擦掉一些记录(刷脏页),把 checkpoint推进一下。
他的大致结构:
代码语言:txt复制show engine innodb statusG
---
LOG
---
Log sequence number 17680223 #代表系统中的lsn值,也就是当前系统已经写入的redo日志量
Log buffer assigned up to 17680223
Log buffer completed up to 17680223
Log written up to 17680223
Log flushed up to 17680223 #代表flushed_to_disk_lsn的值,也就是当前系统已经写入磁盘的redo日志量
Added dirty pages up to 17680223
Pages flushed up to 17680223 #代表flush链表中被最早修改的那个页面对应的oldest_modification属性值
Last checkpoint at 17680223 #checkpoint 的值
16 log i/o's done, 0.00 log i/o's/second
RedoLog Buffer 与写入机制
RedoLog Buffer的block由三部分组成:header(12字节),body(496字节),trailer(字节)。多个block组成了RedoLog Buffer。他的大小可以由innodb_log_buffer_size的大小来控制,默认时16M。
正常情况下,一个事务产生的RedoLog要先写入到RedoLog Buffer中,直到事务COMMIT才会被写入到磁盘中。多个事务产生的RedoLog也不是顺序存储的,而是随机存储的。他刷盘的策略有:
- 如果此时的RedoLog已经占用了RedoLog Buffer一半以上的内存,就要进行刷盘。
- 事务提交时,会把相关的记录刷盘
- 后台线程会定时刷盘
- 正常关闭服务时会刷盘
一些注意事项
关于RedoLog的一些配置:
- 可以通过参数innodb_log_files_in_group来改变文件个数,这个参数的默认值是2;
- 可以通过参数innodb_log_file_size来指定每个redo日志的大小;
- 可以通过参数innodb_log_group_home_dir指定redo日志所在的目录。
RedoLog并没有参与到正常流程下的数据落盘的过程。这个问题涉及到RedoLog究竟存了些什么。
实际上,数据最终落库是以一页数据为单位进行写入磁盘的,RedoLog本身并没有记录一页完整的数据,而是记录了一页上面的数据都发生了什么变化。因此不能实现写入磁盘的功能。
- 正常情况下的脏页刷盘和RedoLog没有任何关系
- 崩溃恢复的时候,先把数据页读取到内存,然后通过RedoLog恢复页面发生的操作。此时页面变回脏页,再次落盘时和第一种情况一样。
innodb_flush_logs_at_trx_commit 的作用
- 0,每次提交的事务只将RedoLog存储在RedoLog Buffer中,一秒后再持久化。性能很高,异常时可能会丢失1s 的日志,
- 1,每次提交的事务都持久化到磁盘。write fsync 最常用。性能不高,但是安全,理论上不存在丢数据的可能。
- 2,每次都把RedoLog写到pagecache中,不fsync也就是说,把持久化交给了操作系统。Mysql服务崩溃了不会有影响。操作系统崩了,可能要丢数据了。
BinLog 重要日志
BinLog 是服务层的日志,相对而言实现起来比较简单,他不能提供crash-safe的功能。、
运行原理
BinLog 的写入逻辑比较简单:事务执行过程中,先把日志写到 binlog cache,事务提交的时候,再把 binlog cache 写到 BinLog文件中。
一个事务的 BinLog是不能被拆开的,因此不论这个事务多大,也要确保一次性写入。
这就涉及到了 binlog cache 的保存问题:
- 系统给binlog cache 分配了一片内存,每个线程一个,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。
- 事务提交的时候,执行器把 binlog cache 里的完整事务写入到 BinLog 中,并清空 binlog cache。
注意事项
参数 sync_binlog 控制的:
- sync_binlog=0 的时候,表示每次提交事务都只 write,不 fsync;
- sync_binlog=1 的时候,表示每次提交事务都会执行 fsync;
- sync_binlog=N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。
BinLog 落进文件分成两步:
- 把日志写入到文件系统的 page cache,并没有把数据持久化到磁盘,所以速度比较快。
- fsync将数据持久化到磁盘。一般情况下,我们认为 fsync 才占磁盘的 IOPS。
UndoLog
回滚日志也是InnoDB实现的,目的是为了进行事务回滚,只要用在MVCC的时候。在数据修改的流程中,会记录一条与当前操作相反的逻辑日志到UndoLog中(可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录)。
日志写入用到的技术方案
WAL 机制
WAL 介绍
WAL 的全称是 Write-Ahead Logging,它的关键点就是先写日志,再写磁盘。具体来说,当有一条记录需要更新的时候,InnoDB 引擎就会先把记录写到 redo log里面,并更新内存,这个时候更新就算完成了。同时,InnoDB 引擎会在适当的时候,将这个操作记录更新到磁盘里面,而这个更新往往是在系统比较空闲的时候做。
如何理解Mysql中的WAL技术
关键点:将写入拆分为多步,先写日志,空闲时间再写磁盘,提高写效率。
代码语言:txt复制update user set a = 1 where b =2;
以一条更新语句为例,修改并不会直接写入这行数据所在的数据库文件(在硬盘中)。而是先写入到redolog日志。会有其他线程把redolog 的日志写入到硬盘中。当我们执行完这条语句,此时对于这条数据的修改,没有直接刷进其所在的数据页,他现在存在两个地方:change buffer (内存)和redo_log(磁盘) 中。当我们下一次查询的时候会先读change buffer 中的变动,对数据做merge。如果服务挂了,内存没有了,服务再次启动时,会先把redo_log 中的变动更新到数据库文件中,此时数据依然是修改后的状态。这个过程叫做:crash-safe。
那么为什么不直接写入到数据库文件呢?
第一,为了提高写入磁盘的效率;第二,提高并发。
数据库最大的性能瓶颈在于磁盘IO效率。充分利用磁盘IO,提高效率基本上有三个方法:
- 随机读写改顺序读写
- 缓冲单条读写改批量读写
- 单线程读写改并发读写 WAL本质上就是这三个优化的集中体现。这个设计思路在我们日常工作中也非常有用。尤其是需要高并发写入的同时又要保证数据一致的时候。
WAL的优点
- 读和写可以并发执行,不会阻塞。
- WAL 可以降低写磁盘的成本,在保证数据一致性的同时,提高效率。
- 有效减少了磁盘的随机IO,减少了fsync的次数
两阶段提交
两阶段提交是分布式系统中比较常见的一种事务提交算法。在Innodb的日志提交时用到了这个协议,用来保证事务提交时,redolog和binlog 都处于完成状态。具体而言:
- 操作完更新语句,把数据保存到内存
- 写入RedoLog,处于prepare阶段
- 写入BonLog
- 提交事务,BinLog和RedoLog都处于完成状态。
这里可以仔细推敲下,异常无论发生在1和2 之间还是其他之间,都可以保证下次恢复现场时数据的完整性。因为只有BinLog和RedoLog都存在的事务操作记录才是正确的。
两阶段提交我们在自己的服务开发中也有可能会使用到,这里还有维基-三阶段提交可以参考下
常见问题
BinLog与RedoLog 有哪些区别?
首先要先说明白,他们两个在目前InnoDB中是缺一不可的。少了哪一个都会出现问题。其次,他们之间至少有三点有很大的区别:
- RedoLog 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
- RedoLog 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
- RedoLog 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。
最后,简单说下:如果没有了RedoLog,数据就无法做到crash-safe,因为 binlog不具备保存事务的性质,最重要的是它并不具备记录数据页上究竟发生了什么。如果没有 binlog的话,RedoLog是个循环写入的,不存在归档这个功能。
RedoLog如何保证 crash-safe的?
我们先说下,如果没有RedoLog的话,数据怎么就丢了。假设,我们执行完一个事务,更新了一条数据。根据之前的笔记,这时候这条数据所在的数据页 一定保存在内存中,且是一个脏页,并没有被回写到磁盘里。此时的数据是不安全的,假如Mysql异常挂掉了,内存中的数据就没了。这个时候我们的更新就会丢掉。
加上RedoLog以后,我们执行完事务,此时就直接把操作记录保存在RedoLog中,并且已经被保存进磁盘了。事务提交后,RedoLog也就处在完成状态了。这个时候的状态是:虽然我们改过的数据还在内存中,没有刷盘,但是我们操作记录已经写进日志并持久化了。当出现异常后,虽然内存没了,但是服务启动后可以根据RedoLog恢复现场,这样就不会出现数据丢失了。
什么时候会刷脏页?
- RedoLog写满时,需要暂停更新操作。此时会停止所有的写入操作!
- 机器的物理内存满了的时候
- Mysql处于空闲状态时
- Mysql重启时
RedoLog和BinLog是如何配合工作的?
它们有一个共同的数据字段,叫 XID。崩溃恢复的时候,会按顺序扫描 RedoLog:
- 如果碰到既有 prepare、又有 commit 的 RedoLog,就直接提交;
- 如果碰到只有 prepare、而没有 commit 的RedoLog,就拿着 XID 去BinLog 找对应的事务。
RedoLog和BinLog常用的配置说明
- N=1,1 适合数据安全性要求非常高,而且磁盘IO写能力足够支持业务,比如充值消费系统;
- N=1,0 适合数据安全性要求高,磁盘IO写能力支持业务不富余,允许备库落后或无复制;
- N=2,0或2,m(0<m<100) 适合数据安全性有要求,允许丢失一点事务日志,复制架构的延迟也能接受;
- N=0,0 磁盘IO写能力有限,无复制或允许复制延迟稍微长点能接受,例如:日志性登记业务;
当两个参数设置为双1的时候,写入性能最差,sync_binlog=N (N>1 ) innodb_flush_log_at_trx_commit=2 时,MySQL的写操作才能达到最高性能。
一些参考
MySQL :: MySQL 8.0 Reference Manual :: 15.6.5 Redo Log
Mysql中的Redo Log解析(一) - 云 社区 - 腾讯云
Mysql中的Redo Log解析(二) - 云 社区 - 腾讯云
MySQL中的Redo Log(三) - 云 社区 - 腾讯云
使用O_DIRECT_NO_FSYNC来提升MySQL性能 - 知乎
维基-两阶段提交
innodb_flush_log_at_trx_commit和sync_binlog参数详解