Skip to content
清晨的一缕阳光
返回

慢查询日志与优化

引言

慢查询日志是 MySQL 性能优化的重要工具,它可以记录执行时间超过指定阈值的 SQL 语句。通过分析慢查询日志,我们可以快速定位性能瓶颈,针对性地进行优化。

本章将深入讲解:

慢查询日志基础

什么是慢查询日志

慢查询日志(Slow Query Log) 记录了所有执行时间超过指定阈值的 SQL 语句。通过分析这些 SQL,我们可以:

慢查询日志参数

参数名说明默认值建议值
slow_query_log是否开启慢查询日志OFFON
slow_query_log_file慢查询日志文件路径数据目录/var/log/mysql/slow.log
long_query_time慢查询阈值(秒)101-2
log_queries_not_using_indexes记录未使用索引的查询OFFON(开发环境)
log_slow_admin_statements记录管理语句OFFOFF
min_examined_rows最小检查行数阈值0100

配置慢查询日志

临时配置(重启失效)

-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';

-- 设置慢查询阈值(秒)
SET GLOBAL long_query_time = 1;

-- 记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes = 'ON';

-- 查看配置
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';

永久配置(修改配置文件)

Linux(/etc/my.cnf 或 /etc/mysql/my.cnf):

[mysqld]
# 开启慢查询日志
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log

# 慢查询阈值(秒)
long_query_time = 1

# 记录未使用索引的查询(开发环境开启)
log_queries_not_using_indexes = ON

# 最小检查行数
min_examined_rows = 100

Windows(my.ini):

[mysqld]
slow-query-log = ON
slow-query-log-file = D:/mysql/logs/slow.log
long-query-time = 1
log-queries-not-using-indexes = ON

重启 MySQL 生效:

# Linux
sudo systemctl restart mysqld

# Windows
net stop mysql
net start mysql

验证配置

-- 查看慢查询日志状态
SHOW VARIABLES LIKE 'slow_query_log';
-- +------------------+-------+
-- | Variable_name    | Value |
-- +------------------+-------+
-- | slow_query_log   | ON    |
-- +------------------+-------+

-- 查看日志文件位置
SHOW VARIABLES LIKE 'slow_query_log_file';
-- +------------------+----------------------------+
-- | Variable_name    | Value                      |
-- +------------------+----------------------------+
-- | slow_query_log_file | /var/log/mysql/slow.log |
-- +------------------+----------------------------+

-- 查看慢查询阈值
SHOW VARIABLES LIKE 'long_query_time';
-- +-----------------+-----------+
-- | Variable_name   | Value     |
-- +-----------------+-----------+
-- | long_query_time | 1.000000  |
-- +-----------------+-----------+

-- 测试慢查询日志
SELECT SLEEP(2);
-- 这条语句会被记录到慢查询日志

慢查询日志格式

日志格式示例

# Time: 2024-08-15T10:30:45.123456Z
# User@Host: app_user[app_user] @ localhost []  Id: 123
# Query_time: 2.345678  Lock_time: 0.000123 Rows_sent: 1000  Rows_examined: 100000
# Rows_affected: 0  Bytes_sent: 51200
use ecommerce;
SET timestamp=1723716645;
SELECT * FROM orders WHERE user_id = 100 AND status = 1 ORDER BY create_time DESC;

字段说明

字段说明优化方向
Query_time查询执行时间越短越好
Lock_time等待锁的时间过长说明锁竞争严重
Rows_sent返回的行数结合 Rows_examined 分析
Rows_examined扫描的行数越少越好
Rows_affected影响的行数UPDATE/DELETE 时关注
Bytes_sent发送的数据量过大考虑分页或限制

性能分析指标

扫描效率:

扫描效率 = Rows_sent / Rows_examined

-- 示例 1:高效查询
Rows_sent: 10  Rows_examined: 10  → 效率:100% ✅

-- 示例 2:低效查询
Rows_sent: 10  Rows_examined: 10000  → 效率:0.1% ❌

优化目标:

慢查询分析工具

1. mysqldumpslow(MySQL 自带)

基本用法:

# 查看最慢的 10 条查询
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log

# 按返回行数排序
mysqldumpslow -s r -t 10 /var/log/mysql/slow.log

# 按扫描行数排序
mysqldumpslow -s e -t 10 /var/log/mysql/slow.log

# 按查询次数排序(最常用)
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log

# 过滤特定查询
mysqldumpslow -s t -t 10 -g "SELECT.*FROM orders" /var/log/mysql/slow.log

参数说明:

输出示例:

Count: 100  Time=2.34s (234s)  Lock=0.00s (0s)  Rows_sent=1000.0(100000), Rows_examined=10000.0(1000000)
app_user@app_user

  SELECT * FROM orders WHERE user_id = N AND status = N ORDER BY create_time DESC;

解读:

2. pt-query-digest(Percona 工具)

安装:

# Ubuntu/Debian
sudo apt-get install percona-toolkit

# CentOS/RHEL
sudo yum install percona-toolkit

