MySQL索引,以及它们的好处和坏处

MySQL 索引:概念、类型,以及好处和坏处

一、MySQL 索引到底是什么?

可以把索引类比成书的目录

  • 没有索引:查一本 1000 页的书,没有目录,只能从头翻到尾。
  • 有索引:有"目录 + 页码",可以直接翻到对应位置。

在 MySQL(尤其是 InnoDB)里,索引本质上是:

按某些列排好序的数据结构(主要是 B+Tree),用额外的空间提高查询速度。

索引记录的内容:

  • 索引列的值(例如 idnamecreate_time 等);
  • 指向真实数据行的"地址"(InnoDB 里通常是 主键值)。

查询过程大致是:

  1. 先在索引结构中快速查到匹配的索引记录;
  2. 再根据记录中的主键回到聚簇索引中取出整行数据(称为"回表");
  3. 如果是覆盖索引(查询的列都在索引里),则不需要回表。

二、常见的索引类型

1. 按底层数据结构划分

1.1 B+Tree 索引(最常用)
  • InnoDB 中,几乎所有主键索引、唯一索引、普通索引、联合索引都是 B+Tree。
  • 是一种多叉平衡树:
    • 每个节点存放大量 key 和指针;
    • 高度一般只有 2~4 层;
    • 查找复杂度为 O(logN)。

优点:

  • 支持范围查询>、<、>=、<=、BETWEEN
  • 支持排序、分组
  • 支持前缀匹配LIKE 'abc%');
  • 节点之间有序,叶子节点之间有链表,方便范围扫描。
1.2 Hash 索引
  • 在 MEMORY 引擎中常见,InnoDB 内部也会有自适应哈希索引。
  • 底层用哈希表,通过 key 算 hash 直接映射到桶

优点:

  • 等值查询(=IN)非常快。

缺点:

  • 不支持范围查询;
  • 不支持有序扫描、排序、前缀匹配;
  • 冲突时需要链表或其他机制,性能会退化。
1.3 全文索引(FULLTEXT)
  • 针对文本字段(CHARVARCHARTEXT)做分词检索:
sql 复制代码
ALTER TABLE article
ADD FULLTEXT INDEX idx_title_content (title, content);

SELECT * FROM article
WHERE MATCH(title, content) AGAINST ('分布式系统');

应用场景:文章搜索、评论搜索等。

1.4 空间索引(SPATIAL)
  • 针对地理空间数据(点、线、多边形)进行加速。
  • 多见于地图、LBS 系统。

2. 按逻辑用途划分

2.1 主键索引(PRIMARY KEY)
  • 一张表只能有一个主键索引;
  • InnoDB 中,主键索引就是聚簇索引(Clustered Index)
    • 叶子节点存放整行数据
    • 表的数据是按照主键顺序存储的。
2.2 唯一索引(UNIQUE)
  • 保证索引列的值不重复
  • 查询性能类似普通索引,多了唯一性检查。
2.3 普通索引(INDEX / KEY)
  • 只用于加速查询,没有唯一性约束。
2.4 联合索引(多列索引)

例如:

sql 复制代码
CREATE INDEX idx_user_city_age
ON user (city, age);

特点:

  • 遵循最左前缀原则
    • 可以用于 WHERE city = ?
    • 可以用于 WHERE city = ? AND age = ?
    • 不能用于单独的 WHERE age = ?
2.5 覆盖索引(Covering Index)

如果查询的字段全部包含在某个索引里,就不需要回表。

例如:

sql 复制代码
CREATE INDEX idx_user_name_age
ON user (name, age);

SELECT name, age
FROM user
WHERE name = 'Tom';
  • 只从 idx_user_name_age 中就能拿到结果,不访问聚簇索引;
  • I/O 更少,性能更高。

三、索引的好处

1. 查询速度大幅提升

  • 没有索引:全表扫描,扫描 N 行。
  • 有索引:通过 B+Tree 查询,复杂度 O(logN),随着数据量增大优势巨大。

常见受益场景:

  • WHERE 条件查询:
    WHERE status = 1 AND create_time > '2025-01-01'
  • JOIN 关联:JOIN 字段加索引;
  • ORDER BY:如果排序字段有索引,可以利用索引顺序避免额外排序;
  • GROUP BY:索引可以帮助快速分组。

2. 减少排序开销(filesort)

例如:

sql 复制代码
SELECT *
FROM order
WHERE user_id = 100
ORDER BY create_time DESC
LIMIT 20;

如果建立联合索引:

sql 复制代码
CREATE INDEX idx_user_time
ON order (user_id, create_time);

则:

  • MySQL 可以顺序扫描索引的一段区间,拿到有序的前 20 条;
  • 避免额外 filesort 和大量磁盘 I/O。

3. 强制唯一性,保证数据正确性

  • 唯一索引或主键索引可以防止数据重复:
    • 如唯一手机号、唯一用户名、唯一业务编号等。
  • 避免业务层自己处理重复逻辑。

4. 帮助优化器生成更优执行计划

良好的索引布局可以:

  • 让优化器更准确估算"需要访问多少行";
  • 选择更高效的执行计划;
  • 减少不必要的行扫描。

四、索引的坏处(成本与风险)

1. 写入性能下降(INSERT / UPDATE / DELETE 变慢)

每次改动数据时,MySQL 不只更新数据行,还需要更新所有相关索引。

