一次慢查询排查:从慢日志到改写 SQL

米阳 2022-4-9 4 4/9

有天下午开始,列表页偶尔要等五六秒才出来。不是一直慢,是“偶尔”,这种最难查。

先打开慢日志

SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1;
SHOW VARIABLES LIKE 'slow_query_log_file';

等了一阵,日志里出现了这个:

# Query_time: 5.82  Rows_examined: 412033
SELECT * FROM article WHERE status = 'publish' AND lang = 'zh' ORDER BY created_at DESC LIMIT 20;

扫描了 41 万行,但表里只有几千篇文章。明显是排序没走索引。

看执行计划

EXPLAIN SELECT ...;

type=ALL,Extra: Using filesort。原因是 status 和 lang 两个条件上都有索引,但 created_at 上没有能配合的索引;MySQL 要么先过滤再排序(filesort,要临时表),要么走排序索引再回表过滤(可能扫更多行)。优化器选错了。

建联合索引

按“等值条件在前、排序列在后”的原则:

ALTER TABLE article ADD INDEX idx_status_lang_ctime (status, lang, created_at);

这样 status、lang 两个等值条件先定位,created_at 天然有序,直接取前 20 行就停,不需要 filesort。

再次 explain:type=ref,rows=20,Extra 里 filesort 消失,查询从 5.8 秒降到 30 毫秒。

一个反直觉的点

我最初想的是“给 created_at 单独建索引”。但只建 created_at 的话,MySQL 会沿索引倒序扫,逐行回表判断 status/lang,如果最近的几千篇都是英文或草稿,就会一直扫下去才凑够 20 行。慢日志里那 41 万行扫描,就是这么来的。

联合索引把过滤和排序合到一棵树里,才是对的。

另外两件事

  1. SELECT * 换成只取需要的列,能省掉大量回表。列表页只要标题、时间、摘要,没必要把正文拉出来。
  2. 加索引之前用 pt-duplicate-key-checker 之类的工具看一眼现有索引,我差点又建了一个和已有索引前缀重复的。

慢查询不可怕,怕的是没有数据支撑就猜。先把慢日志开起来,比什么都强。

- THE END -

米阳

4月09日10:50

最后修改:2022年4月9日
0

非特殊说明,本博所有文章均为博主原创。

共有 0 条评论