图文结合带你搞懂MySQL日志之Slow Query Log(慢查询日志)
Posted Java佳佳
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了图文结合带你搞懂MySQL日志之Slow Query Log(慢查询日志)相关的知识,希望对你有一定的参考价值。
什么是慢查询日志
mysql 的慢查询日志,用来记录在 MySQL 中响应时间超过阀值的语句,具体指运行时间超过 long_query_time 值的 SQL,则会被记录到慢查询日志中。long_query_time 的默认值为 10,意思是运行 10 秒以上 (不含 10 秒) 的语句,认为是超出了我们的最大忍耐时间值。
它的主要作用是,帮助我们发现那些执行时间特别长的 SQL 查询,并且有针对性地进行优化,从而提高系统的整体效率。当我们的数据库服务器发生阻塞、运行变慢的时候,检查一下慢查询日志,找到那些慢查询,对解决问题很有帮助。比如一条 sq | 执行超过 5 秒钟,我们就算慢 SQL,希望能收集超过 5 秒的 sql,结合 explain 进行全面分析。
默认情况下,MySQL 数据库没有开启慢查询日志,需要我们手动来设置这个参数。如果不是调优需要的话,一般不建议启动该参数,因为开启慢查询日志会或多或少带来一定的性能影响
慢查询日志支持将日志记录写入文件。
如何开启慢查询日志
开启 slow_query_log
mysql> show variables like '%slow_query_log%';
+-----------------------------------+--------------------------------+
| Variable_name | Value |
+-----------------------------------+--------------------------------+
| slow_query_log | OFF |
| slow_query_log_always_write_time | 10.000000 |
| slow_query_log_file | /var/lib/mysql/KAiTO-slow.log |
| slow_query_log_use_global_control | |
+-----------------------------------+--------------------------------+
4 rows in set (0.00 sec)
# 开启慢查询
mysql > set global slow_query_log='ON';
Query OK, 0 rows affected (0.12 sec)
然后我们再来查看下慢查询日志是否开启,以及慢查询日志文件的位置:
mysql> show variables like '%slow_query_log%';
+-----------------------------------+--------------------------------+
| Variable_name | Value |
+-----------------------------------+--------------------------------+
| slow_query_log | ON |
| slow_query_log_always_write_time | 10.000000 |
| slow_query_log_file | /var/lib/mysql/KAiTO-slow.log |
| slow_query_log_use_global_control | |
+-----------------------------------+--------------------------------+
4 rows in set (0.00 sec)
你能看到这时慢查询分析已经开启,同时文件保存在 /var/lib/mysql/KAiTO-slow.log 文件中。
修改 long_query_time 阈值
接下来我们来看下慢查询的时间阈值设置,使用如下命令:
mysql> show variables like '%long_query_time%';
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.00 sec)
意思就是超过 10 秒的 SQL 语句就会被记录慢查询日志中,那要如何修改这个阈值呢?
mysql> set global long_query_time = 1;
mysql> show global variables like '%long-query_time%';
或修改 my.cnf 文件,[mysqld] 下增加或修改参数 long_query_time、slow_query_log 和 slow_query_log_file 后,然后重启 MySQL 服务器。
[mysqld]
slow_query_log=ON #开启慢查询日志的开关
slow_query_log_file=/var/lib/mysql/my-slow.log #慢查询日志的目录和文件名信息
long_query_time=3 #设置慢查询的阈值为3秒,超出此设定值的SQL即被记录到慢查询日志
log_output=FILE # 一般有两种形式,一种是输出到文件FILE中,一种是写入数据表格table中,会保存到mysql库的slow_log表中
如果不指定存储路径,慢查询日志将默认存储到 MySQL 数据库的数据文件夹下。如果不指定文件名,默认文件名为 hostname-slow.log。
补充
- min_examined_row_limit
除了上述变量,控制慢查询日志的还有一个系统变量: min_examined_row_limit。 这个变量的意思是,查询扫描过的最少记录数。这个变量和查询执行时间,共同组成了判别一个查询是否是慢查询的条件。如果查询扫描过的记录数大于等于这个变量的值,并且查询执行时间超过 long_query_time 的值,那么,这个查询就被记录到慢查询日志中;反之,则不被记录到慢查询日志中。
mysql> show variables like 'min%';
+------------------------+-------+
| Variable_name | Value |
+------------------------+-------+
| min_examined_row_limit | 0 |
+------------------------+-------+
1 row in set (0.01 sec)
你也可以根据需要,通过修改 my.cnf 文件,来修改 min_examined_row_limit 的值。
除了记录普通的慢查询之外,MySQL 还提供了两个参数来让我们记录未使用索引的查询,它们分别是:log-queries-not-using-indexes 和 log_throttle_queries_not_using_indexes
- log-queries-not-using-indexes
系统变量 log-queries-not-using-indexes 作用是未使用索引的查询也被记录到慢查询日志中。
- log_throttle_queries_not_using_indexes
可通过设置 log_throttle_queries_not_using_indexes 来限制每分钟写入慢日志中的不走索引的 SQL 语句个数,该参数默认为 0,表示不开启,也就是说不对写入 SQL 语句条数进行控制。
在生产环境下,如果没有使用索引,那么此类 SQL 语句会频繁地被记录到 slow log,从而导致 slow log 文件大小不断增加,我们可以通过调整此参数进行配置。
- log_slow_extra
如果启用 log_slow_extra 系统变量(从 MySQL 8.0.14 开始提供),服务器会在日志写入几个额外字段。若要记录 bytes_received 与 bytes_sent 这两个字段则需要开启
- percona slow log
GreatSQL 是源于 Percona Server 的分支版本,除了 Percona Server 已有的稳定可靠、高效、管理更方便等优势外,特别是进一步提升了 MGR (MySQL Group Replication) 的性能及可靠性,以及众多 bug 修复。这就是为什么在使用 GreatSQL 查看慢查询日志时,会有 Query_time、Lock_time 等信息,这些都是我们 GreatSQL 源于 Percona Server 的原因,使查询内容更加丰富,更多的数据可以使得我们更好的排查错误。
通过一个简单的案例来展示: 我们先把慢查询日志打开且设置时间阈值大于 1 秒就记录:
#开启慢查询日志
mysql> set global slow_query_log='ON';
Query OK, 0 rows affected (0.00 sec)
#时间阈值超过1秒就记录
mysql> set global long_query_time = 1;
Query OK, 0 rows affected (0.01 sec)
mysql> show variables like '%long_query_time%';
+-----------------+----------+
| Variable_name | Value |
+-----------------+----------+
| long_query_time | 1.000000 |
+-----------------+----------+
1 row in set (0.00 sec)
#查看已经被记录的慢查询数量
mysql> SHOW GLOBAL STATUS LIKE '%Slow_queries%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries | 3 |
+---------------+-------+
1 row in set (0.01 sec)
写一条 SQL 语句使得使用时间大于 1 秒
mysql> SELECT * FROM `student` WHERE id>1000 AND `name`='Yunxi';
+---------+-------+-------+------+---------+
| 9999715 | 707 | Yunxi | 863 | 71 |
.......省略
| 9999999 | 418 | Yunxi | 793 | 734 |
+---------+-------+-------+------+---------+
166949 rows in set (3.94 sec)
mysql> SHOW GLOBAL STATUS LIKE '%Slow_queries%';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| Slow_queries | 4 |
+---------------+-------+
1 row in set (0.00 sec)
可以看到此条 SQL 已经被记录,接下来我们去查看慢查询日志:
# Time: 2022-12-14T15:01:34.892085Z
# User@Host: root[root] @ localhost [] Id: 8
# Query_time: 3.985637 Lock_time: 0.000138 Rows_sent: 165346 Rows_examined: 9900000 Thread_id: 8 Errno: 0 Killed: 0 Bytes_received: 0 Bytes_sent: 4848540 Read_first: 0 Read_last: 0 Read_key: 1 Read_next: 9900000 Read_prev: 0 Read_rnd: 0 Read_rnd_next: 0 Sort_merge_passes: 0 Sort_range_count: 0 Sort_rows: 0 Sort_scan_count: 0 Created_tmp_disk_tables: 0 Created_tmp_tables: 0 Start: 2022-12-14T15:01:30.906448Z End: 2022-12-14T15:01:34.892085Z Schema: slow Rows_affected: 0
# Tmp_tables: 0 Tmp_disk_tables: 0 Tmp_table_sizes: 0
# InnoDB_trx_id: 0
# Full_scan: No Full_join: No Tmp_table: No Tmp_table_on_disk: No
# Filesort: No Filesort_on_disk: No Merge_passes: 0
# InnoDB_IO_r_ops: 27606 InnoDB_IO_r_bytes: 452296704 InnoDB_IO_r_wait: 0.220474
# InnoDB_rec_lock_wait: 0.000000 InnoDB_queue_wait: 0.000000
# InnoDB_pages_distinct: 8191
use slow;
SET timestamp=1671030090;
SELECT * FROM `student` WHERE id>100000 AND `name`='Yunxi';
可以看到慢查询日志记录的非常详细,从上述日志中能看到几个信息:
- 1. 这个 SQL 的耗时 3.985637 秒。
- 2. 返回结果有 165346 行,总共需要扫描 9900000 行数据。如果扫描行数很多,但返回行数很少,说明该 SQL 效率很低,可能索引不当。
- 3.Read_* 等几个指标表示这个 SQL 读记录的方式,是否顺序读、随机读等。
- 4.Sort_* 等几个指标表示该 SQL 是否产生了排序,及其代价。如果有且代价较大,需要想办法优化。
- 5.tmp 等几个指标表示该 SQL 是否产生临时表,及其代价。如果有且代价较大,需要想办法优化。
- 6.Full_scan/Full_join 表示是否产生了全表扫描或全表 JOIN,如果有且 SQL 耗时较大,需要想办法优化。
- 7.InnoDB_IO_* 等几个指标表示 InnoDB 逻辑读相关数据。
- 8.InnoDB_rec_lock_wait 表示是否有行锁等待。
- 9.InnoDB_queue_wait 表示是否有排队等待。
- 10.InnoDB_pages_distinct 表示该 SQL 总共读取了多少个 InnoDB page,是个非常重要的指标。
GreatSQL 可以作为 MySQL 或 Percona Server 的可选替代方案,用于线上生产环境。完全免费并兼容 MySQL 或 Percona Server。综上,如果在生产环境中已经用上 Percona Server 的话,那么也可以放心使用 GreatSQL。详情可见:(https://greatsql.cn/doc/#!&v=47_6_0)了解更多 GreatSQL 内容
查看慢查询数目
查询当前系统中有多少条慢查询记录
SHOW GLOBAL STATUS LIKE '%Slow_queries%';
慢查询日志分析工具
在生产环境中,如果要手工分析日志,查找、分析 SQL,显然是个体力活,MySQL 提供了日志分析工具 mysqldumpslow ,或者是可以使用另一个工具 pt-query-digest。 它可以从 logs、processlist、和 tcpdump 来分析 MySQL 的状况,logs 包括 slow log、general log、binlog。也可以把分析结果输出到文件中,或则把文件写到表中。分析过程是先对查询语句的条件进行参数化,然后对参数化以后的查询进行分组统计,统计出各查询的执行时间、次数、占比等,可以借助分析结果找出问题进行优化。
关闭慢查询日志
作者建议除了调优需要开,正常还是不要开了
MySQL 服务器停止慢查询日志功能的方法:
- 方式 1
[mysqld]
slow_query_log=OFF
- 方式 2
SET GLOBAL slow_query_log=off;
删除慢查询日志
mysql> show variables like '%slow_query_log%';
+-----------------------------------+--------------------------------+
| Variable_name | Value |
+-----------------------------------+--------------------------------+
| slow_query_log | ON |
| slow_query_log_always_write_time | 10.000000 |
| slow_query_log_file | /var/lib/mysql/zhyno1-slow.log |
| slow_query_log_use_global_control | |
+-----------------------------------+--------------------------------+
4 rows in set (0.00 sec)
通过以上查询可以看到慢查询日志的目录,在该目录下手动删除慢查询日志文件即可。或使用命令 mysqladmin 来删除,mysqladmin 命令的语法如下:mysqladmin -uroot -p flush-logs 执行该命令后,命令行会提示输入密码。输入正确密码后,将执行删除操作。新的慢查询日志会直接覆盖旧的查询日志,不需要再手动删除。
注意 慢查询日志都是使用 mysqladmin flush-logs 命令来删除重建的。使用时一定要注意,一旦执行了这个命令,慢查询日志都只存在新的日志文件中,如果需要旧的查询日志,就必须事先备份。
图文结合带你搞懂MySQL日志之Redo Log(重做日志)
往期文章:
导读
前言
请读者注意:本文基于 GreatSQL 8.0.25 & MySQL 5.7.7-RC版本,在 MySQL8.0.30 Redo 发生变化,详情见: MySQL 8.0.30动态redo log初探
前面聊了MySQL中的Undo Log日志和InnoDB中的MVCC,今天一起来学习下Redo Log日志。
事务有4种特性:原子性、一致性、隔离性和持久性(ACID)。那么事务的四种特性到底是基于什么机制实现呢?
事务的隔离性由锁机制实现。
而事务的原子性、一致性和持久性由事务的 Redo 日志和 Undo 日志来保证。
Redo Log
称为重做日志,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
UNDO LOG
称为回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
有的DBA或许会认为 UNDO 是 REDO 的逆过程,其实不然。REDO和UNDO都可以视为是一种恢复操作。
Redo Log:
是存储引擎层(InnoDB)生成的日志,记录的是"物理级别"上的页修改操作,比如页号xx、偏移量yyy写入了’zzz’数据。主要为了保证数据的可靠性;
提交,由Redo Log来保证事务的持久化。
Undo Log:
是存储引擎层(Innodb)生成的日志,记录的是逻辑操作日志,比如对某一行数据进行了INSERT语句操作,那么Undo Log 就记录一条与之相反的DELETE操作。主要用于事务的回滚(Undo Log 记录的是每个修改操作的逆操作)和一致性非锁定读(Undo Log回滚行记录到某种特定的版本—MVCC,即多版本并发控制)。
Redo Log日志
InnoDB存储引擎是以页为单位来管理存储空间的。在真正访问页面之前需要把在磁盘上的页缓存到内存中的Buffer Pool
之后才可以访问。所有的变更都必须先更新缓冲池中的数据,然后缓冲池中的脏页会以一定的频率被刷入磁盘(checkPoint机制
),通过缓冲池来优化CPU和磁盘之间的鸿沟,这样就可以保证整体的性能不会下降太快。
为什么需要Redo日志
一方面,缓冲池可以帮助我们消除CPU和磁盘之间的鸿沟,checkpoint机制
可以保证数据的最终落盘,然而由于checkpoint
并不是每次变更的时候就触发的,而是master
线程隔一段时间去处理的。所以最坏的情况就是事务提交后,刚写完缓冲池,数据库宕机了,那么这段数据就是丢失的,无法恢复。
另一方面,事务包含持久性
的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩溃,这个事务对数据库中所做的更改也不能丢失。
那么如何保证这个持久性呢?一个简单的做法:在事务提交完成之前把该事务所修改的所有页面都刷新到磁盘,但是这个简单粗暴的做法有些问题:
修改量与刷新磁盘工作量严重不成比例
有时候我们只修改了某个页面中的一个字节,但是我们知道在InnoDB中是以页为单位来进行磁盘IO的,也就是说我们在该事务提交时不得不将一个完整的页面从内存中刷新到磁盘,我们又知道一个页面默认是16KB大小,只修改一个字节就要刷新16KB的数据到磁盘上显然是太小题大做了(这也就是所谓“写放大”的意思)。
随机Io刷新较慢
一个事务可能包含很多语句,即使是一条语句也可能修改许多页面,假如该事务修改的这些页面可能并不相邻,这就意味着在将某个事务修改的Buffer Pool中的页面刷新到磁盘时需要进行很多的随机IO,随机IO比顺序IO要慢,尤其对于传统的机械硬盘来说。
另一个解决的思路 :我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内存中修改过的全部页面刷新到磁盘,只需要把修改了哪些东西记录一下就好。
比如,某个事务将系统表空间中 第10号页面中偏移量为 100 处的那个字节的值 1 改成 2 。
我们只需要记录一下:将第0号表空间的10号页面的偏移量为 100 处的值更新为 2 。
InnoDB引擎的事务采用了WAL技术(Write-Ahead Logging)
,这种技术的思想就是先写日志,再写磁盘,只有日志写入成功,才算事务提交成功,这里的日志就是Redo Log。当发生宕机且数据未刷到磁盘的时候,可以通过Redo Log来恢复,保证ACID中的D,这就是Redo Log的作用。
Redo日志记录了什么
为了应对InnoDB各种各样不同的需求,到MySQL 8.0为止,已经有多达 65 种的REDO记录。用来记录这不同的信息,恢复时需要判断不同的 REDO 类型,来做对应的解析。根据 REDO 记录不同的作用对象,可以将这65中 REDO 划分为三个大类:
作用于Page
作用于Space
提供额外信息的Logic类型。
Redo日志的好处、特点
好处
Redo日志降低了刷盘频率
Redo日志占用的空间非常小
存储表空间ID、页号、偏移量以及需要更新的值,所需的存储空间是很小的,刷盘快。
特点
Redo日志是顺序写入磁盘的
在执行事务的过程中,每执行一条语句,就可能产生若干条Redo日志,这些日志是按照产生的顺序写入磁盘的,也就是使用顺序IO,效率比随机IO快。事务执行过程中,Redo Log不断记录
Redo Log
跟Binlog
的区别,Redo Log是存储引擎层产生的,而Binlog是数据库层产生的。假设一个事务,对表做10万行的记录插入,在这个过程中,一直不断的往Redo Log顺序记录,而 Binlog 不会记录,直到这个事务提交,才会一次写入到 Binlog 文件中。
Redo的组成
Redo Log可以简单分为以下两个部分:
重做日志的缓冲 (Redo Log Buffer)
保存在内存中,是易失的。
在服务器启动时就向操作系统申请了一大片称之为Redo Log Buffer
的连续内存空间,翻译成中文就是Redo日志缓冲区。这片内存空间被划分成若干个连续的Redo Log Block
。一个Redo Log Block占用512字节大小。
参数设置
innodb_log_buffer_size:
Redo Log Buffer 的大小,默认为 16M
,最大值是4096M
,最小值为 1M
。
mysql> show variables like '%innodb_log_buffer_size%';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
重做日志文件(Redo Log File),保存在硬盘中,是持久的。
REDO日志文件如图所示,其中的ib_logfile0
和ib_logfile1
即为Redo Log日志。
Redo的整体流程
以一个更新事务为例,Redo Log 流转过程,如下图所示:
流程说明:
第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝(上图中箭头1、4方向画反了)
第2步:生成一条重做日志并写入Redo Log Buffer,记录的是数据被修改后的值
第3步:当事务commit时,将Redo Log Buffer中的内容刷新到 Redo Log File,对 Redo Log File采用追加写的方式
第4步:定期将内存中修改的数据刷新到磁盘中
Redo Log的刷盘策略
Redo Log的写入并不是直接写入磁盘的,InnoDB引擎会在写Redo Log的时候先写Redo Log Buffer
,之后以一定的频率刷入到真正的Redo Log File
中。这里的一定频率怎么规定的呢?这就是我们要说的刷盘策略。
问题:什么时候刷盘呢?
有几种场景可能会触发redo log写文件:
Redo log buffer空间不足时
事务提交
后台线程
做checkpoint
实例shutdown时
binlog切换时
注意,Redo Log Buffer
刷盘到Redo Log File
的过程并不是真正的刷到磁盘中去,只是刷入到文件系统缓存
(page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如 page cache 足够大了)。那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。
针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit
参数,该参数控制 commit 提交事务时,如何将 Redo Log Buffer 中的日志刷新到 Redo Log File 中。它支持三种策略:
设置为0 :
表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步),事务提交不会触发redo写操作,而是留给后台线程每秒一次的刷盘操作,因此实例crash将最多丢失1秒钟内的事务。设置为1 :
表示每次事务提交时都将进行同步,刷盘操作( 默认值 ),每次事务提交都要做一次fsync,这是最安全的配置,即使宕机也不会丢失事务;设置为2 :
表示每次事务提交时都只把 Redo Log Buffer 内容写入 page cache,不进行同步。由os自己决定什么时候同步到磁盘文件,则在事务提交时只做write操作,只保证写到系统的page cache,因此实例crash不会丢失事务,但宕机则可能丢失事务;
下图表示了不同配置值的持久化程度:
显然对性能的影响是随着持久化程度的增加而增加的。通常我们建议在日常场景将该值设置为 1 ,但在系统高峰期临时修改成 2 以应对大负载。
#查看刷盘策略
mysql> show variables like 'innodb_flush_log_at_trx_commit';
+--------------------------------+-------+
| Variable_name | Value |
+--------------------------------+-------+
| innodb_flush_log_at_trx_commit | 1 |
+--------------------------------+-------+
1 row in set (0.00 sec)
另外,InnoDB存储引擎有一个后台线程,每隔1秒,就会把 Redo Log Buffer 中的内容写到文件系统缓存( page cache ),然后调用刷盘操作。
也就是说,一个没有提交事务的Redo Log记录,也可能会刷盘。因为在事务执行过程Redo Log记录是会写入Redo Log Buffer 中,这些Redo Log记录会被后台线程刷盘。
除了后台线程每秒1次
的轮询操作,还有一种情况,当Redo Log Buffer
占用的空间即将达到innodb_log_buffer_size
(这个参数默认是16M)的一半的时候,后台线程会主动刷盘。
写入Redo Log Buffer 过程
1.Mini-Transaction
MySQL把对底层页面中的一次原子访问的过程称之为一个Mini-Transaction
,简称mtr
,比如,向某个索引对应的B+树中插入一条记录的过程就是一个Mini-Transaction
。一个所谓的mtr可以包含一组Redo日志,在进行崩溃恢复时这一组Redo日志作为一个不可分割的整体。
一个事务可以包含若干条语句,每一条语句其实是由若干个 mtr 组成,每一个 mtr 又可以包含若干条Redo日志,画个图表示它们的关系就是这样:
2.Redo 日志写入Log Buffer
向log buffer
中写入Redo日志的过程是顺序的,也就是先往前边的block中写,当该block的空闲空间用完之后再往下一个block中写。当我们想往log buffer
中写入Redo日志时,第一个遇到的问题就是应该写在哪个block的哪个偏移量处,所以InnoDB的设计者特意提供了一个称之为buf_free
的全局变量,该变量指明后续写入的Redo日志应该写入到log buffer
中的哪个位置,如图所示:
一个 mtr 执行过程中可能产生若干条Redo日志,这些Redo日志是一个不可分割的组
,所以其实并不是每生成一条Redo日志,就将其插入到log buffer中,而是每个 mtr 运行过程中产生的日志先暂时存到一个地方,当该 mtr 结束的时候,将过程中产生的一组Redo日志再全部复制到log buffer中。我们现在假设有两个名为T1、T2的事务,每个事务都包含2个 mtr ,我们给这几个 mtr 命名一下:
事务
T1
的两个mtr
分别称为mtr_T1_1
和mtr_T1_2
。事务
T2
的两个mtr
分别称为mtr_T2_1
和mtr_T2_2
。
每个 mtr 都会产生一组Redo日志,用示意图来描述一下这些 mtr 产生的日志情况:
不同的事务可能是 并发
执行的,所以 T1
、 T2
之间的 mtr
可能是 交替执行
的。每当一个mtr执行完成时,伴随该mtr生成的一组Redo日志就需要被复制到log buffer中,也就是说不同事务的mtr可能是交替写入log buffer的,我们画个示意图(为了美观,我们把一个mtr中产生的所有的Redo日志当作一个整体来画):
有的mtr产生的Redo日志量非常大,比如mtr_t1_2产生的Redo日志占用空间比较大,占用了3个block来存储。
3. Redo Log Block的结构图
一个Redo Log Block是由日志头
、日志体
、日志尾
组成。日志头占用12字节,日志尾占用4字节,所以一个block真正能存储的数据就是512-12-4=496字节
。
为什么一个block设计成512字节?
这个和磁盘的扇区有关,机械磁盘默认的扇区就是512字节,如果你要写入的数据大于512字节,那么要写入的扇区肯定不止一个,这时就要涉及到盘片的转动,找到下一个扇区,假设现在需要写入两个扇区A和B,如果扇区A写入成功,而扇区B写入失败,那么就会出现非原子性的写入,而如果每次只写入和扇区的大小一样的512字节,那么每次的写入都是原子性的。
真正的Redo日志都是存储到占用496
字节大小的log block body
中,图中的log block header
和logblock trailer
存储的是一些管理信息。我们来看看这些所谓的管理信息都有什么。
log block header
的属分别如下:LOG_BLOCK_HDR_NO:
log buffer是由log block组成,在内部log buffer就好似一个数组,因此LOG_BLOCK_HDR_NO用来标记这个数组中的位置。其是递增并且循环使用的,占用4个字节,但是由于第一位用来判断是否是flush bit,所以最大的值为2G。LOG_BLOCK_HDR_DATA_LEN:
表示block中已经使用了多少字节,初始值为12
(因为log block body从第12个字节处开始)。随着往block中写入的Redo日志越来也多,本属性值也跟着增长。如果log block body
已经被全部写满,那么本属性的值被设置为512。LOG_BLOCK_FIRST_REC_GROUP:
一条Redo日志也可以称之为一条Redo日志记录(Redo Log Record),一个 mtr 会生产多条Redo日志记录,这些Redo日志记录被称之为一个Redo日志记录组(Redo Log Recordgroup)
。LOG_BLOCK_FIRST_REC_GROUP
就代表该block中第一个mtr生成的Redo日志记录组的偏移量(其实也就是这个block里第一个mtr生成的第一条Redo日志的偏移量)。如果该值的大小和LOG_BLOCK_HDR_DATA_LEN
相同,则表示当前log block不包含新的日志。LOG_BLOCK_CHECKPOINT_NO:
占用4字节,表示该log block最后被写入时的checkpoint
。
log block trailer
中属性的意思如下:LOG_BLOCK_CHECKSUM:
表示block的校验值,用于正确性校验(其值和LOG_BLOCK_HDR_NO相同),我们暂时不关心它。
Redo Log File
相关参数设置
innodb_log_group_home_dir :
指定 Redo Log 文件组所在的路径,默认值为./
,表示在数据库的数据目录下。MySQL的默认数据目录(/var/lib/mysql
)下默认有两个名为ib_logfile0
和ib_logfile1
的文件,log buffer中的日志默认情况下就是刷新到这两个磁盘文件中。此Redo日志文件位置还可以修改。innodb_log_files_in_group:
指明Redo Log File的个数,命名方式如:ib_logfile0,iblogfile1…iblogfilen。默认2个,最大100个。
mysql> show variables like 'innodb_log_files_in_group';
+---------------------------+-------+
| Variable_name | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2 |
+---------------------------+-------+
#ib_logfile0
#ib_logfile1
innodb_flush_log_at_trx_commit:
控制 Redo Log 刷新到磁盘的策略,默认为1。innodb_log_file_size:
单个 Redo Log 文件设置大小,默认值为 48M 。最大值为512G,注意最大值指的是整个 Redo Log 系列文件之和,即(innodb_log_files_in_group * innodb_log_file_size )
不能大于最大值512G。
mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name | Value |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
根据业务修改其大小,以便容纳较大的事务。编辑my.cnf文件并重启数据库生效,如下所示
[root@localhost ~]# vim /etc/my.cnf
innodb_log_file_size=200M
在数据库实例更新比较频繁的情况下,可以适当加大 Redo Log 组数和大小。但也不推荐 Redo Log 设置过大,在MySQL崩溃恢复时会重新执行REDO日志中的记录。
日志文件组
从上边的描述中可以看到,磁盘上的Redo
日志文件不只一个,而是以一个日志文件组
的形式出现的。这些文件以ib_logfile[数字]
(数字可以是0、1、2…)的形式进行命名,每个的Redo日志文件大小都是一样的。
在将Redo日志写入日志文件组时,是从ib_logfile0
开始写,如果ib_logfile0
写满了,就接着ib_logfile1
写。同理,ib_logfile1
.写满了就去写ib_logfile2
,依此类推。如果写到最后一个文件该咋办?那就重新转到ib_logfile0
继续写,所以整个过程如下图所示:
总共的Redo日志文件大小其实就是:innodb_log_file_size × innodb_log_files_in_group
。
采用循环使用的方式向Redo日志文件组里写数据的话,会导致后写入的Redo日志覆盖掉前边写的Redo日志?当然!所以InnoDB的设计者提出了checkpoint
的概念。
checkpoint
在整个日志文件组中还有两个重要的属性,分别是write pos、checkpoint
write pos
是当前记录的位置,一边写一边后移checkpoint
是当前要擦除的位置,也是往后推移
每次刷盘Redo Log记录到日志文件组中,write pos位置就会后移更新。每次MySQL加载日志文件组恢复数据时,会清空加载过的Redo Log记录,并把 checkpoint后移更新。write pos和checkpoint之间的还空着的部分可以用来写入新的Redo Log记录。
如果 write pos 追上 checkpoint ,表示 日志文件组 满了,这时候不能再写入新的 Redo Log 记录,MySQL 得停下来,清空一些记录,把 checkpoint 推进一下。
结尾
由于Redo LOG的内容知识过于庞大,由于篇幅限制,本文只做浅析,这边推荐几篇文章(详见参考文章),感兴趣的同学可以继续深入学习研究。
参考文章
[1]MySQL · 引擎特性 · InnoDB redo log漫游(http://mysql.taobao.org/monthly/2015/05/01/)
[2]MySQL · 引擎特性 · 庖丁解InnoDB之REDO LOG(http://mysql.taobao.org/monthly/2020/02/01/)
[3]MySQL · 引擎特性 · Redo Log record编码格式(http://mysql.taobao.org/monthly/2022/01/02/)
Enjoy GreatSQL :)
《深入浅出MGR》视频课程
戳此小程序即可直达B站
https://www.bilibili.com/medialist/play/1363850082?business=space_collection&business_id=343928&desc=0
文章推荐:
想看更多技术好文,点个“在看”吧!
以上是关于图文结合带你搞懂MySQL日志之Slow Query Log(慢查询日志)的主要内容,如果未能解决你的问题,请参考以下文章
图文结合带你搞懂MySQL日志之Redo Log(重做日志)