mysql在查询的时候走索引比不走索引一定快吗?

索引的核心价值是减少数据库扫描的数据量,从而提升查询效率,但这并非绝对规律。当索引的 "额外开销" 超过其 "扫描优化收益" 时,走索引反而会比全表扫描更慢。

一、核心前提:索引的 "收益" 与 "开销"

要理解 "走索引不一定快",首先要明确索引的本质是「trade-off」(权衡):

1. 索引的 "收益"

  • 减少扫描行数:通过索引树快速定位目标数据,避免全表遍历(如千万级表中查询 1 条数据,索引可直接命中,扫描行数 = 1);
  • 避免排序 / 临时表:若ORDER BY/GROUP BY字段在索引中,可直接利用索引顺序,避免Using filesort/Using temporary

2. 索引的 "开销"

  • 索引维护开销:索引是独立的数据结构(如 B + 树),查询时需先遍历索引树,再通过索引指向的 "行指针" 回表查询完整数据(聚簇索引除外);
  • IO 开销:索引文件会占用额外磁盘空间,查询时需读取索引文件 + 数据文件(全表扫描仅需读取数据文件);
  • 优化器判断开销:数据库优化器需计算 "走索引的成本" 和 "全表扫描的成本",再选择更优方案(若统计信息过时,可能误判)。

当「开销 > 收益」时,走索引会更慢;只有当「收益 > 开销」时,索引才能发挥作用。

二、走索引比全表扫描慢的 5 种典型场景(含原理 + 案例)

场景 1:查询数据量占比极高(高选择性差)

核心逻辑:

索引的优势在 "过滤少量数据",若查询需要返回表中大部分数据(如 30% 以上),索引的 "减少扫描行数" 收益会完全抵消,而 "索引遍历 + 回表" 的开销会凸显。

  • 全表扫描:数据库采用「顺序 IO」读取数据(机械硬盘 / SSD 的顺序 IO 效率远高于随机 IO),无需额外遍历索引;
  • 走索引:需先遍历索引树(随机 IO),再根据索引中的行指针回表读取数据(多次随机 IO),总 IO 开销反而更大。

案例(MySQL InnoDB):

假设有一张user表(100 万行数据),查询 "所有年龄大于 18 岁的用户"(占比约 80%):

sql 复制代码
-- 场景:age字段有索引,但查询数据占比80%
SELECT * FROM user WHERE age > 18;
  • 走索引:遍历索引树找到所有满足age>18的行指针(80 万条),再逐行回表读取完整数据(80 万次随机 IO);
  • 全表扫描:顺序读取整个数据文件(1 次连续 IO),直接过滤数据。此时优化器会自动选择全表扫描,而非走索引。

关键概念:索引选择性

  • 选择性 = 字段唯一值数量 / 总记录数(选择性越高,索引越有用);
  • 例:id(主键,选择性 = 100%)> phone(唯一索引,选择性≈99%)> age(选择性≈10%)> gender(性别,选择性≈2%);
  • 低选择性字段(如gender):即使查询占比 20%,走索引也可能比全表扫描慢。

场景 2:表数据量极小(如几百行)

核心逻辑:

小表的全表扫描开销极低(如 100 行数据,全表扫描仅需读取 1 个数据页),而索引的 "遍历索引树 + 回表" 开销相对更高,完全没必要走索引。

案例:

sql 复制代码
-- 表:user_small(仅50行数据,age有索引)
SELECT * FROM user_small WHERE age = 25;
  • 全表扫描:直接读取 1 个数据页(InnoDB 默认页大小 16KB),遍历 50 行数据,耗时 < 1ms;
  • 走索引:先读取索引页(1 个),找到age=25的行指针,再回表读取数据页(1 个),总 IO 次数与全表扫描相同,但多了索引树遍历的逻辑开销。优化器会直接选择全表扫描,忽略索引。

场景 3:索引失效导致 "伪走索引"

