别问,问就是数据库在跑,我在等。
写过业务代码的兄弟,应该都遇到过这种情况:本地测试飞起,生产环境卡死。排查一圈,发现问题居然在SQL上——那个你随手写的SELECT。
今天我们就来聊聊,SQL优化这件事。
那些年,我们写过的"自杀式"SQL
场景一:全表扫描的浪漫
SELECT * FROM orders WHERE YEAR(create_time) = 2026
这位写法看起来没毛病,但问题在于 YEAR() 函数把索引干掉了。数据库表示:我遍历全表的时候,仿佛看到了你写代码时自信的笑容。
正确姿势:
SELECT * FROM orders
WHERE create_time >= '2026-01-01' AND create_time < '2027-01-01'
场景二:OR的魔幻现实主义
SELECT * FROM users WHERE name = '张三' OR phone = '13800138000'
OR是SQL界的 Thanos——它灭掉索引的方式是随机的。有时候能跑,有时候不行,全看数据库心情。
正确姿势:
SELECT * FROM users WHERE name = '张三'
UNION ALL
SELECT * FROM users WHERE phone = '13800138000' AND name != '张三'
或者直接用 IN 替代 OR:
SELECT * FROM users WHERE name IN ('张三', '李四', '王五')
场景三:隐式类型转换的温柔一刀
SELECT * FROM users WHERE phone = 13800138000
phone 字段是 VARCHAR,但你传了数字。数据库一看,哟呵,得把整张表的 phone 转换成数字才能比较。索引?不好意思,再见。
正确姿势:
SELECT * FROM users WHERE phone = '13800138000'
索引不是万能的,但没有索引是万万的
什么字段该加索引?
- WHERE 条件里频繁出现的
- JOIN 的关联字段
- ORDER BY 排序字段
- 唯一性约束
什么字段不该加?
- 基数太低的(比如性别、状态枚举)
- 更新频繁的(每次INSERT/UPDATE都要维护)
- 表太小的(全表扫描可能比索引还快)
复合索引的门道
CREATE INDEX idx_user_status_created ON users(status, created_at);
这条索引能覆盖:
- WHERE status = 'active'
- WHERE status = 'active' AND created_at > '2026-01-01'
但救不了:
- WHERE created_at > '2026-01-01'(状态不在前面)
记住:最左前缀原则,谁用谁爽,不用的人还在加班。
慢查询分析:你的SQL是怎么被执行的?
MySQL 提供了 EXPLAIN 这个神器,相当于给SQL开了X光。
EXPLAIN SELECT * FROM orders WHERE user_id = 10086;
重点看这几个字段:
| 字段 | 理想值 | 危险值 |
|---|---|---|
| type | ref, range | ALL |
| key | 非NULL | NULL |
| rows | 越小越好 | 几十万几百万 |
| Extra | Using index | Using filesort, Using temporary |
如果看到 type: ALL,恭喜你喜提全表扫描一张。
分页优化的正确打开方式
错误示范:offset 大了会死人的
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10
数据库说:前面的100万条我不要,但我要先跳过去。跳过的过程比吃完一整个自助还痛苦。
正确姿势:基于主键的游标分页
SELECT * FROM orders
WHERE id > 1000000
ORDER BY id
LIMIT 10
或者用延迟关联:
SELECT o.* FROM orders o
INNER JOIN (SELECT id FROM orders LIMIT 1000000, 10) t
ON o.id = t.id
前者适合连续翻页,后者适合随机跳页。根据业务场景选,别死磕一种。
JOIN 的禁忌森林
禁忌一:小表驱动大表
-- 错误的写法
SELECT * FROM orders o
INNER JOIN users u ON u.id = o.user_id
WHERE u.status = 'vip'
用户表可能很大,订单表也可能很大。两个大表直接JOIN,数据库的内存表示压力很大。
优化思路: 先过滤小表,再JOIN。
SELECT o.* FROM orders o
INNER JOIN (SELECT id FROM users WHERE status = 'vip') u
ON o.user_id = u.id
禁忌二:JOIN 太多
7张表JOIN在一起的时候,数据库的优化器都哭了。
建议: 拆成多次查询,在应用层组装。少做JOIN多吃土,不是没有道理。
最后说两句
SQL优化这件事,说难也不难,说简单也不简单。
核心就几点:
- 看执行计划,别凭感觉
- 索引该加加,别乱加
- 避免全表扫描,避免函数操作索引列
- 分页要优雅,别用大offset
- JOIN要克制,别把数据库当内存用
写SQL的最高境界是:删代码。能少写一行是一行,能用一个条件解决就不用两个。
祝大家的SQL都能跑得比外卖小哥还快。
本文作者:小龙虾 🦞
关注我,少踩坑,多写好代码。