一张表 没有走索引扫描,原因有哪些(回表成本)

数据库不走索引扫描的主要原因包括:

1)索引失效;

2)数据量过少;

3)WHERE条件过滤后数据量仍很大。


当过滤后保留大量数据(如10亿条中保留9.9亿条)时,回表操作(索引查找+数据读取)的成本会高于全表扫描。


数据库优化器会计算索引成本和全表扫描成本,选择更低的方式。


其他影响因素还包括函数使用、统计信息过时等。


验证方法可通过执行计划查看,但强制使用索引通常不推荐。


核心原则是索引并非万能,数据量大时全表扫描可能更高效。


一张表 没有走索引扫描,原因有哪些

Q:一张表 没有走索引扫描,原因有哪些?

A:

  1. 索引失效了;
  2. 数据量太少了,系统不走索引;
  3. WHERE 过滤完之后数据量依旧非常大,对比过滤前没什么区别;

比如:原始表有 10 亿条数据 你 WHERE 筛选完了还有 9.9亿条


怎么理解

第3点的深入理解


核心逻辑:回表成本 vs 全表扫描成本

当你通过索引查询时,数据库需要做两件事:

  1. 索引扫描:在索引树中找到符合条件的记录位置

  2. 回表:根据索引中的ROWID去数据表中读取完整数据

为什么数据量太大时不走索引?

场景举例:

sql

复制代码
-- 假设表有10亿条数据,其中9.9亿条满足 status = 'ACTIVE'
SELECT * FROM users WHERE status = 'ACTIVE';

走索引的成本估算:

  • 索引扫描:快速找到9.9亿个ROWID(快)

  • 回表:9.9亿次随机I/O去读取完整行(极慢!)

  • 总成本 ≈ 9.9亿次随机I/O

全表扫描的成本估算:

  • 顺序读取整个表的10亿条数据(连续I/O,很快)

  • 利用多块读(一次读多个数据块)

  • 总成本 ≈ 连续读取所有数据块

结果: 全表扫描反而比索引+回表更快!

形象的比喻

假设有一本10万页的电话簿(10万条记录):

场景1:找特定的人(走索引)

  • 你有姓名索引(字母顺序表)

  • 先查索引找到页码(快)

  • 翻到那一页(1次随机翻页)

  • ✅ 效率极高

场景2:找所有住在北京的人(占90%)

  • 走索引方式:索引告诉你每个北京人所在的页码 → 翻9万次随机页(慢死!)

  • 全表扫描:从第1页翻到第10万页(连续翻页,反而更快)

数据库的优化器决策逻辑

sql

复制代码
-- 数据库内部估算公式(简化版)
索引成本 = 索引扫描成本 + 回表成本 × 预估行数
全表扫描成本 = 表的总块数 / 多块读系数

-- 选择成本更低的方式
IF 索引成本 < 全表扫描成本 THEN
    使用索引
ELSE
    全表扫描

实际例子

sql

复制代码
-- 表:员工表 100万行
-- 索引:dept_id索引

-- 情况1:过滤后数据少 → 走索引
SELECT * FROM emp WHERE dept_id = 99;  -- 只有10条
-- 回表10次,很快 ✓ 走索引

-- 情况2:过滤后数据多 → 不走索引
SELECT * FROM emp WHERE dept_id IN (1,2,3);  -- 80万条
-- 回表80万次,太慢 ✗ 全表扫描

-- 情况3:查询所有列 vs 只查索引列
SELECT emp_name FROM emp WHERE status = 'ACTIVE';  -- 9.9亿条
-- 如果emp_name在索引中(覆盖索引),不需要回表 → 可能走索引

补充:其他不走索引的原因

