深入理解 MySQL 8.0 慢日志:原理、使用与案例分析

发布于:2025-06-25 ⋅ 阅读:(15) ⋅ 点赞:(0)

在 MySQL 数据库管理中,慢查询日志(Slow Query Log)是一个至关重要的工具,它能够帮助我们识别执行时间较长的 SQL 查询,从而为性能优化提供有力支持。

一、慢查询日志原理

1. 记录条件

MySQL 慢查询日志记录执行时间超long_query_time秒,且检查行数达min_examined_row_limit的 SQL 语句。long_query_time默认值 10,最小值 0,支持微秒级精度。如设为 2 秒,满足条件的慢查询将被记录。

另外,默认情况下,管理语句(如ALTER TABLEANALYZE TABLE等)不会被记录,未使用索引进行查找的查询也不会被记录。但我们可以通过log_slow_admin_statementslog_queries_not_using_indexes这两个系统变量来改变这种行为。

2. 记录时机

MySQL 服务器在查询执行完毕且所有锁都已释放后,才会将该查询写入慢查询日志。这就导致日志记录的顺序可能与查询执行的顺序不一致。例如,查询 A 先开始执行,但查询 B 先执行完毕并释放锁,那么查询 B 可能会先被记录到慢查询日志中。

3. 相关系统变量

  • slow_query_log:控制慢查询日志开关,默认禁用。可通过--slow_query_log({0|1})指定初始状态,或运行时用SET GLOBAL slow_query_log = {0|1}控制。

  • slow_query_log_file:指定日志文件名,默认host_name-slow.log 。可通过--slow_query_log_file指定路径,运行时用SET GLOBAL修改文件名并切换文件。

  • log_output:指定日志输出为FILETABLEFILE,TABLE,设为表时需注意文件打开数限制。

  • log_slow_admin_statements:启用后记录ALTER TABLE等慢管理语句。

  • log_queries_not_using_indexes:记录未用索引查询(表行数≥2 时),log_throttle_queries_not_using_indexes可限制记录速率。

  • log_timestamps:控制日志文件时间戳时区,不影响日志表,表数据可通过函数或会话变量转换时区。

  • log_slow_replica_statements(8.0.26+)/log_slow_slave_statements(8.0.26 前):默认副本不记录复制慢查询,仅binlog_formatstatementmixed(语句格式)时生效。

二、慢查询日志使用方法

1. 开启慢查询日志

配置文件方式

在 MySQL 的配置文件(通常是my.cnfmy.ini)中,找到[mysqld]部分,添加或修改以下配置:

slow\_query\_log = 1
slow\_query\_log\_file = /path/to/your/slow-query.log
long\_query\_time = 2
log\_queries\_not\_using\_indexes = 1

上述配置中,slow_query_log = 1开启慢查询日志;slow_query_log_file指定日志文件路径;long_query_time = 2将慢查询的时间阈值设置为 2 秒;log_queries_not_using_indexes = 1表示记录未使用索引的查询。修改完成后,重启 MySQL 服务使配置生效。

命令行方式

也可以在 MySQL 运行时通过 SQL 命令来开启慢查询日志:

SET GLOBAL slow\_query\_log = 1;
SET GLOBAL slow\_query\_log\_file = '/path/to/your/slow-query.log';
SET GLOBAL long\_query\_time = 2;
SET GLOBAL log\_queries\_not\_using\_indexes = 1;

这种方式无需重启 MySQL 服务,但在服务器重启后设置会失效。如果要永久生效,还是建议修改配置文件。

2. 查看慢查询日志

文件方式查看

如果将慢查询日志输出到文件,可以使用文本编辑器直接打开日志文件查看。日志文件的每一条记录通常包含以下信息:

例如:

# Time: 2023 - 05 - 10T14:30:15.123456Z
# User@Host: root(root) @ localhost () Id: 10
# Query\_time: 3.567890 
Lock_time: 0.000123 
Rows_sent: 100 
Rows_examined: 10000
SET timestamp = 1683714615;
SELECT \* FROM large\_table WHERE some\_column = 'value';

其中,Time是查询执行的时间;User@Host是执行查询的用户和主机信息;Query_time是查询执行时间;Lock_time是锁等待时间;Rows_sent是返回的行数;Rows_examined是检查的行数;SET timestamp用于设置查询开始执行时的时间戳;最后是具体的 SQL 查询文本。

表方式查看

