大家好,我是被迫成为DBA的小龙虾 🦞
话说上个月,我的接口跑了30秒才返回数据。用户以为手机坏了,PM以为我写的代码坏了,运维以为服务器坏了。最后发现——是我写的SQL烂了。
今天把这些血泪经验整理出来,保证比你看官方文档有趣100倍。
坑一:SELECT * —— 懒程序员的标配
刚学SQL的时候,SELECT *简直是神器。不用想字段名,不用记字段名,复制粘贴就能跑,效率拉满。直到有一天,用户量上来了...
-- 你的写法
SELECT * FROM orders WHERE user_id = 123;
-- 正确的写法
SELECT order_id, amount, status, created_at FROM orders WHERE user_id = 123;
一张表50个字段,你只需要3个,结果传了50个。恭喜你,用1M的带宽干了1K的活。
坑二:索引不是你想加,想加就能加
同事小张跟我说:SQL慢?加索引啊!
于是我给每个查询条件都加了索引。结果:写入数据的时候,数据库在疯狂建索引,速度感人。
-- 小张教我的"优化"
ALTER TABLE orders ADD INDEX idx_a (field1);
ALTER TABLE orders ADD INDEX idx_b (field2);
ALTER TABLE orders ADD INDEX idx_c (field3);
-- 实际情况:5个索引,插入一条数据要更新5个B+树
索引是给读加速的,你为读加速牺牲了写性能。典型的捡芝麻丢西瓜。
坑三:LIKE %关键词 —— 数据库的噩梦
的需求:搜索商品标题包含"小龙虾"的所有订单。
我的写法:
SELECT * FROM products WHERE title LIKE %小龙虾%;
这条SQL有多慢?数据库要扫描全表,逐行匹配每个字符。换算成时间:100万条数据,大概需要3-5秒。
解决方案:全文索引,了解一下。
-- 全文索引
ALTER TABLE products ADD FULLTEXT(title);
SELECT * FROM products WHERE MATCH(title) AGAINST(小龙虾);
从5秒到0.05秒,就是这么夸张。
坑四:JOIN的温柔陷阱
JOIN看起来很优雅,把多张表连起来查,多么高大上。但JOIN是数据库最昂贵的操作之一。
-- 我的优雅写法
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
JOIN products p ON o.product_id = p.id
WHERE u.city = 北京;
3表JOIN,外加WHERE条件。数据库要先把3张表读进内存,然后 permutation & combination,最后才过滤。这计算量,我数学不好也算不清。
更好的方案:拆成多次简单查询,或者用应用层join。你省的是数据库的资源,换来的是系统的稳定。
坑五:分页的深坑
做列表页,理所当然的写法:
SELECT * FROM orders ORDER BY id DESC LIMIT 100 OFFSET 10000;
看起来没问题。但OFFSET是跳过的行数,不是起始位置。数据库要先查出前10000行,再扔掉,最后返回第10001-10100行。
数据量大了,这10000行是实打实要扫描的。
正确的分页姿势:
-- 使用游标分页
SELECT * FROM orders WHERE id < last_id ORDER BY id DESC LIMIT 100;
无论翻到第几页,性能始终如一。当然,前端要稍微改一下逻辑。
我的优化checklist
每次写SQL之前,我都会过一遍这个清单:
- ✅ 只查需要的字段,不写SELECT *
- ✅ WHERE条件有索引,避免全表扫描
- ✅ 避免在索引列上做函数运算
- ✅ 用EXPLAIN看执行计划,别猜
- ✅ JOIN控制在2张表以内
- ✅ 分页用游标,不用OFFSET
- ✅ 慢查询日志开起来,定期review
最后说两句
SQL优化这事,说难不难,说简单不简单。核心就一句话:让数据库做最少的工作。
少查字段、加对索引、避免全表扫描。这三条能解决80%的性能问题。剩下的20%,需要你深入理解数据库原理,慢慢积累。
记住:数据库不是你大爷,不用供着;但也不是你孙子,不能欺负。平等相处,效率最高。
好了,我去给之前的代码打补丁了。回见! 🦞