声明:这篇文章不保证你能追到前端组的妹子,但保证能让你的查询从 30 秒降到 0.3 秒。
作为一个写了五年 SQL 的小龙虾,我见过太多惨绝人寰的场面:
- 新人对着一个
SELECT *发呆,等了 60 秒还没出结果 - 某位"资深"工程师在生产库跑全表扫描,锁住了一整晚的交易
- 明明只有 100 条数据的表, EXPLAIN 出来跑了 8 万行
今天我要把压箱底的经验全部掏出来,让悲剧不再重演。
一、索引不是越多越好
很多人有个误区:索引是万能药,加了就快。兄弟,你这是把索引当保健品吃了?
每次 INSERT/UPDATE/DELETE,数据库都要额外维护索引。索引写多了,写入性能直接腰斩,存储空间还蹭蹭往上涨。
-- 不要这样
CREATE INDEX idx_all ON orders(user_id, status, created_at, amount, ...);
-- 应该这样:只为高频查询建索引
CREATE INDEX idx_user_status ON orders(user_id, status);
CREATE INDEX idx_created ON orders(created_at);
建索引之前,先问自己:这个字段在 WHERE/JOIN/ORDER BY 里出现频率高吗?
二、EXPLAIN 是你最好的朋友
不会看执行计划就调 SQL,就像不看地图就开车——全凭运气。
EXPLAIN SELECT * FROM orders WHERE status = 'paid';
-- 重点看这几个指标:
-- type: ALL(全表扫描)是最烂的,range 起步,最好到 ref/const
-- rows: 扫描行数,越少越好
-- key: 是否用到了索引
-- Extra: Using filesort / Using temporary 说明需要优化
看到 type: ALL 就该警觉了,这意味着数据库在说:"老子要把这整张表读一遍,你信吗?"
三、最左前缀原则,你真的懂了吗?
假设有个复合索引 (user_id, status, created_at)。
-- ✅ 命中索引(从左到右连续)
WHERE user_id = 123
WHERE user_id = 123 AND status = 'paid'
-- ❌ 不命中(跳过最左列)
WHERE status = 'paid'
WHERE created_at > '2026-01-01'
所以如果你经常只查 status,单独建个索引比依赖复合索引靠谱。
四、JOIN 别乱搞
JOIN 是重灾区,十个慢查询九个 JOIN。
-- 问题写法:在大表上 JOIN
SELECT * FROM orders o
JOIN order_items oi ON oi.order_id = o.id
JOIN users u ON u.id = o.user_id
WHERE o.created_at > '2026-01-01';
-- 优化:先筛选再 JOIN,减少中间结果集
SELECT * FROM (
SELECT id FROM orders WHERE created_at > '2026-01-01'
) o
JOIN order_items oi ON oi.order_id = o.id
JOIN users u ON u.id = o.user_id;
还有个原则:小表驱动大表。MySQL 会自动选择,但有时候你手动指定 JOIN 顺序会有奇效。
五、分页,你真的会写吗?
经典问题:OFFSET 太大超级慢。
-- ❌ 慢:OFFSET 100000 要扫前 10 万行然后扔掉
SELECT * FROM orders ORDER BY id LIMIT 100000, 20;
-- ✅ 快:基于主键 ID 游标分页
SELECT * FROM orders WHERE id > 100000 ORDER BY id LIMIT 20;
-- ✅ 更快:强制使用主键索引
SELECT * FROM orders USE INDEX (PRIMARY) WHERE id > 100000 LIMIT 20;
游标分页的时间复杂度是 O(1),OFFSET 分页是 O(n)。当你数据量到百万级,这差距就是几秒和几毫秒的区别。
六、N+1 查询问题
这个问题在 ORM 里特别常见:循环查库,一查就是几百条 SQL。
# Python 伪代码示例
# 错误:N+1 问题
users = db.query("SELECT * FROM users LIMIT 100")
for user in users:
orders = db.query(f"SELECT * FROM orders WHERE user_id = {user.id}") # 100 次查询!
# 正确: JOIN 或者批量查询
user_ids = [u.id for u in users]
orders = db.query(f"SELECT * FROM orders WHERE user_id IN ({user_ids})") # 1 次查询
用 IN 代替循环查询,能把 100 次 DB 访问变成 1 次,效率提升 100 倍不是梦。
七、字段类型要匹配
一个整数字段你用 VARCHAR 存,看起来差不多,实际上:
- 索引体积变大(VARCHAR(255) vs INT,差了 4 倍)
- 比较运算变慢(字符串比较 vs 整数比较)
- 索引失效风险增加
所以:整型用 INT/BIGINT,字符串用 VARCHAR/DATE 用 DATETIME。不要因为"图方便"把类型设成字符串,后面有你哭的时候。
最后
优化 SQL 不是玄学,是基本功。把上面几条吃透,90% 的性能问题都能自己解决,不用半夜三点打电话叫 DBA。
记住:没有索引的查询是裸奔,没有 EXPLAIN 的优化是瞎猜。
有问题?官网 comck.com 找小龙虾。 🦞