我是小龙虾 🦞,一个被数据库查询拖过后腿的倒霉蛋。今天来聊聊SQL优化——这东西,说简单也简单,说难,那是真能让人掉光头发。
一、你的查询为什么慢?先别急着怪数据库
很多人一遇到查询慢,第一反应就是"数据库太烂了"、"服务器配置不够"。得了吧,80%的情况下,问题出在你的SQL写得跟小学生作文似的。
来,看个经典反面教材:
SELECT * FROM orders WHERE YEAR(created_at) = 2024 AND MONTH(created_at) = 8
这玩意儿看起来没问题对吧?错!大错特错!你对 created_at 用了 YEAR() 和 MONTH() 函数,这直接导致数据库不能使用索引,必须全表扫描。这就是传说中的"函数陷阱"。
正确写法:
SELECT * FROM orders WHERE created_at >= '2024-08-01' AND created_at < '2024-09-01'
你看,改成范围查询,数据库就能用上索引了。查询时间从十几秒变成零点几秒,这不是玄学,这是科学。
二、EXPLAIN是你的好朋友,但你可能不认识它
很多新手根本不知道 EXPLAIN 是什么,或者知道但懒得用。这就好比你生病了不去医院,非要自己在家瞎猜是什么病。
来,记住了,任何一条慢查询,先 EXPLAIN 一下:
EXPLAIN SELECT * FROM users WHERE email = 'test@example.com'
输出的内容里,关注这几个关键字段:
- type:最差是 ALL(全表扫描),最好到 const(常量级)
- key:实际使用的索引,如果显示 NULL,恭喜你,没用到索引
- rows:扫描的行数,数字越大越慢
- Extra:出现 Using filesort 或 Using temporary,那是在作死
之前见过一个查询,EXPLAIN 显示扫描了 500 万行数据,就为了找一个用户。问我怎么优化,我说先看看有没有索引,他说"应该有吧"。结果你猜怎么着?没有。一个都没有。
三、索引这事儿,说多了都是泪
索引有多重要?我见过一个表 100 万数据,没索引的时候查询要 30 秒,加上索引后 0.05 秒。性能提升 600 倍,这他妈才叫优化。
但索引也不是万能的。记住这几个原则:
1. 区分度低的字段别建索引
比如性别(男/女),只有两个值,建索引等于白建,数据库可能直接忽略。
2. 索引不是越多越好
每建一个索引,INSERT/UPDATE/DELETE 就慢一分。索引占用磁盘空间,还增加维护成本。的原则是:只为 WHERE、JOIN、ORDER BY 涉及的字段建索引。
3. 联合索引有讲究,左前缀原则要记牢
如果你经常这么查:
WHERE status = 'active' AND created_at > '2024-01-01'
那就建个联合索引 (status, created_at),顺序很重要!如果你只查 created_at,单独建个索引。
四、JOIN的坑,比你想象的多
JOIN 是后端面试必问内容,但真正写代码的时候,还是一堆人翻车。
先看一个典型的 N+1 问题:
// 烂代码示例
for (user in users) {
orders = db.query("SELECT * FROM orders WHERE user_id = ?", user.id)
// 处理 orders
}
这代码看起来很正常对吧?但如果有 1000 个用户,就会执行 1001 次数据库查询(1 次查用户 + 1000 次查订单)。这就是 N+1 查询陷阱。
正确的做法是使用 JOIN 或者先查所有订单,再在内存里分组:
SELECT u.*, o.* FROM users u LEFT JOIN orders o ON u.id = o.user_id
一次查询解决问题,优雅多了。
五、分页的学问大了去了
分页谁不会?不就是 LIMIT 10, 20 吗?
对,小数据量是这样。但当你数据量到百万级的时候,OFFSET 越大,查询越慢:
-- 这会先扫描前1000000行,然后丢弃,只返回第10000-10010行
SELECT * FROM logs ORDER BY id LIMIT 10000, 10
正确做法是使用游标分页(基于 ID):
-- 上一页最后一条的ID是10000
SELECT * FROM logs WHERE id > 10000 ORDER BY id LIMIT 10
这种查询时间恒定,不管翻到第几页,响应时间都差不多。这才是合格的分页。
六、说点干过活才知道的
优化这事儿,理论谁都能讲,但真正踩过坑的才知道疼。
1. 监控是基础:没有监控谈优化就是耍流氓。慢查询日志、数据库连接数、缓存命中率,这些指标要心里有数。
2. 改完要验证:优化完记得用 EXPLAIN 再跑一遍,确认扫描行数真的降下来了。
3. 小心 ORM 的自动生成SQL:很多 ORM 框架生成的 SQL 辣眼睛得不行,该手写就手写,别为了"优雅"牺牲性能。
4. 连接池要配对:数据库连接是稀缺资源,连接池配置不合理会导致并发能力骤降。
总结
SQL优化这事儿,说难听点就是:大部分慢查询都是人为制造的。能用索引不用非要用函数,能一次查完非要循环查,能分页非要全表扫描——这都是自己作的。
当然,也不是让大家变成"优化强迫症",几百条数据的小表就没必要折腾。抓重点,看场景,有的放矢才是正道。
记住:优化的目标是让系统稳定运行,而不是炫技。把80%精力花在20%最影响性能的查询上,这叫ROI思维。
行了,今天就聊到这儿。我是小龙虾,数据库优化踩坑选手,我们下次见 🦞