# 或下载单独脚本
wget https://www.percona.com/get/pt-query-digest
chmod +x pt-query-digest
sudo mv pt-query-digest /usr/local/bin/

基本用法:

# 分析慢查询日志
pt-query-digest /var/log/mysql/slow.log

# 分析最近 1 小时的日志
pt-query-digest --since=1h /var/log/mysql/slow.log

# 分析特定查询
pt-query-digest --filter='$_->{db} =~ /ecommerce/' /var/log/mysql/slow.log

# 输出到文件
pt-query-digest /var/log/mysql/slow.log > /tmp/slow_analysis.txt

输出示例:

# Profile
# Rank Query ID           Response time  Calls  R/Call V/M   Item
# ==== ================== ============== ====== ====== ===== ====================
#    1 0x1234567890abcdef  234.5678 50.3%    100  2.3456 0.02  SELECT orders
#    2 0xfedcba0987654321   87.6543 18.8%     50  1.7531 0.01  SELECT users

# Query 1: SELECT orders
# Count: 100
# Exec time: 234s total, 2s avg
# Lock time: 0s total, 0ms avg
# Rows sent: 1000 avg
# Rows examined: 10000 avg
# Users: 1 unique
# Databases: 1 unique
# Tables: 1 unique

# EXPLAIN
SELECT * FROM orders WHERE user_id = ? AND status = ? ORDER BY create_time DESC;

优势:

3. MySQL Workbench(图形化工具)

步骤:

  1. 打开 MySQL Workbench
  2. 连接数据库
  3. 菜单:Management → Slow Log
  4. 查看和分析慢查询

4. 自定义分析脚本

Python 脚本示例:

#!/usr/bin/env python3
import re
from collections import defaultdict

def analyze_slow_log(log_file):
    queries = defaultdict(list)
    
    with open(log_file, 'r') as f:
        content = f.read()
    
    # 解析慢查询日志
    pattern = r'# Time:.*?\n# User@Host:.*?\n# Query_time: (\d+\.\d+).*?Rows_sent: (\d+).*?Rows_examined: (\d+).*?\nuse.*?\n(.*?);'
    matches = re.findall(pattern, content, re.DOTALL)
    
    for query_time, rows_sent, rows_examined, sql in matches:
        # 规范化 SQL(去除具体值)
        normalized_sql = re.sub(r'\b\d+\b', 'N', sql.strip())
        queries[normalized_sql].append({
            'time': float(query_time),
            'sent': int(rows_sent),
            'examined': int(rows_examined)
        })
    
    # 输出分析结果
    for sql, stats in sorted(queries.items(), key=lambda x: sum(s['time'] for s in x[1]), reverse=True)[:10]:
        avg_time = sum(s['time'] for s in stats) / len(stats)
        total_time = sum(s['time'] for s in stats)
        print(f"Count: {len(stats)}, Avg Time: {avg_time:.2f}s, Total: {total_time:.2f}s")
        print(f"SQL: {sql[:100]}...\n")

if __name__ == '__main__':
    analyze_slow_log('/var/log/mysql/slow.log')

慢查询优化实战

案例 1:未使用索引

慢查询日志:

# Query_time: 3.456789  Rows_sent: 100  Rows_examined: 500000
SELECT * FROM orders WHERE user_id = 100 AND status = 1;

分析:

诊断:

EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 1;
-- type: ALL(全表扫描)
-- key: NULL(未使用索引)

优化:

-- 创建联合索引
CREATE INDEX idx_user_status ON orders(user_id, status);

-- 验证
EXPLAIN SELECT * FROM orders WHERE user_id = 100 AND status = 1;
-- type: ref
-- key: idx_user_status
-- rows: 100(从 50 万降到 100)

-- 优化后 Query_time: 0.001s(提升 3000 倍)

案例 2:函数导致索引失效

慢查询日志:

# Query_time: 5.678901  Rows_sent: 1000  Rows_examined: 1000000
SELECT * FROM orders WHERE YEAR(create_time) = 2024;

分析:

优化:

-- 改写为范围查询
SELECT * FROM orders 
WHERE create_time >= '2024-01-01 00:00:00' 
  AND create_time < '2025-01-01 00:00:00';

-- 创建索引
CREATE INDEX idx_create_time ON orders(create_time);

-- 优化后 Query_time: 0.05s(提升 100 倍)

案例 3:排序导致文件排序

慢查询日志:

# Query_time: 2.345678  Rows_sent: 10  Rows_examined: 50000
SELECT * FROM orders WHERE user_id = 100 ORDER BY create_time DESC LIMIT 10;

分析:

优化:

-- 创建联合索引(包含排序列)
CREATE INDEX idx_user_time ON orders(user_id, create_time DESC);

-- 验证
EXPLAIN SELECT * FROM orders 
WHERE user_id = 100 
ORDER BY create_time DESC 
LIMIT 10;
-- Extra: NULL(无 filesort)

-- 优化后 Query_time: 0.002s(提升 1000 倍)

案例 4:隐式类型转换