如果将log_output设置为TABLE,MySQL 会将慢查询日志记录到mysql.slow_log表中。可以通过以下 SQL 查询来查看慢查询日志:

SELECT \* FROM mysql.slow\_log;

该表的结构大致如下:

*************************** 1. row ***************************
           start_time: 2025-05-15 10:30:45.123456
              user_host: root[root] @ localhost []
            query_time: 00:00:02.143567
             lock_time: 00:00:00.012345
             rows_sent: 100
         rows_examined: 1000000
                   db: ecom
         last_insert_id: 0
              insert_id: 0
             server_id: 1
              sql_text: SELECT * FROM orders WHERE order_date < '2023-01-01' ORDER BY total_amount DESC
            thread_id: 12345
*************************** 2. row ***************************
           start_time: 2025-05-15 10:35:12.678901
              user_host: app_user[app] @ 192.168.1.50 []
            query_time: 00:00:05.891234
             lock_time: 00:00:00.034567
             rows_sent: 5
         rows_examined: 5000000
                   db: ecom
         last_insert_id: 0
              insert_id: 0
             server_id: 1
              sql_text: SELECT COUNT(*) FROM products JOIN inventory ON products.id = inventory.product_id
            thread_id: 12346
*************************** 3. row ***************************
           start_time: 2025-05-15 10:42:30.456789
              user_host: root[root] @ localhost []
            query_time: 00:00:10.234567
             lock_time: 00:00:00.056789
             rows_sent: 0
         rows_examined: 2000000
                   db: test
         last_insert_id: 0
              insert_id: 0
             server_id: 1
              sql_text: UPDATE users SET status = 'inactive' WHERE last_login < '2022-12-31'
            thread_id: 12347

分析重点
查询耗时:

  • query_time: 00:00:10.23(第三条记录)属于明显慢查询
  • 对比 lock_time 和 query_time,判断锁等待是否是瓶颈(如第二条记录 lock_time 占比约 0.5%,影响较小)

扫描效率:

  • 第一条记录 rows_examined(100 万)与 rows_sent(100)比例悬殊,需添加索引优化
  • 第二条记录扫描 500万行仅返回 5 行,可能存在全表扫描问题

三、慢查询日志分析工具

1. mysqldumpslow

mysqldumpslow是 MySQL 自带的一个命令行工具,用于解析和汇总慢查询日志文件的内容。它可以帮助我们快速了解慢查询日志中的关键信息,例如:

mysqldumpslow /usr/local/mysq/logl/slow-query.log
#  /usr/local/mysq/logl/slow-query.log  根据实际存放位置来写

默认情况下,mysqldumpslow会对查询进行分组,将相似的查询(除了数字和字符串数据值的具体值不同)归为一组,并在显示摘要输出时将这些值抽象为n's'。例如:

Reading mysql slow query log from /usr/local/mysql/data/mysqld80-slow.log
Count: 1  Time=4.32s (4s)  Lock=0.00s (0s)  Rows=0.0 (0), root(root) @ localhost
insert into t2 select \* from t1

Count: 3  Time=2.53s (7s)  Lock=0.00s (0s)  Rows=0.0 (0), root(root) @ localhost
insert into t2 select \* from t1 limit n

上述输出中,Count表示该类查询出现的次数;Time表示总执行时间和平均执行时间;Lock表示总锁等待时间和平均锁等待时间;Rows表示返回的总行数和平均行数。

mysqldumpslow还支持许多选项,例如:

  • -s:指定排序方式。例如,-s r按返回的行数排序,-s c按查询出现的次数排序,-s t按查询时间排序。

  • -t:指定显示的查询数量。例如,-t 10表示只显示前 10 条查询。

  • -g:用于过滤查询,只显示匹配指定模式的查询。例如,-g "SELECT.*FROM users"只显示涉及users表的SELECT查询。

2. pt-query-digest

pt-query-digest是 Percona 提供的一个更为强大的慢查询日志分析工具。它不仅可以分析 MySQL 的慢查询日志,还能对查询执行计划、索引使用情况等进行深入分析,并生成详细的报告。要使用pt-query-digest,首先需要安装 Percona Toolkit:

sudo apt-get install percona-toolkit

安装完成后,可以使用以下命令来分析慢查询日志:

pt-query-digest /path/to/your/slow-query.log
#/path/to/your/ 为实际存放位置

案例