一张表如果有很多索引:

  • 插入一行要在多棵 B+Tree 中插入记录;
  • 更新索引列要在多棵树里做删 + 插;
  • 删除记录要在多棵树上删除对应节点。

因此:

  • 写多读少的表不适合建太多索引(例如日志表、大量流水表);
  • 读多写少的表比较适合加索引(例如配置类、字典类数据)。

2. 占用磁盘与内存空间

  • 每个索引都是一棵 B+Tree,要占用磁盘;
  • 热点索引页会加载到 buffer pool,占用内存。

如果索引太多:

  • 可能占用大量内存;
  • 反而把真正频繁访问的数据页挤出去,性能下降。

3. 索引未生效或使用不当

常见问题:

  1. 没有走到最左前缀

    sql 复制代码
    INDEX idx_a_b_c (a, b, c);
    
    -- 这种情况用不上 idx_a_b_c:
    WHERE b = 1 AND c = 2;
  2. 在索引列上使用函数 / 计算:

    sql 复制代码
    -- create_time 上有索引,但这样写基本废掉索引
    WHERE DATE(create_time) = '2025-01-01';
  3. 使用无法利用索引的模糊匹配:

    sql 复制代码
    WHERE name LIKE '%abc';  -- 前面有通配符,无法利用 B+Tree 顺序
  4. 索引列区分度太低:

    • 例如只有 0 和 1 的 is_deleted 字段;
    • 单独索引往往收益很低,优化器可能直接选择全表扫描。

4. 索引选择不当,反而变慢

  • 当一个表有多个索引时,优化器可能会选错索引;
  • 有时走索引 + 回表,比直接全表扫描还慢;
  • 需要通过 EXPLAIN 查看执行计划,必要时:
    • 调整 SQL 写法;
    • 删除干扰性的索引;
    • 或用 USE INDEX / FORCE INDEX(一般慎用)。

5. 维护成本高

索引不是"一次性工程",需要随着业务演进进行管理:

  • 删除长期不用的索引;
  • 避免多人随意添加重复、冗余索引;
  • 大表上增加/删除索引本身也是一个重操作。

五、索引用法建议(实战向)

  1. 优先给这些字段建索引

    • 频繁出现在 WHERE 条件中的字段;
    • JOIN 关联使用的字段;
    • 参与 ORDER BY / GROUP BY 的字段;
    • 需要保证唯一性的业务字段(手机、邮箱、业务 ID 等)。
  2. 多列条件优先考虑联合索引,而不是一堆单列索引

    如经常有查询:

    sql 复制代码
    WHERE user_id = ? AND status = ? AND create_time > ?;

    可以创建:

    sql 复制代码
    CREATE INDEX idx_user_status_time
    ON order (user_id, status, create_time);
  3. 牢记最左前缀原则

    • 有索引 (a, b, c) 时:
      • 支持:aa, ba, b, c 的组合;
      • 不适合:单独 bc
  4. 避免在索引列上做函数 / 计算

    尽量写成:

    sql 复制代码
    create_time >= '2025-01-01 00:00:00'
    AND create_time < '2025-01-02 00:00:00';

    不要写成:

    sql 复制代码
    DATE(create_time) = '2025-01-01';
  5. 控制索引数量

    经验值(不是绝对):

    • 一般业务表 3~5 个索引比较常见;
    • 超过 8 个索引就要好好审查每一个的价值。
  6. 养成看执行计划(EXPLAIN)的习惯

    每当你怀疑"索引是不是用了",直接:

    sql 复制代码
    EXPLAIN SELECT ...;

    关注:typekeyrows 等字段。


六、总结

  • 索引是用空间换时间的结构,本质是"有序的数据结构(B+Tree 为主)";
  • 好处:查得快、能帮助排序/分组、支持唯一约束、让优化器更聪明;
  • 坏处:写入变慢、占空间、可能被错误使用,还要持续维护;
  • 正确的姿势:
    • 以读写比例为前提,平衡索引数量与类型;
    • 理解最左前缀、覆盖索引等核心概念;
    • EXPLAIN 验证执行计划。

合理设计索引是 MySQL 性能优化中最重要的一块,后续可以结合你项目里的具体表结构,一起设计一套"索引方案 + SQL 例子 + EXPLAIN 分析"。

相关推荐
VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue音乐管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
一招定胜负3 小时前
navicat连接数据库&mysql常见语句及操作
数据库·mysql
阿海5743 小时前
备份mysql数据的shell脚本
mysql
热心市民蟹不肉3 小时前
黑盒漏洞扫描(三)
数据库·redis·安全·缓存
chian_ocean3 小时前
openEuler集群 Chrony 时间同步实战:从零构建高精度分布式时钟体系
数据库
Databend3 小时前
构建海量记忆:基于 Databend 的 2C Agent 平台 | 沉浸式翻译 @ Databend meetup 上海站回顾及思考
数据库
αSIM0V4 小时前
数据库期末重点
数据库·软件工程
sayyy4 小时前
【Docker】 安装 mysql8.0
mysql·docker
2301_800256114 小时前
【第九章知识点总结1】9.1 Motivation and use cases 9.2 Conceptual model
java·前端·数据库
不会写程序的未来程序员4 小时前
Redis 的核心机制(线程模型、原子性、Bigkey、单线程设计原因等)
数据库·redis