索引的核心价值是减少数据库扫描的数据量,从而提升查询效率,但这并非绝对规律。当索引的 "额外开销" 超过其 "扫描优化收益" 时,走索引反而会比全表扫描更慢。
一、核心前提:索引的 "收益" 与 "开销"
要理解 "走索引不一定快",首先要明确索引的本质是「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)。全索引扫描需要遍历整个索引树,若索引文件比数据文件更大,耗时会超过全表扫描。
典型案例(索引失效场景):
-
索引字段被函数操作(如
DATE(create_time)):sql-- create_time有索引,但函数操作导致索引失效,实际走全索引扫描 SELECT * FROM order WHERE DATE(create_time) = '2025-11-23'; -
隐式类型转换(如
phone是INT,查询用字符串):sql-- phone是INT类型,'13800138000'是字符串,隐式转换导致索引失效 SELECT * FROM user WHERE phone = '13800138000'; -
联合索引不满足最左前缀原则:
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 表名更新统计信息。
四、面试高频考点:如何判断 "是否该走索引"?(实用技巧)
-
看查询数据占比:返回数据占比 <10%→走索引;占比> 30%→全表扫描更优;
-
看索引选择性:选择性 > 30%→索引有用;选择性 < 10%→低选择性字段(如性别),走索引可能更慢;
-
看是否回表 :覆盖索引(查询字段均在索引中)→ 必走索引(无回表开销);
SELECT *+ 非聚簇索引→ 需回表,需评估成本; -
用 EXPLAIN 验证:
type字段:ref/range→ 有效索引(快);index→ 全索引扫描(可能比ALL慢);rows字段:预估扫描行数越少,走索引越优;Extra字段:Using index(覆盖索引,最优)、Using index condition(ICP 优化,较优)。
五、总结:核心原则与避坑指南
1. 核心结论
- 走索引不一定比全表扫描快,关键看「收益(减少扫描行数)是否大于开销(索引遍历 + 回表)」;
- 索引的价值是 "过滤少量、高选择性数据",而非 "万能优化"。
2. 避坑指南
- 避免给低选择性字段建索引(如性别、状态),反而会拖慢写入和查询;
- 小表无需建索引(全表扫描效率足够),索引只会增加维护开销;
- 避免
SELECT *:优先用覆盖索引(查询字段 = 索引字段),减少回表开销; - 定期更新统计信息(
ANALYZE TABLE),避免优化器误判; - 用
EXPLAIN分析执行计划,若发现type=index但rows极大,可能是全索引扫描,需优化查询或索引。
3. 一句话记忆
「索引适合 "精准打击"(少量数据、高选择性),全表扫描适合 "地毯式搜索"(大量数据、低选择性)」。
面试真题演练
问题 1:为什么查询 "性别 = 男" 时,优化器选择全表扫描而非走索引?
答:因为 "性别" 是低选择性字段(选择性≈2%),查询数据占比高(约 50%)。走索引需遍历索引树 + 大量回表(随机 IO),而全表扫描是顺序 IO,成本更低,因此优化器选择全表扫描。
问题 2:小表(100 行)中,给name字段建索引,查询SELECT * FROM 表 WHERE name='张三',会走索引吗?
答:大概率不会。小表全表扫描仅需读取 1 个数据页,开销极低;走索引需遍历索引树 + 回表,额外开销超过收益,优化器会选择全表扫描。