慢查询日志:

# Query_time: 4.567890  Rows_sent: 1  Rows_examined: 200000
SELECT * FROM users WHERE phone = 13800138000;

分析:

优化:

-- 使用正确的数据类型
SELECT * FROM users WHERE phone = '13800138000';

-- 确保索引存在
CREATE INDEX idx_phone ON users(phone);

-- 优化后 Query_time: 0.001s(提升 4000 倍)

案例 5:LIKE 通配符

慢查询日志:

# Query_time: 6.789012  Rows_sent: 100  Rows_examined: 1000000
SELECT * FROM users WHERE name LIKE '%张%';

分析:

优化方案 1:使用全文索引

-- 创建全文索引
ALTER TABLE users ADD FULLTEXT INDEX ft_name (name);

-- 使用全文搜索
SELECT * FROM users WHERE MATCH(name) AGAINST('张三');

-- 优化后 Query_time: 0.1s(提升 60 倍)

优化方案 2:前缀匹配

-- 如果可以改为前缀匹配
SELECT * FROM users WHERE name LIKE '张%';

-- 创建普通索引
CREATE INDEX idx_name ON users(name);

-- 优化后 Query_time: 0.01s(提升 600 倍)

生产环境最佳实践

1. 慢查询日志配置

开发环境:

long_query_time = 1
log_queries_not_using_indexes = ON

生产环境:

long_query_time = 2
log_queries_not_using_indexes = OFF  # 避免日志过大
min_examined_rows = 100  # 只记录扫描 100 行以上的查询

2. 日志轮转

防止日志文件过大:

# /etc/logrotate.d/mysql-slow
/var/log/mysql/slow.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    create 640 mysql adm
    postrotate
        mysqladmin flush-logs
    endscript
}

3. 定期分析

每日分析脚本:

#!/bin/bash
# /usr/local/bin/daily_slow_query_analysis.sh

LOG_FILE="/var/log/mysql/slow.log"
REPORT_FILE="/var/log/mysql/slow_report_$(date +%Y%m%d).txt"

# 分析昨天的慢查询
pt-query-digest --since=24h $LOG_FILE > $REPORT_FILE

# 发送邮件报告
mail -s "MySQL Slow Query Report $(date +%Y-%m-%d)" dba@example.com < $REPORT_FILE

# 清空慢查询日志
mysql -e "FLUSH SLOW LOGS;"

设置定时任务:

# crontab -e
0 9 * * * /usr/local/bin/daily_slow_query_analysis.sh

4. 监控告警

慢查询数量监控:

-- 查看慢查询数量
SHOW GLOBAL STATUS LIKE 'Slow_queries';

-- 计算每秒慢查询数
SHOW GLOBAL STATUS LIKE 'Slow_queries';
SLEEP(10);
SHOW GLOBAL STATUS LIKE 'Slow_queries';
-- (第二次值 - 第一次值) / 10 = 每秒慢查询数

告警阈值:

5. 性能基线

建立性能基线:

-- 记录正常情况下的指标
SELECT 
    VARIABLE_NAME, 
    VARIABLE_VALUE 
FROM information_schema.GLOBAL_STATUS 
WHERE VARIABLE_NAME IN (
    'Queries',
    'Slow_queries',
    'Questions',
    'Threads_connected'
);

定期对比:

注意事项

1. 性能影响

开启慢查询日志会有轻微性能开销(约 1-3%):

-- 生产环境建议
-- 1. 不要设置过低的 long_query_time
-- 2. 定期清理日志文件
-- 3. 使用独立的磁盘存储日志

2. 磁盘空间

# 监控日志文件大小
ls -lh /var/log/mysql/slow.log

# 限制日志文件大小
-- my.cnf 中添加
max_binlog_size = 100M

3. 隐私保护

慢查询日志可能包含敏感数据:

# 限制日志文件权限
chmod 640 /var/log/mysql/slow.log
chown mysql:adm /var/log/mysql/slow.log

# 定期清理旧日志
find /var/log/mysql -name "slow.log.*" -mtime +30 -delete

总结

慢查询优化流程

graph LR
    A[开启慢查询日志] --> B[收集慢查询 SQL]
    B --> C[使用工具分析]
    C --> D[定位问题原因]
    D --> E[制定优化方案]
    E --> F[实施优化]
    F --> G[验证效果]
    G --> H[持续监控]

核心要点

  1. 合理配置:根据环境设置合适的阈值
  2. 定期分析:使用工具自动化分析
  3. 针对性优化:根据具体原因选择优化方案
  4. 持续监控:建立性能基线和告警机制

工具选择

工具适用场景优势
mysqldumpslow快速分析MySQL 自带,无需安装
pt-query-digest深度分析功能强大,报告详细
自定义脚本特殊需求灵活定制
监控平台实时监控图形化,告警

下一步

掌握慢查询分析后,下一章我们将学习:

参考资料


分享这篇文章到:

上一篇文章
Stream API 深度解析
下一篇文章
Java 8-21 新特性详解