核心逻辑:

看似 "走了索引",但实际是「全索引扫描」(type=index),而非「索引精准命中」(type=ref/range)。全索引扫描需要遍历整个索引树,若索引文件比数据文件更大,耗时会超过全表扫描。

典型案例(索引失效场景):

  1. 索引字段被函数操作(如DATE(create_time)):

    sql 复制代码
    -- create_time有索引,但函数操作导致索引失效,实际走全索引扫描
    SELECT * FROM order WHERE DATE(create_time) = '2025-11-23';
  2. 隐式类型转换(如phoneINT,查询用字符串):

    sql 复制代码
    -- phone是INT类型,'13800138000'是字符串,隐式转换导致索引失效
    SELECT * FROM user WHERE phone = '13800138000';
  3. 联合索引不满足最左前缀原则:

    sql 复制代码
    -- 联合索引:idx_age_name(age, name),查询无age,仅查name
    SELECT * FROM user WHERE name = '张三'; -- 走全索引扫描(type=index)

对比:

  • 全索引扫描(type=index):遍历整个索引树(如索引文件 100MB),耗时久;
  • 全表扫描(type=ALL):遍历数据文件(如数据文件 80MB),耗时更短。

场景 4:需要 "回表" 且数据分散

核心逻辑:

非聚簇索引(如普通索引、联合索引)的叶子节点存储的是「主键值」,而非完整数据。查询时需先通过索引找到主键,再到聚簇索引(主键索引)中查询完整数据,这个过程叫「回表」。若查询的数据分散在表中多个数据页,回表会产生大量「随机 IO」(每个数据页需单独读取),而全表扫描是「顺序 IO」(连续读取数据页),效率更高。

案例:

sql 复制代码
-- 表:order(10万行,普通索引idx_user_id(user_id),非聚簇索引)
-- 查询:用户ID在100-200之间的所有订单(共5000条,分散在100个数据页)
SELECT * FROM order WHERE user_id BETWEEN 100 AND 200;
  • 走索引:遍历 idx_user_id 找到 5000 个主键→回表查询 100 个数据页(100 次随机 IO);
  • 全表扫描:顺序读取 50 个数据页(覆盖所有目标数据),1 次连续 IO。此时随机 IO 的开销远大于顺序 IO,走索引更慢。

场景 5:写入操作(INSERT/UPDATE/DELETE)------ 索引反而拖慢速度

核心逻辑:

索引对「查询」是优化,但对「写入」是负担。写入时,数据库不仅要修改数据文件,还要同步维护所有相关索引(如 B + 树的分裂、合并、排序),索引越多,写入开销越大。

案例:

一张表有 5 个索引,插入 1 条数据时:

  • 无索引:仅需在数据文件末尾插入(或指定位置修改),耗时 1ms;
  • 有索引:需修改 5 个索引树(每个索引树都要找到对应位置插入 / 更新),耗时 5ms+。此时索引越多,写入越慢,甚至可能导致锁等待时间延长。

三、数据库优化器的 "决策逻辑":何时选择走索引 / 全表扫描?

数据库不会盲目走索引,而是通过「成本计算」决定最优方案(以 MySQL 为例):

1. 优化器的 "成本模型"

  • 全表扫描成本 = 数据文件大小 × 每页 IO 成本 + 扫描行数 × 行过滤成本;
  • 走索引成本 = 索引文件大小 × 每页 IO 成本 + 索引扫描行数 × 行过滤成本 + 回表行数 × 回表成本。

优化器会对比两者成本,选择成本更低的方案。

2. 影响决策的关键因素

  • 统计信息:数据库会记录表的行数、数据页数量、索引选择性等统计信息(通过ANALYZE TABLE更新),统计信息过时会导致误判;
  • 查询条件:WHERE子句的过滤条件(如=vs>)、返回字段(SELECT *vsSELECT 主键);
  • 索引类型:聚簇索引(无需回表)比非聚簇索引更易被选择,覆盖索引(无需回表)成本最低。

