你点开监控面板,看到那条SQL的响应时间又飙到几秒——页面卡住,用户划走,日志里慢查询告警一连串弹出来。别急着骂数据库,这事儿我踩过太多坑,也救过不少快挂掉的接口。
今天不讲大道理,就聊几个马上能试、一试就见效的优化动作。每个都来自真实项目,没编造,也没套话。
为什么加了索引查询还是慢?八成是索引失效了
建了索引≠索引在用。执行计划里要是还写着 type: ALL,基本可以确定:索引躺平了。
最常见的情况,是在索引字段上用了函数。比如 WHERE DATE(create_time) = '2024-01-01'。数据库没法拿函数结果去查索引树,只能扫全表。
还有类型不匹配。user_id 是字符串,但代码里传的是数字 123,MySQL 自动转类型,索引就废了。改成 WHERE user_id = '123',立马生效。
模糊查询也容易翻车。LIKE '%keyword%' 开头带 %,索引基本作废;换成 LIKE 'keyword%',就能用上。之前帮一个电商后台改搜索接口,只调了这一处,响应时间缩短了不少。
你真的需要查询所有列吗?用覆盖索引减少回表
SELECT * 看着省事,实际代价不小。数据库先靠索引找到主键,再根据主键回数据页把整行捞出来——多一次磁盘 IO,慢就慢在这儿。
覆盖索引就是让“查什么,索引里就有什么”。比如你经常查 user_id 和 status,又按 user_id 过滤,那就建个 (user_id, status) 的联合索引。SELECT user_id, status FROM orders WHERE user_id = 100,直接从索引里拿完事,不用回表。
之前优化一个订单列表页,原 SQL 是 SELECT * FROM orders WHERE user_id = ? ORDER BY create_time DESC LIMIT 20。我把字段精简成 4 个业务必需的,再在 (user_id, create_time) 上建索引,响应时间明显缩短。
分页越往后越慢?试试延迟关联或游标分页
LIMIT 100000, 20 这种写法,数据库得先数出 100020 行,再扔掉前 10 万。页码越大,扫描越多,慢得理直气壮。
延迟关联是个实招:先用索引快速捞出要的主键,再用这些主键去查完整数据。
-- 慢的写法
SELECT * FROM articles ORDER BY id LIMIT 100000, 20;
-- 快的写法
SELECT a.* FROM articles a
INNER JOIN (SELECT id FROM articles ORDER BY id LIMIT 100000, 20) AS tmp
ON a.id = tmp.id;
内层子查询只走索引,外层精准取数据。之前优化一个 CMS 后台的分页接口,翻到第 500 页时,响应时间大幅下降。
如果排序字段是时间戳这类单调递增的值,更推荐游标分页:记住上一页最后一条的 create_time,下一页直接 WHERE create_time < '2024-01-01 12:00:00' LIMIT 20。没有偏移量,性能稳如老狗。
关联查询慢?可能是驱动表选错了
JOIN 不是随便写的。数据库会挑一个表当“主力”(驱动表),另一个表负责“配合”(被驱动)。主力表应该小一点,不然就得拿几十万行去反复匹配。
有次优化报表 SQL,orders 表百万级,users 表才几千行。原写法是 FROM orders LEFT JOIN users,orders 当主力,每行都要去 users 里找一遍。我改成 FROM users RIGHT JOIN orders,或者加 STRAIGHT_JOIN 强制 users 先查,效果立竿见影。
还有一个硬要求:被驱动表的关联字段必须有索引。比如 orders.user_id 没索引,INNER JOIN users ON orders.user_id = users.id 就得每次扫全表 orders。加上索引后,JOIN 速度明显提升。
排序和分组太慢?试试索引下推或临时表优化
ORDER BY 或 GROUP BY 一旦触发 filesort(文件排序),说明数据撑爆内存,开始往磁盘写了——IO 一上来,响应时间就崩。
解法很直接:把排序字段塞进索引里。比如常按 create_time 排序,那就建 (user_id, create_time) 这样的复合索引。扫描索引时顺序天然就对了,省掉额外排序步骤。
分组慢,很多时候是因为聚合字段没索引。比如 GROUP BY product_id 后算 SUM(amount),但 amount 在数据页里,每次都要来回读。我在 (product_id, amount) 上建索引,让聚合数据直接从索引里拿,响应时间明显变快。
实在搞不定的复杂分组,可以考虑预计算:每天凌晨跑个定时任务,把统计结果写进一张 sales_daily_summary 表里。查的时候直读这张表,虽然数据不是实时的,但对多数运营看板完全够用。
一个今天就能执行的操作步骤
打开你正在用的数据库客户端(比如 DBeaver、Navicat,或者 MySQL 命令行),找一条最近报过慢的 SQL(从慢查询日志、APM 工具或业务监控里复制过来就行)。然后:
用
EXPLAIN看一眼:重点盯type列——如果是ALL或index,说明没走有效索引;再看Extra有没有Using filesort或Using temporary。建一个复合索引:把
WHERE条件里的字段,加上ORDER BY或GROUP BY字段,按顺序组合起来。比如WHERE user_id = ? AND status = ? ORDER BY create_time,就建(user_id, status, create_time)。改 SQL,只查真要用的字段:删掉
SELECT *,换成明确列出的字段,并确认它们全都在上一步建的索引里。再跑一次EXPLAIN,看Extra里有没有出现Using index。
做完这三步,这条 SQL 很大概率会快起来。明天继续挑下一条。坚持两周,你会明显感觉后台接口顺了,监控曲线也平了。