慢查询日志的配置与采集
MySQL慢查询优化第一步是确认慢查询日志真的打开了。生产环境开启slow_query_log,把超过阈值(默认10秒,建议改2秒或1秒)的SQL记录下来。同时打开log_queries_not_using_indexes,让没走索引的查询也进日志,这类SQL往往是性能隐患。
-- 查看当前配置
SHOW VARIABLES LIKE 'slow_query%';
-- 动态开启
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
设置完要确认my.cnf里持久化,否则重启丢失。实际运维中可以直接用mysqldumpslow或pt-query-digest分析日志文件,按执行次数、耗时排序,找出TOP SQL。
EXPLAIN执行计划:从type到rows的解读
EXPLAIN是优化SQL的基础工具。重点关注几个字段:type(访问类型,最好const>eq_ref>ref>range>index>ALL)、key(实际用的索引)、rows(预估扫描行数)、Extra(Using filesort、Using temporary都要消除)。
EXPLAIN SELECT * FROM orders
WHERE user_id = 100 AND status = 'PAID'
ORDER BY created_at DESC;
type为ALL说明全表扫描,rows接近全表数据量,这个查询必须加索引。Extra出现Using filesort意味着排序没走索引,order by字段要和where条件组成联合索引。
联合索引设计与最左前缀原则
联合索引按最左前缀匹配,where里第一个条件必须命中索引最左列。建索引的顺序原则:等值条件放前面,范围条件放后面;区分度高的列放前面,避免索引树过于膨胀。
-- 覆盖查询的联合索引
ALTER TABLE orders ADD INDEX idx_user_status_time (user_id, status, created_at);
上例中user_id等值、status等值、created_at排序,联合索引一次索引遍历就拿到全部数据,连回表都省了,这就是覆盖索引。如果把范围条件的status放前面,会导致后续索引列无法参与匹配。
分页深翻与COUNT的优化
LIMIT 1000000, 20这种深分页会先扫描前100万行再丢弃,代价极高。改用上一页最大ID做游标:WHERE id > 上一页最大id LIMIT 20,配合主键索引,翻页性能稳定。如果必须用offset,可以先取id再JOIN。
-- 游标分页,性能稳定
SELECT * FROM orders
WHERE id > 10000000
ORDER BY id
LIMIT 20;
COUNT全表统计在千万级表上很慢,对频繁执行的统计查询建汇总表或缓存计数,别每次都扫全表。
SQL优化的常见雷区与线上验证
几个高频坑:函数包裹字段(WHERE DATE(create_time)=…索引失效,改范围查询);隐式类型转换(varchar列与数字比较,索引失效);OR条件无法合并;不等于(!=)让索引失效。
-- 错误写法:函数包裹,索引失效
WHERE DATE(create_time) = '2026-08-25'
-- 正确写法:范围查询,命中索引
WHERE create_time >= '2026-08-25 00:00:00'
AND create_time < '2026-08-26 00:00:00'
优化后一定要在压测环境下对比执行时间与资源消耗,同样用EXPLAIN看rows变化。索引不是越多越好,每个索引都会拖慢写入,只保留高频查询命中的索引。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/mysql-man-cha-xun-fen-xi-yu-sql-you-hua-shi-zhan-zhi-xing/