除了你说的3点,还有:

  1. 使用了函数或类型转换

    sql

    复制代码
    WHERE YEAR(create_date) = 2024  -- 函数破坏索引
    WHERE id = '123'  -- 隐式类型转换(id是数字)
  2. 使用不等于、LIKE '%xxx'

    sql

    复制代码
    WHERE status != 'DELETED'  -- 不等于
    WHERE name LIKE '%张三%'    -- 以通配符开头
  3. 统计信息过旧

    • 表数据变化了,但统计信息没更新

    • 优化器误判,选了错误的执行计划

  4. NULL值问题

    sql

    复制代码
    WHERE name IS NOT NULL  -- 复合索引可能失效

如何验证?

sql

复制代码
-- 查看执行计划,看是否走了索引
EXPLAIN PLAN FOR 
SELECT * FROM emp WHERE dept_id = 10;

SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);

-- 强制走索引(不推荐,只用于测试)
SELECT /*+ INDEX(emp idx_dept_id) */ * 
FROM emp WHERE dept_id IN (1,2,3);

总结: 索引不是万能的,当回表成本太高时,全表扫描反而是更优选择。数据库优化器会根据数据分布自动选择最优路径。


正确的理解应该是:

原始表有 10 亿条数据,WHERE 筛选后还剩 9.9 亿条

这句话的意思是:

  • WHERE 条件过滤掉了 0.1 亿条(1000万条)

  • WHERE 条件保留了 9.9 亿条

  • 也就是说:要查询的数据量非常大(占总数据的99%)

更清晰的表述方式

应该这样说:

WHERE 条件过滤掉的数据很少需要返回的数据量依然很大(占总数据的绝大部分)

举例说明:

sql

复制代码
-- 表:10亿条订单记录
-- 你想查除了"已删除"之外的所有订单

-- 情况A:过滤掉很少
SELECT * FROM orders WHERE status != 'DELETED';
-- 假设只有100万条是DELETED,剩下9.9亿条需要返回
-- 结果:不走索引,因为要返回99%的数据

-- 情况B:过滤掉很多  
SELECT * FROM orders WHERE status = 'DELETED';
-- 只返回100万条(占总数据1%)
-- 结果:走索引,因为返回数据量小

为什么这样就不走索引?

text

复制代码
走索引的成本 = 索引查找成本 + 回表成本 × 9.9亿次
全表扫描成本 = 顺序读取10亿条的成本

9.9亿次随机回表 > 顺序读取10亿条
所以数据库选择全表扫描

你原先批注的那句话

你写的:

3,WHERE 过滤完之后数据量依旧非常大,对比过滤前没什么区别;

这个理解是完全正确的!就是说:

  • 过滤前:10亿

  • 过滤后:9.9亿

  • 过滤比例很小(只减少了1%)

  • 所以和没过滤差不多,走索引没意义

正确的说法对比

正确说法
筛选掉了小部分
过滤后剩余大量
WHERE留了9.9亿(只删了0.1亿)

总结: 当WHERE条件过滤效率低(过滤掉的数据很少,保留的数据很多)时,数据库不会走索引,因为回表成本太高,不如直接全表扫描。

相关推荐
六月雨滴2 小时前
Oracle 参数文件管理
数据库·oracle·dba
zhaoyong2222 小时前
CSS如何利用Less构建高度自定义组件_通过样式作用域防止冲突与溢出
jvm·数据库·python
2301_781571422 小时前
Less如何优化CSS文件大小_利用压缩配置去除冗余样式
jvm·数据库·python
2401_867623982 小时前
Next.js 13 中为嵌套客户端组件实现局部加载状态的正确方法
jvm·数据库·python
weixin_444012932 小时前
CSS Flex布局中如何实现导航栏与Logo的左右分布_利用justify-content- space-between
jvm·数据库·python
彳亍1012 小时前
Less如何优化CSS文件大小_利用压缩配置去除冗余样式
jvm·数据库·python
m0_748554812 小时前
SQL如何防止JOIN查询导致数据库宕机_查询超时限制与资源管理
jvm·数据库·python
m0_748554812 小时前
React 中的渲染(Rendering)机制详解
jvm·数据库·python
SelectDB2 小时前
时间序列近邻关联性能实测:Doris ASOF JOIN 领先 ClickHouse、DuckDB
大数据·数据库·数据分析