前言:为什么你的 SQL 这么慢?
很多刚入行的开发者认为:SQL 只要能跑通就行,性能是 DBA 的事。
直到有一天,数据量从 1 万涨到了 100 万,原本秒开的页面变成了 10 秒加载,CPU 告警,数据库连接池爆满。
这时候你才发现:代码写的烂,服务器再强也带不动。
SQL 优化不是玄学,它是有迹可循的。今天我们就来聊聊,如何把那些“龟速” SQL 变成“火箭”。
第一招:拒绝“全家桶” —— 别用 SELECT * ❌
这是新手最容易犯的错误,没有之一。
❌ 烂代码:
SELECT * FROM users WHERE age > 18;✅ 优化后:
SELECT id, username, avatar FROM users WHERE age > 18;为什么?
- 网络开销:
*会查出所有字段,如果表里有一个很大的TEXT或BLOB字段(比如存了文章详情),即使你不需要它,数据库也会把它读出来传输给你,占用带宽。 - 失去“覆盖索引”机会:如果你只查
id和username,而这两个字段刚好建了索引,数据库甚至不需要回表(去查实际的数据行),直接从索引树里就能把数据给你,速度快 10 倍。
第二招:诊断神器 —— 学会看 EXPLAIN 🩺
不知道 SQL 为什么慢?盲目猜?
MySQL 自带了一个诊断命令:EXPLAIN。在你的 SQL 前面加上它,就能看到执行计划。
操作:
EXPLAIN SELECT * FROM orders WHERE user_id = 100;重点看这三列:
type (访问类型):
ALL:全表扫描(最差,数据量大时必须优化)。index:全索引扫描(也很差)。range:索引范围查找(合格,用于 >、<、between)。ref/eq_ref:精确索引匹配(很好)。const:主键查询(最快)。
- key (使用的索引):显示实际用到了哪个索引。如果是
NULL,说明没用上索引。 - rows (扫描行数):估计要扫描多少行。这个数字越小越好。
口诀:看到 ALL 就要慌,看到 NULL 要抓狂。
第三招:索引失效的“四大雷区” 💣
建了索引就万事大吉了?No!如果你写的方式不对,索引会失效,退化成全表扫描。
1. 不要在索引列上做运算
❌ 失效:
-- 对 create_time 做了函数计算
SELECT * FROM orders WHERE YEAR(create_time) = 2025;✅ 优化:
-- 改成范围查询
SELECT * FROM orders WHERE create_time BETWEEN '2025-01-01' AND '2025-12-31';2. 模糊查询别把 % 放最前面
❌ 失效:
-- 相当于全书查阅,没法利用字典序
SELECT * FROM users WHERE name LIKE '%大卫';✅ 优化:
-- 只有后缀匹配才走索引
SELECT * FROM users WHERE name LIKE '大卫%';3. 字符串不加单引号
❌ 失效:
-- phone 是 varchar 类型,这里写成了数字,触发隐式类型转换
SELECT * FROM users WHERE phone = 13800138000;✅ 优化:
SELECT * FROM users WHERE phone = '13800138000';4. 范围查询右边的列失效
如果你有一个组合索引 (a, b, c)。
当 a > 10 时,b 和 c 的索引就用不上了。所以要把范围查询放在条件的最后。
第四招:深分页噩梦 —— 优化 LIMIT 🐢
场景:管理后台翻页,点到第 10000 页时,系统卡死。
❌ 烂代码:
-- 数据库需要扫描 1000010 行,然后扔掉前 1000000 行
SELECT * FROM logs LIMIT 1000000, 10;✅ 优化方案 1 (延迟关联/子查询):
先在索引树上快速找到这 10 个 ID(因为不回表,速度极快),然后再去查详情。
SELECT a.* FROM logs a
INNER JOIN (
SELECT id FROM logs LIMIT 1000000, 10
) b ON a.id = b.id;✅ 优化方案 2 (最大 ID 法 - 仅限连续 ID):
如果你知道上一页最后的 ID 是多少。
SELECT * FROM logs WHERE id > 1000000 LIMIT 10;第五招:小表驱动大表 (IN vs EXISTS) 🤏
假设你有两张表:User (小表,100人) 和 Order (大表,100万条)。
场景 1:查哪些用户有订单
这里应该用 IN,先查出 User 的 ID,再去 Order 里找。
SELECT * FROM User WHERE id IN (SELECT user_id FROM Order); -- 慎用,逻辑可能反更正:通常业务是“查大表中的数据,且该数据在小表中存在”。
口诀:
IN:适合 子查询的结果集小 的情况(外表大,内表小)。EXISTS:适合 子查询的结果集大 的情况(外表小,内表大)。
例子:
如果 Order 表巨大,User 表很小,要查“所有下过单的用户信息”:
✅ 推荐 (EXISTS - 小表 User 驱动):
SELECT * FROM User u
WHERE EXISTS (SELECT 1 FROM Order o WHERE o.user_id = u.id);这里主查询扫 User 表(少),每一行去 Order 表利用索引判断是否存在(快)。
总结
SQL 优化其实就是一场“减少磁盘 I/O”的游戏。
- 只拿你需要的 (
SELECT *达咩)。 - 让数据库少走弯路 (正确使用索引)。
- 先看地图再出发 (养成用
EXPLAIN的习惯)。 - 避免做无用功 (深分页优化)。
把这些习惯融入到日常开发中,你会发现你的系统不仅快,而且稳。面试时聊起这些,也能让面试官眼前一亮。