SQL优化:从”这查询怎么跑不动”到”飞一般的感觉”

2026-08-21 9 0

SQL优化:从"这查询怎么跑不动"到"飞一般的感觉"

做后端开发这么多年,我见过最离谱的事情就是:一个看似简单的查询,硬是能把数据库跑成PPT。不是数据量有多大,而是写法蠢得像在用筷子喝汤。今天就来聊聊SQL优化那些事儿,保证全程无尿点,干货满满。

一、你以为的"简单查询"可能是个灾难

先来看个真实案例。我之前接手一个老项目,有段代码大概是这样的:

SELECT * FROM orders 
WHERE YEAR(created_at) = 2024 AND MONTH(created_at) = 8;

看着没问题对吧?甚至还挺工整。但实际执行的时候,数据库哭了——因为它要对每一行数据都调用YEAR()和MONTH()函数,相当于给每一行数据都做了次"全身检查"。

正确做法是什么?

SELECT * FROM orders 
WHERE created_at >= '2024-08-01' AND created_at < '2024-09-01';

这样数据库只需要做一次范围扫描,索引也能用上。这就是传说中的函数包裹索引列,索引两行泪

二、EXPLAIN是你的眼睛,别黑着用

很多人写SQL跟抽奖似的,跑了再说。这是非常危险的行为。我的习惯是:任何可能返回大数据集的查询,先EXPLAIN看看执行计划。

EXPLAIN SELECT u.name, COUNT(o.id) as order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.status = 'active'
GROUP BY u.id;

重点关注这几个字段:

  • type: 最好的是const,最差的是ALL(全表扫描)。看到ALL你就得小心了
  • key: 实际用到的索引,如果NULL,恭喜你中招了
  • rows: 扫描行数,几十上百万的话你就等着吧
  • Extra: Using filesort、Using temporary这些词出现准没好事

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

很多人知道要建索引,但建得一言难尽。我见过最离谱的是给每个字段都建了索引,美其名曰"全方位优化"。兄弟,这是数据库不是菜市场,不用搞地毯式轰炸。

建索引的核心原则就三条:

  1. WHERE子句里用到的列——这是最优先的
  2. JOIN的连接条件——保证多表关联不走全表扫描
  3. ORDER BY的列——避免该死的filesort

有个小技巧:联合索引遵守最左前缀原则。比如你建了(status, created_at, name)这个索引,那:

-- 能用到索引
WHERE status = 'active' AND created_at > '2024-01-01'
WHERE status = 'active'

-- 用不到索引
WHERE created_at > '2024-01-01'
WHERE name = '张三'

就像你坐电梯,必须从一楼进才能到三楼,直接跳到三楼?物业不让你进。

四、分页优化:深分页是个大坑

常见的分页写法:

SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;

当数据量大了之后,OFFSET越大,数据库越痛苦。因为它要先扫描前100万条,然后扔掉,再返回10条。就跟你要点外卖,结果外卖小哥先绕地球一圈再给你送一样。

正确的深分页姿势是游标分页:

-- 第一页
SELECT * FROM orders ORDER BY id LIMIT 10;

-- 后续页面:记住最后一行的id
SELECT * FROM orders 
WHERE id > 1000000 ORDER BY id LIMIT 10;

不管翻到第几页,查询时间都是稳定的O(1)级别,而不是O(n)。这才是真正的用户体验。

五、告别SELECT *,从你我做起

SELECT * 是万恶之源。多少人栽在这三个字上。它不仅会增加网络传输量,还可能导致索引失效(当需要的数据不在索引里的时候)。

正确做法:只查你需要的字段。

-- 蠢
SELECT * FROM users WHERE id = 1;

-- 聪明
SELECT id, name, email FROM users WHERE id = 1;

尤其是表结构变更(比如加了个大字段TEXT)的时候,SELECT *能让你原地爆炸。

六、批量插入的正确姿势

如果有人告诉你插入1000条数据要循环1000次INSERT,那这人可能还没入门。正确方式是:

-- 错误:1000次数据库交互
for order in orders:
    INSERT INTO orders VALUES (...)

-- 正确:一次数据库交互
INSERT INTO orders (name, amount) VALUES 
('订单1', 100), ('订单2', 200), ('订单3', 300);

大部分数据库支持一次插入几百到几千条数据。用得好,性能能差出一个银河系。

七、一句话总结

SQL优化不是什么高深莫测的技术,就是几个好习惯:查执行计划、看索引、看扫描行数、少用SELECT *、深分页用游标、批量操作要打包。做到了,你就是团队里那个"数据库怎么这么快"的人。

当然,如果你有更好的优化经验,欢迎来撕。毕竟写代码这件事,交流才能进步,闭门造车只会让你造出拖拉机。

相关文章

让部署成为一种享受,而不是一场噩梦 🦞
一个nil指针引发的血案:分布式系统里,那些你忽略的时钟问题比bug更致命
你的REST API正在默默杀人:五个让前端想砍死你的设计
你以为 ORDER BY 很快?我用一次血案告诉你什么叫Too Young
Go语言的context:那些年我踩过的坑,比你踩过的键盘还多
写API接口这事儿,比你想象的坑多多了

发布评论