MySQL慢查询日志配置与采集
MySQL慢查询诊断的第一步是开启慢查询日志并正确配置阈值。默认情况下慢查询日志是关闭的,需要手动开启。通过以下命令检查当前配置:
SHOW VARIABLES LIKE 'slow_query%';
SHOW VARIABLES LIKE 'long_query_time';
SHOW VARIABLES LIKE 'log_queries_not_using_indexes';
-- 结果示例
-- slow_query_log: OFF
-- long_query_time: 10.000000
-- log_queries_not_using_indexes: OFF
动态开启慢查询日志(不需重启MySQL):
-- 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
-- 设置慢查询阈值为1秒
SET GLOBAL long_query_time = 1;
-- 记录未使用索引的查询
SET GLOBAL log_queries_not_using_indexes = 'ON';
-- 指定日志文件路径
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
-- 记录慢管理语句(ALTER/CREATE等)
SET GLOBAL log_slow_admin_statements = 'ON';
生产环境建议long_query_time设为1秒甚至0.5秒,配合log_queries_not_using_indexes捕捉全表扫描。永久配置写入my.cnf:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
log_slow_admin_statements = 1
min_examined_row_limit = 100 # 检查行数少于100不记录
EXPLAIN执行计划解读
定位到慢查询后,用EXPLAIN分析执行计划。EXPLAIN输出的关键字段:
EXPLAIN SELECT * FROM orders o
JOIN order_items oi ON o.id = oi.order_id
WHERE o.status = 'PAID' AND oi.price > 100;
-- +----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
-- | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
-- +----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
-- | 1 | SIMPLE | o | NULL | ALL | NULL | NULL | NULL | NULL | 50k | 10.00 | Using where |
-- | 1 | SIMPLE | oi | NULL | ref | idx_order | idx_order| 8 | db.o.id| 3 | 33.33 | Using where |
-- +----+-------------+-------+------------+------+---------------+---------+---------+-------+------+----------+-------------+
各字段诊断要点:
type列表示访问类型,性能从好到差:
| type值 | 含义 | 性能 |
|——–|——|——|
| system | 表只有一行 | 优 |
| const | 主键或唯一索引等值查询 | 优 |
| eq_ref | 主键或唯一索引JOIN | 优 |
| ref | 非唯一索引等值查询 | 良 |
| range | 索引范围扫描 | 良 |
| index | 扫描整个索引树 | 中 |
| ALL | 全表扫描 | 差 |
上面示例中orders表type为ALL,表示全表扫描50k行,这是需要优化的核心问题。
Extra列的常见值:
– Using index:覆盖索引,不回表,最优
– Using where:通过WHERE过滤,需要检查是否走了索引
– Using filesort:额外排序,需优化
– Using temporary:使用临时表,需优化
– Using join buffer:JOIN无索引,使用Block Nested Loop
索引优化策略与案例
慢查询的根本解决手段是建立合适的索引。复合索引的设计遵循最左前缀原则:
-- 原始查询
SELECT * FROM orders
WHERE user_id = 1001 AND status = 'PAID' AND create_time > '2026-01-01';
-- 错误索引:只给user_id建索引
-- ALTER TABLE orders ADD INDEX idx_user (user_id);
-- status和create_time需要回表过滤
-- 正确索引:复合索引覆盖查询条件
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, create_time);
-- 验证:EXPLAIN后type变为ref,key显示新索引
EXPLAIN SELECT * FROM orders
WHERE user_id = 1001 AND status = 'PAID' AND create_time > '2026-01-01';
-- type: ref, key: idx_user_status_time, rows: 3
复合索引的字段顺序原则:等值条件在前,范围条件在后。因为范围条件之后的字段无法走索引。
覆盖索引避免回表,将查询列都包含在索引中:
-- 查询只需要部分列
SELECT user_id, status, total_amount FROM orders WHERE user_id = 1001;
-- 覆盖索引:包含所有查询列
ALTER TABLE orders ADD INDEX idx_cover (user_id, status, total_amount);
-- EXPLAIN结果Extra列显示Using index,表示无需回表
-- 对于InnoDB,二级索引存储的是主键值,回表代价在行数大时很高
分页查询深度翻页优化
LIMIT深度翻页是常见的慢查询来源。LIMIT 100000, 20需要扫描100020行后丢弃前10万行,效率极低。
延迟关联优化方案:先通过子查询用索引找到主键,再关联获取完整行:
-- 原始慢查询
SELECT * FROM orders ORDER BY create_time DESC LIMIT 100000, 20;
-- 扫描100020行,耗时2.3秒
-- 优化1:延迟关联
SELECT o.* FROM orders o
INNER JOIN (
SELECT id FROM orders ORDER BY create_time DESC LIMIT 100000, 20
) t ON o.id = t.id;
-- 子查询走索引覆盖扫描,外层只回表20行,耗时0.05秒
-- 优化2:游标分页(记住上一页最后一条记录的值)
SELECT * FROM orders
WHERE create_time < '2026-07-29 12:00:00'
ORDER BY create_time DESC
LIMIT 20;
-- 直接走索引范围查询,扫描20行,耗时0.001秒
游标分页的限制是无法跳转到指定页码,只支持上一页/下一页。对于搜索结果、动态列表等场景完全够用。
JION查询优化与驱动表选择
多表JOIN的性能取决于驱动表和被驱动表的选择。MySQL优化器有时选择错误,需要通过STRAIGHT_JOIN强制指定驱动表:
-- 小表驱动大表:用户表(1000行)驱动订单表(50万行)
SELECT STRAIGHT_JOIN o.* FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.status = 'ACTIVE';
-- EXPLAIN验证
EXPLAIN SELECT STRAIGHT_JOIN o.* FROM users u JOIN orders o ON u.id = o.user_id WHERE u.status = 'ACTIVE';
-- users表为驱动表,type: ref
-- orders表为被驱动表,type: ref, Extra: Using index
JOIN优化的核心原则:
1. 小表驱动大表,减少嵌套循环次数
2. 被驱动表的JOIN字段必须有索引
3. JOIN字段类型必须一致,否则隐式转换导致索引失效
4. 避免JOIN超过3张表,复杂关联拆分为多次查询
慢查询分析工具mysqldumpslow
慢查询日志积累后,需要统计聚合找出最频繁和最耗时的SQL。mysqldumpslow是MySQL自带的日志分析工具:
# 按总耗时排序,取前10条
mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
# 按次数排序(找出最频繁的慢查询)
mysqldumpslow -s c -t 10 /var/log/mysql/slow.log
# 按返回行数排序
mysqldumpslow -s r -t 10 /var/log/mysql/slow.log
# 输出示例
# Count: 342 Time=2.56s (875s) Lock=0.00s (0s) Rows=50000.0 (17100000)
# SELECT * FROM orders WHERE status = 'S' AND create_time BETWEEN 'S' AND 'S'
#
# Count: 156 Time=1.23s (192s) Lock=0.01s (1s) Rows=3.0 (468)
# SELECT * FROM users WHERE phone = 'S'
Count是执行次数,Time是平均耗时和总耗时,Rows是平均扫描行数。参数-s t按总耗时排序,-s c按次数排序,-s r按行数排序,-t 10取前10条。
对于更复杂的分析需求,pt-query-digest(Percona Toolkit)提供更详细的统计:
pt-query-digest /var/log/mysql/slow.log
# 输出包含:
# - 每条SQL的执行分布(百分位延迟)
# - SQL指纹(参数归一化后的模板)
# - 索引使用统计
# - 按时间段的慢查询分布直方图
在线DDL与索引创建锁表问题
大表添加索引会锁表,导致服务不可用。MySQL 8.0的Online DDL对大部分索引操作是in-place的,但仍需注意:
-- MySQL 8.0 Online DDL(默认INPLACE+并发DML)
ALTER TABLE orders ADD INDEX idx_status_time (status, create_time),
ALGORITHM=INPLACE, LOCK=NONE;
-- 检查是否支持Online DDL
-- ALGORITHM=INPLACE: 不复制全表数据
-- LOCK=NONE: 允许并发DML操作
-- 如果返回错误,说明该操作不支持在线执行
-- 对于超大表(亿级),使用pt-online-schema-change
pt-online-schema-change \
--alter "ADD INDEX idx_status_time (status, create_time)" \
--execute \
D=db_name,t=orders,h=127.0.0.1,u=admin,p=password
pt-online-schema-change的原理是创建影子表,通过触发器同步增量数据,最后原子切换表名。执行过程中业务完全不受影响,但会占用额外磁盘空间(约等于原表大小)。执行前评估磁盘空间是否充足,大表建议在低峰期执行。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-man-cha-xun-zhen-duan-yu-suo-yin-you-hua-shi-zhan-zhi/