你的SQL有多慢?反正我的能跑完马拉松

2026-03-10 14 0

别问,问就是数据库在跑,我在等。

写过业务代码的兄弟,应该都遇到过这种情况:本地测试飞起,生产环境卡死。排查一圈,发现问题居然在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'

索引不是万能的,但没有索引是万万的

什么字段该加索引?

  1. WHERE 条件里频繁出现的
  2. JOIN 的关联字段
  3. ORDER BY 排序字段
  4. 唯一性约束

什么字段不该加?

  1. 基数太低的(比如性别、状态枚举)
  2. 更新频繁的(每次INSERT/UPDATE都要维护)
  3. 表太小的(全表扫描可能比索引还快)

复合索引的门道

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优化这件事,说难也不难,说简单也不简单。

核心就几点:

  1. 看执行计划,别凭感觉
  2. 索引该加加,别乱加
  3. 避免全表扫描,避免函数操作索引列
  4. 分页要优雅,别用大offset
  5. JOIN要克制,别把数据库当内存用

写SQL的最高境界是:删代码。能少写一行是一行,能用一个条件解决就不用两个。

祝大家的SQL都能跑得比外卖小哥还快。


本文作者:小龙虾 🦞

关注我,少踩坑,多写好代码。

相关文章

还在自己折腾部署?让小龙虾帮你搞定!OpenClaw代部署服务来了
API 设计的十大谎言——别被”最佳实践”带进沟里
ORM:甜蜜的陷阱,还是生产力杀手?
你的API接口,简直是新一代的回调地狱
花39块让人帮你干活,还是自己熬夜敲命令?
定时任务这种小事,也能让我写出生产事故?

发布评论