蓝易云MySQL 之优化 SELECT 语句把“慢”拆成可控的成本⚡️优化 SELECT 的本质是做三件事让优化器选到更好的执行计划、让存储引擎少读数据页、让返回路径少做无效工作。你可以把一次查询的成本粗略理解为[\text{总成本} \approx \text{扫描行数} \times \text{每行代价} \text{排序/分组代价} \text{回表代价}]目标就是把扫描行数、回表次数、排序/临时表这三块压下去。1先定位用 EXPLAIN 把“猜测”变“证据”EXPLAIN SELECT id, user_id, amount FROM orders WHERE user_id 10086 AND status 1 ORDER BY created_at DESC LIMIT 20;解释逐行抓重点EXPLAIN不跑结果只输出执行计划用来判断有没有走到你期望的索引。SELECT id, user_id, amount只取必要字段减少 IO 与网络传输避免SELECT *导致多余列读取。WHERE user_id ... AND status ...等值过滤是索引最喜欢的形态。ORDER BY created_at DESC LIMIT 20如果能用同一个索引顺序返回就能避免额外排序否则容易出现“额外排序/临时表”的信号。2索引让查询“走对路”并尽量做到 覆盖索引 ✅ALTER TABLE orders ADD INDEX idx_user_status_ct (user_id, status, created_at, id);解释ALTER TABLE ... ADD INDEX创建索引是优化 SELECT 的最高杠杆之一。idx_user_status_ct (user_id, status, created_at, id)user_id, status放前面对应WHERE的高频过滤列让扫描范围迅速收窄。created_at接着配合ORDER BY created_at有机会直接“按索引顺序”取前 20 条减少排序。id放末尾常用于让索引更容易覆盖查询或减少回表是否需要取决于查询返回列。核心目标让查询尽量变成Index Range Scan 少回表甚至直接覆盖索引返回字段都在索引里。3WHERE 写法别让索引“失明”-- 反例对列做函数运算容易导致索引失效 SELECT * FROM orders WHERE DATE(created_at) 2026-03-04; -- 正例改成范围查询 SELECT * FROM orders WHERE created_at 2026-03-04 00:00:00 AND created_at 2026-03-05 00:00:00;解释反例DATE(created_at)把列先计算再比较索引通常无法按原值做范围定位扫描量会膨胀。正例用时间范围让created_at能参与索引范围扫描扫描行数显著下降。同类坑还有隐式类型转换字符串比数字、前导通配符LIKE %abc、OR混合多列导致难以用单一索引。4分页用“定位翻页”替代 OFFSET 大跳跃 -- 慢OFFSET 越大越慢MySQL 需要跳过前 N 行 SELECT id, created_at FROM orders WHERE user_id 10086 ORDER BY created_at DESC LIMIT 20 OFFSET 200000; -- 快基于“上一页最后一条”的定位翻页Seek Method SELECT id, created_at FROM orders WHERE user_id 10086 AND created_at 2026-03-04 12:00:00 ORDER BY created_at DESC LIMIT 20;解释OFFSET 200000本质是“读出来再丢掉”越翻越慢。定位翻页把上一页末尾的created_at或(created_at,id)当游标直接从索引位置继续扫成本稳定。若存在相同时间戳建议用created_atid作为复合游标避免漏/重复。5高频慢点与对策一张表对齐思路症状典型原因直接打法扫描行数巨大索引缺失 / 条件不可用索引补联合索引把函数/隐式转换改成范围或等值排序很慢ORDER BY无法用索引顺序把排序列纳入联合索引并匹配顺序减少排序字段回表太多只用到二级索引但要取很多列做 覆盖索引只取必要列JOIN 慢驱动表不对/条件不走索引小表驱动大表ON/WHERE列补索引只 JOIN 必要字段分组慢临时表/文件排序用索引前缀分组减少 group by 列先过滤再聚合6优化落地工作流团队协作更顺flowchart TD A[抓慢SQL样本] -- B[EXPLAIN验证计划] B -- C{扫描行数高?} C --|是| D[补索引/改WHERE写法] C --|否| E{排序/临时表高?} E --|是| F[调整ORDER BY/GROUP BY与索引顺序] E --|否| G{回表多?} G --|是| H[覆盖索引/减少返回列] G --|否| I[验证收益与回归测试] I -- J[上线监控与复盘]最后一句务实建议 ✅优先级永远是先少扫 → 再少排 → 最后少回表。把这条链路跑顺90% 的 SELECT 慢问题都会从“玄学”变成“工程问题”。