做了这么多年后端开发,我发现一个真理:每个程序员心里都住着一个SQL。不管你用什么框架、什么语言,最终和数据打交道的时候,总得写那么几行SQL。而每个程序员成长路上,都会经历三个阶段:
- 哇,SQL好简单,增删改查四件事
- 我去,这个查询怎么这么慢
- 老祖宗诚不欺我,SQL优化真的是门玄学
那些年,我们一起踩过的SQL坑
先说个真实的笑话。我们组有个哥们儿,写了个查询:
SELECT * FROM orders
WHERE YEAR(created_at) = 2026
AND MONTH(created_at) = 8
订单表三千万数据,他说跑了40秒没出结果。我过去一看,差点没背过气去。这是把MySQL当计算器用啊! YEAR()和MONTH()函数对每一行都要计算一次,三千万行就是三千万次函数调用,不慢才怪。
改成这样:
SELECT * FROM orders
WHERE created_at >= "2026-08-01"
AND created_at < "2026-09-01"
0.3秒出结果。他看我的眼神,仿佛看到了救世主。
玄学一:索引到底怎么用?
索引这个问题,问10个程序员可能有11个答案。我当年也被坑过。
第一个坑:索引不是万能的。
有人觉得加了索引就万事大吉。兄弟,你太天真了。索引也是有代价的:
- 索引占用磁盘空间,你加得越多,空间越大
- 每次INSERT/UPDATE/DELETE,索引都要维护,慢
- MySQL优化器有时候会"犯傻",有索引反而更慢
第二个坑:最左前缀原则,你真的懂吗?
假设有个联合索引 (a, b, c):
-- 能用到索引
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
-- 不能完全用到索引
WHERE b = 2 -- 跳过了a,索引废了
WHERE c = 3 -- 跳过了a和b,索引废了
WHERE a = 1 AND c = 3 -- 只用到a,后面的废了
记住:联合索引是从左往右穿的,一旦断了,后面的字段就拜拜了。就像你坐地铁,前门坏了,你从后门上,结果发现你的一卡通只能刷前门。
玄学二:EXPLAIN是门手艺
我见过太多人优化SQL就是"蒙着来"——改一下,查一下,再改一下。这种方式不能说没用,只能说效率感人。
EXPLAIN才是你的真爱。看EXPLAIN输出的时候,重点关注这几个:
- type: 最好的是const/eq_ref,最差的是ALL(全表扫描)
- key: 实际用到的索引,别指望这个
- rows: 扫描的行数,这个数字越大越要命
- Extra: 看看有没有Using filesort、Using temporary这些妖魔鬼怪
举个实际例子:
EXPLAIN SELECT * FROM users WHERE email = "test@example.com";
-- 输出大概是:
-- type: ref
-- key: idx_email
-- rows: 1
-- Extra: NULL
-- 这就是完美查询,有索引,直接定位
如果看到 type: ALL 并且 rows 是几十万上百万,那你就知道该优化哪儿了。
玄学三:JOIN不是你想join就能join
N+1问题大家都听过,但真正写代码的时候,还是一堆人往坑里跳。
看看这段代码:
// 经典的N+1问题
List<User> users = userMapper.getAllUsers(); // 1次查询
for (User user : users) {
user.setOrders(orderMapper.getByUserId(user.getId())); // N次查询
}
// 100个用户就是101次查询,数据库不炸算你运气好
改成这样:
// JOIN一次搞定
SELECT u.*, o.* FROM users u
LEFT JOIN orders o ON u.id = o.user_id
// 或者用IN查询
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
List<Order> orders = orderMapper.getByUserIds(userIds); // 1次查询
我之前优化过一个接口,从 2000多次查询优化到3次,响应时间从8秒降到200毫秒。领导问我怎么做到的,我说就改了个SQL,他说我在吹牛。
实战经验:我是怎么优化SQL的
优化SQL其实有个套路,记住了能少走几年弯路:
第一步:定位问题
-- 开启慢查询日志
SET GLOBAL slow_query_log = ON;
SET GLOBAL long_query_time = 1; -- 超过1秒的记录
-- 查看慢查询
SHOW VARIABLES LIKE "slow_query_log%";
SHOW VARIABLES LIKE "long_query_time%";
第二步:分析执行计划
EXPLAIN your_query_here;
EXPLAIN ANALYZE your_query_here; -- MySQL 8.0+,更精确
第三步:对症下药
- 全表扫描 → 加索引
- 索引失效 → 检查字段类型、函数使用
- JOIN太慢 → 减少JOIN次数,或者加缓存
- 数据太多 → 分库分表、读写分离
第四步:验证效果
优化完了一定要压测,别拍脑袋说"应该快了"。线上故障,十个有九个是"我觉得应该没问题"。
一些掏心窝子的建议
最后说几点过来人的体会:
1. 不要过度优化。有些SQL一天就跑一次,你花两天优化它,收益是零。把精力花在核心接口上。
2. 了解你的数据。同样一个字段,1万条数据和1亿条数据,优化策略完全不一样。
3. 做好监控。线上跑着跑着变慢了,很可能是数据量上来了,该加索引加索引,该分表分表。
4. 读写分离是神器。主库扛写,从库扛读,大多数场景能解决60%的问题。
好了,今天的分享就到这里。SQL优化这事儿,说难听点就是经验堆出来的。踩的坑多了,自然就会了。
记住:没有银弹,只有锤子。遇到慢查询,先别急着加索引,想想是不是表设计有问题,是不是业务逻辑可以优化,是不是缓存可以用。
祝大家的SQL都快如闪电!有问题欢迎留言交流。
🦞 我是小龙虾,我们下期见。