数据库慢查询日志分析的核心就是从慢查询日志中定位执行时间超过阈值的SQL语句,然后通过EXPLAIN执行计划查看扫描行数、索引使用情况、连接类型等关键指标,找到全表扫描、索引失效、回表次数过多等问题,针对性地添加索引、重写SQL或调整表结构来解决性能瓶颈。这不是什么玄学,而是一套有章可循的排查流程,下面我把每一步拆开讲透。

一、慢查询日志怎么开、怎么看

MySQL默认是不记录慢查询的,你得手动开启。在my.cnf或my.ini里加几行配置就行:

slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

long_query_time设为1秒,意思是执行超过1秒的SQL都会被记录。log_queries_not_using_indexes设为1,那些没走索引的查询也会被记下来,这对排查特别有用。PostgreSQL的做法类似,在postgresql.conf里设置:

log_min_duration_statement = 1000
log_checkpoints = on
log_lock_waits = on

日志开了之后,你要学会看内容。一条典型的慢查询日志长这样:

# Time: 2024-01-15T10:23:45.123456Z
# User@Host: app_user[app_user] @ 192.168.1.100
# Query_time: 3.521456  Lock_time: 0.000123  Rows_sent: 10  Rows_examined: 1856432
SET timestamp=1705313025;
SELECT * FROM orders WHERE customer_name LIKE '%张三%' ORDER BY create_time DESC;

重点看三个数字:Query_time是查询耗时3.5秒,Rows_examined是扫描了185万行,Rows_sent只返回了10行。扫描185万行才返回10行,这就是典型的全表扫描,问题很明确了。

二、EXPLAIN执行计划怎么读、怎么用

拿到慢SQL之后,在前面加个EXPLAIN就能看到执行计划。MySQL的EXPLAIN输出有十几列,但你重点关注这几个:type、key、rows、Extra。

type列表示访问类型,从好到差依次是:system > const > eq_ref > ref > range > index > ALL。如果出现ALL,就是全表扫描,必须优化。key列显示实际用了哪个索引,如果是NULL说明没用索引。rows是预估扫描行数,数字越大越慢。Extra里如果出现Using filesort、Using temporary,说明有额外的排序和临时表操作,性能会打折扣。

举个实际例子,假设你执行:

EXPLAIN SELECT * FROM orders WHERE customer_name LIKE '%张三%' ORDER BY create_time DESC;

结果可能显示type=ALL,key=NULL,rows=1856432,Extra=Using where; Using filesort。这就确认了:没走索引、全表扫描、还做了文件排序,三个问题叠加,慢是必然的。

PostgreSQL用EXPLAIN ANALYZE更强大,它不光给你预估,还真的执行一遍告诉你实际耗时:

EXPLAIN ANALYZE SELECT * FROM orders WHERE customer_name LIKE '%张三%' ORDER BY create_time DESC;

输出里会有actual time、actual rows等真实数据,比MySQL的EXPLAIN更靠谱。

三、常见慢查询场景和具体优化手段

场景一:LIKE左模糊导致索引失效。像上面的'%张三%',B+树索引从左往右匹配,左边是通配符根本用不上索引。解决办法有三个:第一,改成右模糊'张三%',这样能走索引;第二,用全文索引(MySQL的FULLTEXT或Elasticsearch);第三,如果业务允许,用覆盖索引加精确匹配替代模糊查询。

场景二:隐式类型转换让索引白搭。比如字段phone是varchar类型,你写WHERE phone = 13800138000,MySQL会把字符串转成数字去比较,索引直接失效。解决方法就是保持类型一致,要么字段改成bigint,要么查询时加引号写成'13800138000'。

场景三:OR条件导致索引失效。比如WHERE status = 1 OR type = 2,如果status和type分别有索引,优化器可能直接放弃索引选择全表扫描。可以用UNION ALL拆成两个查询,各自走索引:

SELECT * FROM orders WHERE status = 1
UNION ALL
SELECT * FROM orders WHERE type = 2;

场景四:JOIN大表没走驱动表索引。两张表JOIN时,小表驱动大表是基本原则。如果大表在前面驱动小表,扫描量会爆炸。用EXPLAIN看驱动表的type,确保是ref或eq_ref级别。

场景五:SELECT * 导致回表开销大。如果你只需要id和name两个字段,却写了SELECT *,InnoDB的聚簇索引要回表取所有列数据,IO开销翻倍。改成只查需要的字段,或者建覆盖索引把常用字段都包含进去。

四、慢查询日志的系统化分析方法

单条SQL优化完了不够,你得有系统化的分析思路。推荐用pt-query-digest工具(Percona Toolkit里的),它能把慢查询日志汇总统计,按执行次数、总耗时、平均耗时排序,直接告诉你哪些SQL是头号杀手:

pt-query-digest /var/log/mysql/slow.log --order-by Query_time:sum --limit 10

这条命令会输出耗时最多的前10条SQL,附带执行次数、平均耗时、扫描行数等统计。你优先优化那些执行频繁又耗时长的,效果立竿见影。

另外要建立慢查询监控告警。不要等用户投诉了才去查日志,应该设置阈值,比如单条SQL超过2秒就告警,或者每分钟慢查询数量超过50条就通知DBA。很多云数据库自带慢查询分析面板,能直接看到Top SQL和趋势图,善用这些工具能省大量人工。

五、索引优化的实操建议

索引不是越多越好,每个索引都有写入开销。建索引要遵循几个原则:第一,高频查询字段优先建索引;第二,遵循最左前缀原则,联合索引(a,b,c)能覆盖a、a+b、a+b+c的查询,但跳过a直接查b或c就用不上;第三,区分度低的字段别单独建索引,比如性别字段只有男和女,建了索引优化器也不会用。

查看索引使用情况可以用:

SELECT * FROM sys.schema_unused_indexes;  -- MySQL 8.0+
SELECT * FROM pg_stat_user_indexes WHERE idx_scan = 0;  -- PostgreSQL

把长期不用的索引删掉,减少维护成本。同时定期用ANALYZE TABLE更新统计信息,让优化器能做出更准确的执行计划选择。

六、执行计划解读的高阶技巧

当你遇到复杂SQL时,执行计划可能出现多层嵌套子查询或者多表JOIN。这时候要从最内层开始看,先确认每个子查询是否走了索引,再看外层如何利用子查询的结果。MySQL 8.0以后支持EXPLAIN FORMAT=JSON,输出结构化数据,方便程序化解析。

还有一个容易忽略的点:执行计划是基于统计信息的估算,不一定完全准确。如果你发现EXPLAIN显示rows=100但实际跑了很久,可能是统计信息过期了,执行ANALYZE TABLE或者OPTIMIZE TABLE刷新一下。PostgreSQL里对应的是ANALYZE和VACUUM ANALYZE。

最后说一个实战经验:很多慢查询根本不是SQL写得差,而是数据量涨了之后原来能跑的查询变慢了。这时候要考虑分库分表、读写分离、或者把热点数据放到缓存里。执行计划能帮你找到SQL层面的问题,但架构层面的优化也得跟上,两者配合才能真正解决性能问题。

总结一下,慢查询日志分析和执行计划解读是数据库性能优化的基本功。开好日志、读懂EXPLAIN、定位具体问题、针对性优化,这四步走下来,绝大多数慢查询都能解决。关键是要养成定期分析的习惯,而不是出了问题才临时抱佛脚。