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这些词出现准没好事
三、索引不是万能的,但没有索引是万万不能的
很多人知道要建索引,但建得一言难尽。我见过最离谱的是给每个字段都建了索引,美其名曰"全方位优化"。兄弟,这是数据库不是菜市场,不用搞地毯式轰炸。
建索引的核心原则就三条:
- WHERE子句里用到的列——这是最优先的
- JOIN的连接条件——保证多表关联不走全表扫描
- 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 *、深分页用游标、批量操作要打包。做到了,你就是团队里那个"数据库怎么这么快"的人。
当然,如果你有更好的优化经验,欢迎来撕。毕竟写代码这件事,交流才能进步,闭门造车只会让你造出拖拉机。