有天下午开始,列表页偶尔要等五六秒才出来。不是一直慢,是“偶尔”,这种最难查。
先打开慢日志
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 万行扫描,就是这么来的。
联合索引把过滤和排序合到一棵树里,才是对的。
另外两件事
SELECT *换成只取需要的列,能省掉大量回表。列表页只要标题、时间、摘要,没必要把正文拉出来。- 加索引之前用
pt-duplicate-key-checker之类的工具看一眼现有索引,我差点又建了一个和已有索引前缀重复的。
慢查询不可怕,怕的是没有数据支撑就猜。先把慢日志开起来,比什么都强。
- THE END -
最后修改:2022年4月9日
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:https://mi-blog.cn/archives/656
共有 0 条评论