例子:统计信息过时导致的误判

若表中实际数据 100 万行,但统计信息显示仅 1 万行,优化器可能误判 "走索引成本更低",但实际查询时回表开销极大,导致走索引比全表扫描慢。此时需执行ANALYZE TABLE 表名更新统计信息。

四、面试高频考点:如何判断 "是否该走索引"?(实用技巧)

  1. 看查询数据占比:返回数据占比 <10%→走索引;占比> 30%→全表扫描更优;

  2. 看索引选择性:选择性 > 30%→索引有用;选择性 < 10%→低选择性字段(如性别),走索引可能更慢;

  3. 看是否回表 :覆盖索引(查询字段均在索引中)→ 必走索引(无回表开销);SELECT *+ 非聚簇索引→ 需回表,需评估成本;

  4. 用 EXPLAIN 验证

    • type字段:ref/range→ 有效索引(快);index→ 全索引扫描(可能比ALL慢);
    • rows字段:预估扫描行数越少,走索引越优;
    • Extra字段:Using index(覆盖索引,最优)、Using index condition(ICP 优化,较优)。

五、总结:核心原则与避坑指南

1. 核心结论

  • 走索引不一定比全表扫描快,关键看「收益(减少扫描行数)是否大于开销(索引遍历 + 回表)」;
  • 索引的价值是 "过滤少量、高选择性数据",而非 "万能优化"。

2. 避坑指南

  • 避免给低选择性字段建索引(如性别、状态),反而会拖慢写入和查询;
  • 小表无需建索引(全表扫描效率足够),索引只会增加维护开销;
  • 避免SELECT *:优先用覆盖索引(查询字段 = 索引字段),减少回表开销;
  • 定期更新统计信息(ANALYZE TABLE),避免优化器误判;
  • EXPLAIN分析执行计划,若发现type=indexrows极大,可能是全索引扫描,需优化查询或索引。

3. 一句话记忆

「索引适合 "精准打击"(少量数据、高选择性),全表扫描适合 "地毯式搜索"(大量数据、低选择性)」。

面试真题演练

问题 1:为什么查询 "性别 = 男" 时,优化器选择全表扫描而非走索引?

答:因为 "性别" 是低选择性字段(选择性≈2%),查询数据占比高(约 50%)。走索引需遍历索引树 + 大量回表(随机 IO),而全表扫描是顺序 IO,成本更低,因此优化器选择全表扫描。

问题 2:小表(100 行)中,给name字段建索引,查询SELECT * FROM 表 WHERE name='张三',会走索引吗?

答:大概率不会。小表全表扫描仅需读取 1 个数据页,开销极低;走索引需遍历索引树 + 回表,额外开销超过收益,优化器会选择全表扫描。

相关推荐
9号达人12 分钟前
@NotBlank 不生效报错 No validator could be found:Hibernate Validator 版本匹配指北
后端·面试·程序员
h贤14 分钟前
高可靠微服务消息设计:Outbox模式、延迟队列与Watermill集成实践
后端
架构师专栏16 分钟前
Spring Boot 4 概述与重大变化
spring boot·后端
武子康19 分钟前
大数据-162 Apache Kylin 增量 Cube 与 Segment 实战:按天分区增量构建指南
大数据·后端·apache kylin
SimonKing42 分钟前
IntelliJ IDEA 2025.2.x的小惊喜和小BUG
java·后端·程序员
青梅主码1 小时前
介绍一下我用AI开发的一款新工具:函数图像绘制工具(二)
后端
q***01771 小时前
Spring Boot 热部署
java·spring boot·后端
IT_陈寒2 小时前
JavaScript 闭包通关指南:从作用域链到内存管理的8个核心知识点
前端·人工智能·后端
ChineHe2 小时前
Golang并发编程篇002_Go并发基础
开发语言·后端·golang