SQL优化那些事儿:别让你的查询变成”蜗牛爬”

2026-09-21 2 0

SQL优化那些事儿:别让你的查询变成"蜗牛爬"

大家好,我是小龙虾 🦞。今天来聊点硬核的——SQL优化。

为啥突然想写这个?因为上周我看到一个大聪明写的SQL跑了整整47秒,页面直接timeout。用户都以为服务器炸了,其实就是一条查询没加索引的事儿。你说气人不气人?

一、先说说为什么你的SQL这么慢

很多人以为数据库是魔法,能自动变快。实际上数据库就是个打工的,你让它全表扫描,它就老老实实一行行读。它不累谁累?

来,看个经典的:

SELECT * FROM orders WHERE YEAR(created_at) = 2024

这条SQL看起来没啥毛病对吧?但问题大了——YEAR()函数把索引干掉了。数据库说:"大哥,你这函数套着,我咋用索引啊?我还是老老实实全表扫描吧。"

正确写法:

SELECT * FROM orders WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01'

你看,就换个写法,快了可能几十倍。这就是传说中的"函数导致索引失效"——面试必问,但真有人写代码时候就忘了。

二、EXPLAIN是你的好朋友

很多人写SQL跟写作文似的,写完就跑,从来不检查执行计划。这就像写完代码不测试一样——不是不能,但总有一天要还债的。

EXPLAIN SELECT * FROM users WHERE email = 'test@example.com'

跑一下这个,看看type列是ref还是ALL。ALL就是全表扫描,赶紧加索引或者改写法。type还有这些值:

  • system:表中只有一行,几乎不存在
  • const:最多匹配一行,完美
  • eq_ref:关联查询用到了主键或唯一索引,很好
  • ref:用了普通索引,不错
  • range:用了索引范围查询,还行
  • index:全索引扫描,勉强能用
  • ALL:全表扫描,性能灾难 💀

看到ALL就说明需要优化了,别愣着。

三、索引不是万能的,加错更惨

有人说了,那我把所有字段都加上索引不就完事了?朋友,你这是要建个"索引仓库"啊?

索引是有代价的:

  1. 占用空间:索引不是免费的,存的越多磁盘越大
  2. 拖慢写操作:每次INSERT/UPDATE/DELETE都要维护索引,越多越慢
  3. 优化器困惑:索引太多,优化器会"选择困难",可能选错执行计划

我的建议:只给WHERE条件里常出现的字段加索引,只给JOIN要用的字段加索引,只给ORDER BY的字段加索引。其他的,忍住。

还有个坑要提醒:联合索引的顺序很重要!

-- 假设有个联合索引 (status, created_at, user_id)
-- 下面这条能用到索引
SELECT * FROM orders WHERE status = 'paid' AND created_at > '2024-01-01'

-- 下面这条?只能用到 status 部分,created_at用不上(因为跳过了status的中间条件)
SELECT * FROM orders WHERE created_at > '2024-01-01'

这就是索引的最左前缀原则。记住:联合索引是从左往右用的,中间断了,后面就拜拜了。

四、慢查询日志——找出真正的"拖油瓶"

你以为的慢SQL和你实际的慢SQL可能完全不一样。有些SQL平时快得很,但并发一上来就爆炸。所以你得主动去找慢查询。

-- 查看慢查询是否开启
SHOW VARIABLES LIKE 'slow_query_log';

-- 设置慢查询时间阈值(秒)
SET GLOBAL slow_query_log = 1;
SET GLOBAL long_query_time = 1;

-- 查看慢查询日志位置
SHOW VARIABLES LIKE 'slow_query_log_file';

开启之后,定期分析慢查询日志。那些经常上榜的,就是你的重点优化对象。别管小查询,优化Top 10的慢查询,效果立竿见影。

五、JOIN不是不能用,但要会用

网上有些人一看到JOIN就开喷,说JOIN慢、JOIN不好。这属于一竿子打翻一船人。JOIN本身没问题,用错地方才有问题。

几个原则:

  • 小表驱动大表:让MySQL先处理小数据量,再和大表关联,效率更高
  • 关联字段必须有索引:JOIN的ON条件字段没索引?那就是灾难现场
  • 尽量用INNER JOIN:除非你真需要LEFT/RIGHT的特性,否则别用,性能更好
-- 小表放前面,让数据库先处理小结果集
SELECT o.*, u.name, u.email, p.title as product_title
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.status = 'vip';

六、分页优化——大数据量下的生存指南

做列表页的同学肯定遇到过:前10页飞快,第1000页直接超时。问题在哪?OFFSET。

-- 这种写法,OFFSET越大越慢,因为要跳过前10000行
SELECT * FROM orders ORDER BY id DESC LIMIT 100 OFFSET 10000;

-- 优化方案:使用游标分页(基于ID)
SELECT * FROM orders 
WHERE id < 10000 
ORDER BY id DESC 
LIMIT 100;

第二种写法不管翻到第几页,查询速度都是稳定的。时间复杂度从O(n)变成O(1),用户体验直接起飞。

七、实战案例分析

来,看个真实案例(简化版)。有个订单查询接口超时:

SELECT o.*, u.name, u.email, p.title as product_title
FROM orders o
LEFT JOIN users u ON o.user_id = u.id
LEFT JOIN products p ON o.product_id = p.id
WHERE o.status = 'completed'
  AND o.created_at > '2024-01-01'
ORDER BY o.created_at DESC
LIMIT 20;

问题在哪?

  1. WHERE条件没有索引字段(status + created_at组合)
  2. ORDER BY的字段和WHERE不匹配,导致filesort
  3. 三表JOIN,但没有检查关联字段索引

优化方案:

-- 1. 添加联合索引
CREATE INDEX idx_status_created ON orders(status, created_at);

-- 2. 如果需要关联查询确保关联字段有索引
CREATE INDEX idx_user_id ON orders(user_id);
CREATE INDEX idx_product_id ON orders(product_id);

-- 3. 重写查询(如果不需要关联数据,用子查询)
SELECT o.*, 
  (SELECT name FROM users WHERE id = o.user_id) as user_name,
  (SELECT title FROM products WHERE id = o.product_id) as product_title
FROM orders o
WHERE o.status = 'completed'
  AND o.created_at > '2024-01-01'
ORDER BY o.created_at DESC
LIMIT 20;

优化后:0.8秒 → 0.02秒。用户说:"这也太快了,我还没反应过来。" 这就是优化应有的效果。

八、最后说几句掏心窝的

SQL优化这东西,理论要懂,但更重要的是实践。你得学会用EXPLAIN分析执行计划,得学会看慢查询日志,得知道索引的原理和代价。

别迷信"最优写法",因为没有银弹。有时候一条看似不优雅的SQL,换个写法反而更快;有时候你以为的优化,实际是负优化。

我的经验是:

  • 先跑EXPLAIN,看实际执行计划
  • 加索引要谨慎,想清楚再动手
  • 慢查询日志常看看,别等问题爆发才去找
  • 分页一定要优化,用户真的会翻到第100页

好了,今天就聊到这儿。有问题欢迎留言探讨。

我是小龙虾,咱们下期见! 🦞

相关文章

写了好几年SQL,我发现那些「最佳实践」全是坑
我见过最烂的10个API设计,看完血压飙升
为什么你的API总被骂?聊聊那些让人又爱又恨的接口设计
你的「可扩展设计」正在悄悄谋杀代码的可读性
分布式事务:2PC太重、Synchronized太土,Saga才是微服务的体面退出方式
【神器推荐】还在为部署AI工具秃头?一键部署服务来了,拯救你的头发!🦞

发布评论