数据库不走索引扫描的主要原因包括:
1)索引失效;
2)数据量过少;
3)WHERE条件过滤后数据量仍很大。
当过滤后保留大量数据(如10亿条中保留9.9亿条)时,回表操作(索引查找+数据读取)的成本会高于全表扫描。
数据库优化器会计算索引成本和全表扫描成本,选择更低的方式。
其他影响因素还包括函数使用、统计信息过时等。
验证方法可通过执行计划查看,但强制使用索引通常不推荐。
核心原则是索引并非万能,数据量大时全表扫描可能更高效。
一张表 没有走索引扫描,原因有哪些
Q:一张表 没有走索引扫描,原因有哪些?
A:
- 索引失效了;
- 数据量太少了,系统不走索引;
- WHERE 过滤完之后数据量依旧非常大,对比过滤前没什么区别;
比如:原始表有 10 亿条数据 你 WHERE 筛选完了还有 9.9亿条
怎么理解
第3点的深入理解
核心逻辑:回表成本 vs 全表扫描成本
当你通过索引查询时,数据库需要做两件事:
-
索引扫描:在索引树中找到符合条件的记录位置
-
回表:根据索引中的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点,还有:
-
使用了函数或类型转换
sql
WHERE YEAR(create_date) = 2024 -- 函数破坏索引 WHERE id = '123' -- 隐式类型转换(id是数字) -
使用不等于、LIKE '%xxx'
sql
WHERE status != 'DELETED' -- 不等于 WHERE name LIKE '%张三%' -- 以通配符开头 -
统计信息过旧
-
表数据变化了,但统计信息没更新
-
优化器误判,选了错误的执行计划
-
-
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条件过滤效率低(过滤掉的数据很少,保留的数据很多)时,数据库不会走索引,因为回表成本太高,不如直接全表扫描。