数据库性能优化中,慢查询就像隐藏在系统里的"血栓",它们悄无声息地消耗着系统资源,最终可能导致整个应用瘫痪。我在金融行业做数据库运维时,曾遇到过一个典型案例:某核心交易系统在业务高峰期频繁出现响应超时,最终排查发现是一条毫不起眼的统计查询没有走索引,单次执行竟消耗了8秒,在并发场景下直接拖垮了整个数据库集群。
MySQL的慢查询监控机制本质上是个"SQL执行记录仪",其工作原理可分为三个层次:
关键配置参数说明:
long_query_time=1 # 超过1秒的查询被记录
log_queries_not_using_indexes=ON # 捕获未走索引的查询
slow_query_log_file=/var/log/mysql-slow.log # 日志存储路径
这是最经典的方案,适合所有MySQL版本。在my.cnf中配置以下参数后需重启服务:
ini复制[mysqld]
slow_query_log = 1
long_query_time = 0.5 # 根据业务调整阈值
log_output = FILE # 也可设为TABLE存入mysql.slow_log
日志分析推荐使用mysqldumpslow工具,其排序功能非常实用:
bash复制# 按平均耗时排序
mysqldumpslow -s at /var/log/mysql-slow.log
# 统计最频繁的慢查询
mysqldumpslow -s c /var/log/mysql-slow.log
MySQL 5.7+版本推荐使用此方案,无需重启即可生效:
sql复制-- 启用events_statements_history_long表
UPDATE performance_schema.setup_consumers
SET ENABLED = 'YES'
WHERE NAME = 'events_statements_history_long';
-- 查询当前慢SQL(示例查询耗时TOP 10)
SELECT DIGEST_TEXT, AVG_TIMER_WAIT/1000000000 AS avg_ms
FROM performance_schema.events_statements_history_long
WHERE DIGEST_TEXT IS NOT NULL
ORDER BY avg_ms DESC
LIMIT 10;
临时排查问题时,可以在特定会话中开启精细监控:
sql复制-- 开启当前会话的SQL跟踪
SET SESSION profiling = 1;
-- 执行待监控的SQL...
-- 查看性能分析结果
SHOW PROFILE;
-- 更详细的执行计划分析
EXPLAIN ANALYZE SELECT * FROM large_table WHERE unindexed_column = 'value';
生产环境推荐组合方案:
通过EXPLAIN查看关键指标:
sql复制EXPLAIN FORMAT=JSON
SELECT * FROM orders WHERE user_id = 100 AND status = 'pending';
重点关注:
通过SHOW PROFILE查看详细资源占用:
sql复制-- 查看可用分析类型
SELECT * FROM INFORMATION_SCHEMA.PROFILING;
-- 查看CPU/IO消耗
SHOW PROFILE CPU, BLOCK IO FOR QUERY 1;
慢查询可能是锁等待导致的:
sql复制-- 查看当前锁等待
SELECT * FROM sys.innodb_lock_waits;
-- 查看事务详情
SELECT * FROM performance_schema.events_transactions_current;
将慢查询与应用日志关联:
bash复制# 使用pt-query-digest关联时间戳
pt-query-digest --since '2023-07-01 09:00:00' \
/var/log/mysql-slow.log \
/var/log/app/app.log
sql复制-- 对比不同时段的慢查询变化
SELECT DATE_FORMAT(start_time, '%Y-%m-%d %H:00') AS hour,
COUNT(*) AS slow_queries
FROM mysql.slow_log
WHERE start_time > NOW() - INTERVAL 7 DAY
GROUP BY hour;
通过sys库检查索引使用情况:
sql复制SELECT * FROM sys.schema_unused_indexes;
SELECT * FROM sys.statements_with_full_table_scans;
问题SQL:
sql复制SELECT * FROM user_activities
WHERE DATE(created_at) = '2023-07-15';
优化方案:
sql复制SELECT * FROM user_activities
WHERE created_at BETWEEN '2023-07-15 00:00:00' AND '2023-07-15 23:59:59';
sql复制ALTER TABLE user_activities
ADD INDEX idx_created_date ((DATE(created_at)));
原始慢查询:
sql复制SELECT * FROM large_table
ORDER BY create_time DESC
LIMIT 10000, 20;
优化方案:
sql复制-- 方案1:使用覆盖索引
SELECT id FROM large_table
ORDER BY create_time DESC
LIMIT 10000, 20;
-- 方案2:游标分页(需前端配合)
SELECT * FROM large_table
WHERE create_time < '2023-07-01 00:00:00'
ORDER BY create_time DESC
LIMIT 20;
问题场景:
sql复制SELECT u.*, o.total_amount
FROM users u JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active';
优化步骤:
sql复制SELECT u.*, o_stats.total_amount
FROM users u
JOIN (
SELECT user_id, SUM(amount) AS total_amount
FROM orders
GROUP BY user_id
) o_stats ON u.id = o_stats.user_id
WHERE u.status = 'active';
根据业务类型建议不同阈值:
推荐架构:
code复制慢查询日志 → Filebeat → Logstash → Elasticsearch
↘ pt-query-digest → 邮件报警
通过Prometheus监控:
yaml复制# mysql_exporter配置示例
- name: mysql_slow_queries
query: |
SELECT COUNT(*)
FROM mysql.slow_log
WHERE start_time > NOW() - INTERVAL 5 MINUTE
metrics:
- name: mysql_slow_queries_total
type: gauge
help: "Total slow queries in last 5 minutes"
sql复制-- 每月归档慢查询日志
CREATE TABLE slow_log_archive_202307 LIKE mysql.slow_log;
INSERT INTO slow_log_archive_202307 SELECT * FROM mysql.slow_log;
TRUNCATE mysql.slow_log;
索引误区:
配置陷阱:
ini复制# 可能导致OOM的危险设置
sort_buffer_size = 256M # 每个连接都会分配
join_buffer_size = 512M
工具使用禁忌:
隐式转换案例:
sql复制-- 字段是varchar但用数字查询(错误)
SELECT * FROM products WHERE code = 1001;
-- 正确写法
SELECT * FROM products WHERE code = '1001';
临时表优化:
sql复制-- 使用内存临时表
SET tmp_table_size = 256M;
SET max_heap_table_size = 256M;
-- 或者强制使用磁盘临时表
SET big_tables = ON;
在金融级数据库维护中,我总结出一个黄金法则:任何执行超过100ms的查询都必须被审查。曾经有个统计查询在测试环境运行良好,但在生产环境因数据量差异变成了慢查询,导致月末结算时整个系统卡死。现在我们的监控体系会对慢查询进行实时评分,结合执行频率、资源消耗等维度自动生成优化工单。