你的数据库为什么总是慢如蜗牛?

页面卡在 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。重点看三列:

  • typeALL 是全表扫描,rangeref 才算正常;
  • key:实际用了哪个索引;
  • rows:预估要扫多少行——数字越大越危险。

再顺手跑 SHOW INDEX FROM 表名,看看有没有重复劳动:比如已有 (a, b) 联合索引,就别再单独建 (a) 了。

有个运维监控系统,报表查询老卡顿。我们拿 EXPLAIN 一看,发现一堆 Using temporary; Using filesort。给 GROUP BYORDER BY 涉及的字段补了联合索引,临时表没了,排序快了,服务器负载也松了一大口气。

今天下班前就能执行的一个具体步骤

现在,打开你天天用的 phpMyAdmin / DBeaver / Navicat / MySQL Workbench(选你手边那个),连上生产库的测试环境。
找到你系统里数据最多、接口调用最勤的那个表(比如 ordersusersarticles)。
执行:

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 数字有没有明显变小。

就这三步,今晚就能做完。明天上线,用户刷页面的手感,可能就悄悄变了。