前几天帮同事排查一个系统卡顿的问题,发现根源出在一条SQL上。页面加载要十几秒,一查日志,原来是数据库拖了后腿。拿到这条SQL后,第一反应不是改写,也不是加索引,而是先看它的执行计划。
为什么执行计划这么重要?
很多人遇到慢查询,第一反应是“加个索引试试”。可现实是,有时候加了索引也没用,甚至更慢。原因在于你不知道数据库是怎么执行这条SQL的。就像开车导航,如果不看路线图,光猛踩油门,只会越走越偏。
执行计划就是数据库的“行车路线图”,它告诉你:表是怎么扫描的、走了哪个索引、有没有做嵌套循环、是否触发了临时表或文件排序。
怎么看执行计划?用EXPLAIN就够了
在MySQL里,只需要在SQL前面加个 EXPLAIN,就能看到执行计划。比如:
EXPLAIN SELECT * FROM users WHERE email = 'john@example.com';
执行后你会看到一堆字段,其中几个关键的得盯住:
- type:访问类型,从 const 到 ALL,越靠前越好。ALL 意味着全表扫描,基本可以判定有问题。
- key:实际使用的索引。如果这里是 NULL,那你写的索引可能没生效。
- rows:预估扫描行数。如果是几万甚至几十万,就得警惕了。
- Extra:额外信息,出现 Using filesort 或 Using temporary 基本意味着性能隐患。
一个真实案例:明明有索引却没走
有次写了个查询:
SELECT * FROM orders WHERE YEAR(create_time) = 2024;
虽然 create_time 字段上有索引,但 EXPLAIN 显示 type=ALL,key=NULL。问题出在函数 YEAR() 上——对字段使用函数会导致索引失效。
改成范围查询就解决了:
SELECT * FROM orders WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
再看执行计划,type 变成了 range,key 也用上了,rows 从 50 万降到几千。
别只盯着单条SQL,联合查询更要看执行顺序
多表关联时,EXPLAIN 的输出顺序其实是执行顺序。数据库会决定哪张表当驱动表。通常小结果集的表应该放在前面,否则容易引发笛卡尔积。
比如下面这条:
EXPLAIN SELECT * FROM users u JOIN orders o ON u.id = o.user_id WHERE u.status = 1;
如果 users 表中 status=1 的数据很少,那它就应该当驱动表。但如果执行计划显示 orders 是第一行,那就说明优化器可能判断失误,这时候可以用 STRAIGHT_JOIN 强制顺序,或者加 Hint 提示。
不同数据库语法略有差异
MySQL 用 EXPLAIN,PostgreSQL 也一样。Oracle 要用 EXPLAIN PLAN FOR,然后查 plan_table。SQL Server 则支持 SET SHOWPLAN_ALL ON 或图形化执行计划。虽然写法不同,但核心思路一致:看扫描方式、看索引使用、看数据量级。
养成习惯:上线前先过一遍执行计划
现在我写完一条复杂SQL,第一件事就是 EXPLAIN 一下。哪怕看起来很简单,也可能因为数据分布、统计信息不准导致执行路径异常。特别是在分页查询、模糊匹配、IN 子句这些场景下,执行计划很容易“翻车”。
有一次线上加了个 IN 查询,本地测试没问题,结果上线后数据库CPU直接拉满。一查执行计划,发现 IN 里的子查询被展开成多个全表扫描。换成临时表+JOIN 后,压力立马下来了。
执行计划不是高级功能,它是每个写SQL的人都该掌握的基本功。别等系统崩了才想起看它。