你的数据库为什么总是慢如蜗牛?
页面卡在 loading,用户刷着刷着就走了。你盯着监控面板上那根忽高忽低的 CPU 曲线,心里清楚:不是服务器扛不住,是数据库在喊救命。
问题十有八九出在索引——建错了,比不建还糟。它吃磁盘、拖慢插入更新,还假装自己很努力。
你真的理解索引是怎么工作的吗?
索引就是数据库的“目录页”。没它,查一条记录就得从头翻到尾;有了它,直接跳到对应页码。
但它不是免费午餐。每加一个索引,写入数据时就要多维护一份结构,磁盘也得多占一块地方。所以别一上来就给所有字段都建索引——先想清楚:谁最常被找?
有个真实例子:一个用户表存了快一千万行,按手机号查用户总要等好几秒。一查发现,phone 字段压根没索引。加上后,查询从“数着秒等”变成“眨个眼就出来”,体验立马不一样。
哪些字段应该创建索引?这3个原则是关键
别凭感觉,看这三条:
第一,优先选“长得都不太一样”的字段。比如 user_id,几乎每条都不同;而 gender 只有男/女两个值,建了也白搭。
第二,盯紧你代码里反复出现的字段:WHERE 后面过滤的、JOIN 时连表用的、ORDER BY 排序靠的——这些就是索引的头号候选人。
第三,别对超长字段硬刚。比如文章正文动辄几千字,直接建索引又重又慢。换成前缀索引(只索引前100个字符)或全文索引更实在。
我们优化过一个订单系统,慢日志里最多的是这句:
WHERE status = 'PAID' AND create_time > '2024-01-01'
status 单独建索引没用(就几个固定值),但和时间组合起来就很香。建了个联合索引 (status, create_time),数据库扫一遍索引就能把结果框出来,不用再回表翻数据。
联合索引怎么建?顺序的奥秘是什么
单列索引像单把钥匙,联合索引像一串钥匙环——但得按对顺序才能开门。
核心就一条:最左前缀匹配。(city, age, gender) 这个索引,能支持 WHERE city = ?、WHERE city = ? AND age > ?,但 WHERE age = ? 就完全用不上。
怎么排?等值查询(=、IN)的放左边,范围查询(>, <, BETWEEN)的放右边。比如 WHERE city = '北京' AND age > 25,索引就该是 (city, age) ——先锁死“北京”,再在这堆人里筛年龄。
教训来自一个电商项目:商品表早先建了 (category_id, price) 索引。结果业务方天天跑 WHERE price BETWEEN 100 AND 500 ORDER BY sales_volume DESC,这个索引根本搭不上边,全表扫描成了日常。后来补了个 (price, sales_volume),问题当场解决。
索引有哪些隐藏的坑需要避开?
索引建了却没生效?大概率掉进这几个坑里:
- 对字段算数或套函数:
WHERE YEAR(create_time) = 2023→ 索引直接失效。改成WHERE create_time >= '2023-01-01' AND create_time < '2024-01-01'才行。 LIKE开头带%:WHERE name LIKE '%张%'无法走索引;WHERE name LIKE '张%'还可以。- 类型不一致:字段是
VARCHAR,你传个数字进去,MySQL 会悄悄转类型,索引也就悄悄失效了。 OR用得太随意:WHERE a = 1 OR b = 2,如果b没索引,整个条件可能放弃走索引。- 表太小:几百行的配置表,全表扫描比绕路走索引还快,优化器自己就会绕开。
如何检查和优化现有的索引?
别猜,打开工具看真实执行计划。
MySQL 用户,记住这句咒语:EXPLAIN + 你的SQL。重点看三列:
type:ALL是全表扫描,range或ref才算正常;key:实际用了哪个索引;rows:预估要扫多少行——数字越大越危险。
再顺手跑 SHOW INDEX FROM 表名,看看有没有重复劳动:比如已有 (a, b) 联合索引,就别再单独建 (a) 了。
有个运维监控系统,报表查询老卡顿。我们拿 EXPLAIN 一看,发现一堆 Using temporary; Using filesort。给 GROUP BY 和 ORDER BY 涉及的字段补了联合索引,临时表没了,排序快了,服务器负载也松了一大口气。
今天下班前就能执行的一个具体步骤
现在,打开你天天用的 phpMyAdmin / DBeaver / Navicat / MySQL Workbench(选你手边那个),连上生产库的测试环境。
找到你系统里数据最多、接口调用最勤的那个表(比如 orders、users 或 articles)。
执行:
SHOW INDEX FROM 你的表名;
EXPLAIN SELECT * FROM 你的表名 WHERE 你常用的查询条件;
对照着看:key 列是不是空的?rows 是不是大得离谱?
挑一个你确认“肯定该有索引却没建”的字段或组合(比如 WHERE user_id = ? AND status = ?),在测试环境建个联合索引:
ALTER TABLE 你的表名 ADD INDEX idx_user_status (user_id, status);
再跑一遍 EXPLAIN,对比 rows 数字有没有明显变小。
就这三步,今晚就能做完。明天上线,用户刷页面的手感,可能就悄悄变了。