我为什么把“数据库索引”当成了第一堂课
我为什么把“数据库索引”当成了第一堂课
刚做后端那阵子,我对数据库的理解停留在“能连上、能查出来就行”。索引?知道有这东西,但觉得那是 DBA 的事,跟写业务代码的我没关系。直到一次大促前的压测,把我从这种天真里狠狠拽了出来。
那次压测的是用户订单列表接口,SQL 大概长这样:
SELECT * FROM orders
WHERE user_id = 12345
AND status = 'PAID'
ORDER BY create_time DESC
LIMIT 20;
单用户测试时响应挺快,200ms 左右。但压测一上来,50 并发直接飙到 3 秒,100 并发就超时了。我第一反应是“加机器”,但运维同学看了一眼监控说:“慢查询把连接池堵了,加机器也没用。”
他让我去查 EXPLAIN。结果出来那一刻我脸红了:
type: ALL
rows: 8,600,000
Extra: Using where; Using filesort
全表扫描,860 万行,还用了文件排序。
orders 表上有 860 万数据,而 user_id 上居然没建索引。我赶紧加了一个联合索引:
CREATE INDEX idx_user_status_time ON orders(user_id, status, create_time);
再跑压测,同样的 100 并发,响应时间稳定在 15ms。
那一刻我突然明白:索引不是优化手段,是基本功。 你不会用索引,就像开车不会踩刹车——不出事是因为还没到关键时刻。
一、索引不是“越多越好”,是“用对才好”
踩过坑之后,我开始疯狂给表加索引,觉得“反正查询快就行”。结果另一个问题来了:一个高频写入的表,我加了 6 个索引,写入性能直接掉了 40%。因为每次 INSERT 都要更新所有索引树,索引越多,写入越慢。
后来我总结了索引设计的几个原则:
1. 联合索引的顺序是门学问
最左前缀匹配原则不是说说而已。比如索引 (a, b, c):
WHERE a = 1✅ 能用到WHERE a = 1 AND b = 2✅ 能用到WHERE b = 2❌ 用不到WHERE a = 1 AND c = 3✅ 用到 a,但 c 用不到
所以联合索引要把区分度高、过滤性强的列放前面。我们订单表的索引设计,最终定的是 (user_id, status, create_time)——因为 user_id 区分度最高,先过滤掉 99% 的数据,再按状态筛,最后排序。
2. 别给低区分度的列建单列索引
比如 gender 字段只有 M/F,建索引毫无意义,因为过滤完还剩一半数据,优化器大概率直接走全表。
3. 覆盖索引是性能利器
如果查询的列都在索引里,数据库根本不用回表。比如:
SELECT user_id, status, create_time FROM orders WHERE user_id = 12345;
如果索引是 (user_id, status, create_time),这三个字段全在索引里,查询直接从索引树返回,快得飞起。
二、这些“索引杀手”你中了几条?
在 Code Review 里,我看到过太多让索引失效的写法:
| 写法 | 为什么失效 | 正确姿势 |
|---|---|---|
WHERE DATE(create_time) = '2024-01-01' |
对字段用函数 | create_time >= '2024-01-01' AND create_time < '2024-01-02' |
WHERE amount + 10 > 100 |
对字段做运算 | WHERE amount > 90 |
WHERE name LIKE '%明%' |
前导通配符 | 用全文索引或改成 LIKE '张%' |
WHERE a = 1 OR b = 2 |
OR 导致索引失效 | 用 UNION 拆成两个查询 |
WHERE status != 'PAID' |
不等于导致全表 | 改成 IN 枚举值 |
每一条我都踩过坑。现在 Review SQL 时,看到对字段做函数或运算的,我直接标红打回。
三、慢查询日志:你的免费性能顾问
我们线上 MySQL 的 slow_query_log 一直开着,long_query_time 设的 0.5 秒。每周我都会翻一遍慢查询日志,看看有没有新面孔。
有一次发现一条新上的 SQL 频繁出现在慢查询里:
SELECT COUNT(*) FROM orders WHERE user_id IN (SELECT user_id FROM vip_users);
子查询里 vip_users 有几万条,MySQL 对 IN 子查询的优化很差,每次都全表扫。改成 JOIN 后:
SELECT COUNT(*) FROM orders o JOIN vip_users v ON o.user_id = v.user_id;
耗时从 2.3 秒降到 80ms。这种问题,不查慢查询日志根本发现不了——接口没报错,用户只是觉得“偶尔有点慢”,但数据库已经在默默承受压力了。
四、一个习惯:上线前 EXPLAIN 三问
现在我写任何新 SQL,上线前都会跑 EXPLAIN,然后问自己三个问题:
- type 到什么级别?
ALL(全表)<index(索引全扫)<range(范围)<ref(非唯一索引)<eq_ref(唯一索引)<const(主键)。至少要到range级别。 - rows 扫了多少行? 如果表里 100 万数据,rows 显示 50 万,那索引基本没帮上忙。
- Extra 有没有危险信号?
Using filesort、Using temporary、Using join buffer——出现这些,大概率是性能隐患。
一个心态的转变
以前我觉得数据库是黑盒,只要能查出来就行。现在明白:SQL 和索引是你跟数据库之间的契约。你写得越精准,它执行得越高效;你糊弄它,它就糊弄你——不是现在,是在数据量翻十倍的那天。
如果你现在的项目里还有 type=ALL 的慢查询在跑,今晚就 EXPLAIN 一下。不用一步到位建一堆索引,先解决那个扫描行数最多的。每优化一条,你的系统就多一分底气,你睡觉就多一分踏实。毕竟,凌晨三点的慢查询告警,比任何闹钟都刺耳。
- 点赞
- 收藏
- 关注作者
评论(0)