我为什么把“数据库索引”当成了第一堂课

举报
逆行时光 发表于 2026/11/03 08:35:31 2026/11/03
【摘要】 我为什么把“数据库索引”当成了第一堂课刚做后端那阵子,我对数据库的理解停留在“能连上、能查出来就行”。索引?知道有这东西,但觉得那是 DBA 的事,跟写业务代码的我没关系。直到一次大促前的压测,把我从这种天真里狠狠拽了出来。那次压测的是用户订单列表接口,SQL 大概长这样:SELECT * FROM orders WHERE user_id = 12345 AND status = '...

我为什么把“数据库索引”当成了第一堂课

刚做后端那阵子,我对数据库的理解停留在“能连上、能查出来就行”。索引?知道有这东西,但觉得那是 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,然后问自己三个问题:

  1. type 到什么级别? ALL(全表)< index(索引全扫)< range(范围)< ref(非唯一索引)< eq_ref(唯一索引)< const(主键)。至少要到 range 级别。
  2. rows 扫了多少行? 如果表里 100 万数据,rows 显示 50 万,那索引基本没帮上忙。
  3. Extra 有没有危险信号? Using filesort、Using temporary、Using join buffer——出现这些,大概率是性能隐患。

一个心态的转变

以前我觉得数据库是黑盒,只要能查出来就行。现在明白:SQL 和索引是你跟数据库之间的契约。你写得越精准,它执行得越高效;你糊弄它,它就糊弄你——不是现在,是在数据量翻十倍的那天。

如果你现在的项目里还有 type=ALL 的慢查询在跑,今晚就 EXPLAIN 一下。不用一步到位建一堆索引,先解决那个扫描行数最多的。每优化一条,你的系统就多一分底气,你睡觉就多一分踏实。毕竟,凌晨三点的慢查询告警,比任何闹钟都刺耳。

【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。