B-Tree 索引查找路径示意图

配图说明:B-Tree 索引查找路径示意图。图片来源:Lets Groove

前言

MySQL 优化里,索引是绕不开的话题。很多人知道“加索引能让查询变快”,但具体为什么快、什么时候不一定快,可能并不清楚。

官方文档对索引的解释很直接:如果没有索引,MySQL 可能需要从第一行开始扫描表;如果有合适索引,就可以更快定位到目标数据。实际开发里,我们不应该只凭感觉加索引,而是要结合查询语句和执行计划判断。

准备测试表

下面以用户行为日志表为例。日志类表在业务系统里很常见,数据量上来以后,查询性能问题也比较容易暴露。

CREATE TABLE user_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL,
    action_type VARCHAR(50) NOT NULL,
    created_at DATETIME NOT NULL,
    content VARCHAR(255)
);

假设这张表记录用户行为日志,数据量达到几十万甚至几百万后,下面这种查询就很常见:

SELECT *
FROM user_log
WHERE user_id = 10001
ORDER BY created_at DESC
LIMIT 20;

没有索引时的问题

可以先用 EXPLAIN 看执行计划:

EXPLAIN
SELECT *
FROM user_log
WHERE user_id = 10001
ORDER BY created_at DESC
LIMIT 20;

如果结果里的 typeALL,通常表示全表扫描。数据量小时可能没感觉,数据量上来后查询会明显变慢。

除了 type,还可以重点看 keyrowsExtrakey 表示实际使用的索引,rows 表示优化器预估要扫描的行数,Extra 里如果出现 Using filesort,说明排序可能还有优化空间。

添加联合索引

针对这个查询,可以建立一个联合索引:

CREATE INDEX idx_user_created
ON user_log(user_id, created_at);

再次执行同样的 EXPLAIN

EXPLAIN
SELECT *
FROM user_log
WHERE user_id = 10001
ORDER BY created_at DESC
LIMIT 20;

如果索引设计合理,key 应该会显示使用了 idx_user_createdrows 的预估扫描行数也会明显下降。对于日志查询来说,这类变化通常比单纯看响应时间更有参考价值,因为本地小数据量测试可能掩盖真实问题。

为什么是 user_id + created_at

这个查询的核心条件是 WHERE user_id = 10001,同时还需要按 created_at 倒序取最近 20 条。user_id 用来快速定位某个用户的数据,created_at 用来配合排序。

联合索引不是随便把字段拼在一起,而是要结合查询条件、排序方式和字段区分度设计。对于这个例子,(user_id, created_at) 比单独给 created_at 建索引更贴合查询。

索引不是越多越好

索引会提升查询速度,但也有成本。它会占用额外磁盘空间,插入、更新、删除数据时也需要维护索引。如果一张表上建了很多无效索引,写入性能反而会受影响。

低区分度字段单独建索引也未必划算。比如 genderstatus 这类字段,如果取值很少,单独建索引的收益可能不明显。具体是否要建,还是要结合数据量、查询频率和执行计划判断。

建议的优化流程

  1. 先通过慢查询日志或接口耗时定位问题 SQL。
  2. 使用 EXPLAIN 查看执行计划,不要靠猜。
  3. 结合 WHEREORDER BYJOIN 设计索引。
  4. 添加索引后再次查看执行计划,确认扫描行数是否下降。
  5. 在线上大表加索引前,评估锁表、磁盘和执行时间风险。

总结

MySQL 索引优化不要靠感觉,建议形成固定流程:先看慢查询,再用 EXPLAIN 分析执行计划,然后根据查询条件和排序方式设计索引,最后验证效果。

索引的本质不是“加了就快”,而是让数据库少扫描无关数据。理解这一点后,再看很多 SQL 优化问题就会清楚很多。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