# 20ms user time, 120ms system time, 34.79M rss, 102.62M vsz
# Current date: Tue Jun 24 15:51:51 2025
# Hostname: localhost.localdomain
# Files: /var/log/mysql/mysql_slow.log
# Overall: 1 total, 1 unique, 0 QPS, 0x concurrency ______________________
# Time range: all events occurred at 2025-06-23T11:17:18
# Attribute          total     min     max     avg     95%  stddev  median
# ============     ======= ======= ======= ======= ======= ======= =======
# Exec time             8s      8s      8s      8s      8s       0      8s
# Lock time            2ms     2ms     2ms     2ms     2ms       0     2ms
# Rows sent              0       0       0       0       0       0       0
# Rows examine           0       0       0       0       0       0       0
# Query size            18      18      18      18      18       0      18

1、关键指标

  1. 执行时间(Exec time)

    • 关注点:长时间执行可能由表数据量大、锁冲突、磁盘 IO 瓶颈等原因导致。
  2. 锁等待时间(Lock time)

    • 关注点:若锁等待时间占比高,可能存在事务并发冲突(如之前死锁案例中的情况)。
  3. 扫描行数(Rows examine)与发送行数(Rows sent)

    • 关注点
      • Rows examine高表示全表扫描(如无索引时),需优化索引;
      • Rows sent低但Rows examine高,说明查询返回结果少但扫描量大,索引缺失问题显著。
  4. 查询类型与具体语句

pt-query-digest的输出结果包含丰富的信息,例如:

  • 按查询执行时间、锁等待时间、返回行数等维度排序的查询列表。

  • 每个查询的详细执行计划,包括索引使用情况、扫描行数等。

  • 针对每个查询的优化建议,例如添加索引、重写查询等。

通过分析pt-query-digest的输出,我们可以更全面地了解数据库的性能问题,并采取针对性的优化措施。

四、案例分析

案例一:查询执行时间过长

在某电商平台的实际运营中,随着业务规模的扩大,用户频繁反馈商品列表页加载迟缓。技术团队迅速开启慢查询日志,并将long_query_time阈值设为 2 秒。经过 48 小时的数据采集,发现了一条典型慢查询记录:

\# Time: 2023-06-01T15:20:30.567890Z
\# User@Host: web_user(web_server) @ 192.168.1.100 () Id: 50
# Query_time: 3.567890 
Lock_time: 0.000123 
Rows_sent: 100 
Rows_examined: 100000
SET timestamp = 1685632830;
SELECT * FROM products WHERE category = 'electronics' AND price > 500;

通过EXPLAIN执行计划分析,发现该查询触发了全表扫描:在包含 10 万条记录的products表中,由于categoryprice列缺乏有效索引,数据库引擎无法快速定位目标数据,导致单条查询耗时长达 3.5 秒。从索引原理来看,单列索引无法同时满足多条件筛选需求,而复合索引能构建高效的 B + 树数据结构,显著提升查询性能。

优化方案采用覆盖索引策略,创建联合索引:

CREATE INDEX idx_category_price ON products (category, price);

优化后进行压力测试,相同查询场景下响应时间降至 120 毫秒,配合前端缓存机制,页面加载速度提升 80% 以上。同时需要注意,复合索引遵循 “最左前缀原则”,查询条件必须包含category列才能有效利用该索引。

案例二:未使用索引的查询

某数据分析系统在处理订单统计时,出现响应延迟问题。通过慢查询日志定位到高频慢查询:

\# Time: 2023-06-02T10:15:45.234567Z
\# User@Host: analytics\_user(analytics\_server) @ 192.168.1.101 () Id: 60
# Query_time: 2.123456 
Lock_time: 0.000234 
Rows_sent: 500 
Rows_examined: 50000
SET timestamp = 1685686545;
SELECT \* FROM orders WHERE order\_date BETWEEN '2023-01-01' AND '2023-06-01';

该查询需要扫描 5 万条订单记录,而order_date列缺失索引导致全表扫描。虽然日期范围查询理论上可以使用索引,但无序的扫描方式严重影响性能。

采用单列索引优化方案:

CREATE INDEX idx_order_date ON orders (order_date);

优化后配合分区表技术(按月份对订单表分区),相同查询响应时间缩短至 350 毫秒。值得注意的是,对于频繁写入的表,过多索引会增加写操作开销,建议通过SHOW INDEX命令监控索引使用情况,必要时进行索引重建或合并。同时,对于范围查询场景,覆盖索引比普通索引能获得更好的性能表现。


网站公告

今日签到

点亮在社区的每一天
去签到