SQL面试之--明明建了索引为什么失效了?

背景

在数据库查询优化中,​索引是提升查询性能的核心手段。但实际场景中,即使创建了索引,查询性能仍可能未达预期导致slow sql,这类问题但凡你在简历上写了个熟悉mysql或者PG这类的数据库,面试官可能都会问一下,以此判断你到底是简单的掌握了增删改查建表删表还是精通。

下面我们就来分析一下这个问题

索引失效意味着什么?

索引失效,说得明白点,通常索引失效的最终表现就是扫全表去了,比如Mysql的ALL,或者PG的Seq Scan(顺序扫描)

那如何验证是否触发全表扫描呢?​​

要看是哪种类型很简单,在sql前加一个EXPLAIN

bash 复制代码
--Mysql
EXPLAIN SELECT * FROM table_name WHERE name='test';
-- 若 type 列为 ALL,表示全表扫描 | 若 key 列为 NULL,表示未使用索引。

--PG
EXPLAIN ANALYZE SELECT * FROM table_name WHERE name='test';
-- 若出现 Seq Scan on table_name,表示全表扫描。

特殊情况

至于为什么我之前说通常情况下,因为有些场景即使索引有效,数据库的优化器仍可能选择全表扫描(因为它觉得全表扫描比用索引更快,是的,优化器否定了你并朝你丢来一个鸡蛋😀​):

场景 原因
​表数据量极小​ 小表(如 < 1000 行)的全表扫描可能比索引扫描更快(减少随机 I/O)。
​​索引选择性过低​ 索引列的值重复率高(如"性别"一共就三种枚举值-男/女/空),用索引不如全表扫描高效

索引失效的常见原因及案例

言归正传,在排查掉特殊情况后,我们开始分析索引失效的常见原因及案例

1. 查询写法问题​
1.1 对索引列使用函数或计算​
bash 复制代码
-- 示例:date_column是索引列,但查询时使用函数或者计算
SELECT * FROM orders WHERE YEAR(date_column) = 2023;
SELECT * FROM orders WHERE date_column+1 = 2023;

-- 失效原因:索引存储原始值,无法匹配函数处理后的值

--优化方案​:改写查询条件,避免索引列参与计算:
SELECT * FROM orders 
WHERE date_column BETWEEN '2023-01-01' AND '2023-12-31';
1.2 对索引列使用左模'%test'
bash 复制代码
-- 示例:name列有索引,但左模糊匹配 '%ie' 或者 '%ie%'
SELECT * FROM users WHERE name LIKE '%ie';
-- 失效原因:80%的现实情况下,我们建的索引都是默认B-tree索引,而B-tree是不支持左模的--因为它是基于最左前缀有序存储的,无法直接定位中间或结尾的字符。

这里顺便科普一下,哪些索引可支持模糊匹配​

  1. Mysql专为文本搜索设计,支持任意位置的词项匹配的全文索引(Full-Text Index):
  2. PG(GIN)和 ES的倒排索引
额外知识:倒排索引

这里再灌点知识,什么是倒排索引?倒排索引是一种 ​​"从词项到文档"​​ 的索引结构,与传统的 正排索引​("从文档到词项")相反,这么讲有点干巴,上例子。

传统的正排索引,类似小说的目录,我们通过目录去找对应的内容,每一个章节名就是一个文档id,你想查看第3章,直接翻到对应页码即可。

但你最喜欢的的角色是Kuromi,你只想看Kuromi出现的章节,这时候就难办了,我总不能一页一页去翻吧?

这时候聪明的你,想到了为角色建立章节的映射,记录角色出现的章节及位置。(也就是为词项建立倒排索引--第一次为全量

bash 复制代码
"Kuromi" → [
  {章节: 1, 位置: [10, 25], 出现次数: 2},  -- 第1章第10、25行
  {章节: 3, 位置: [5], 出现次数: 1},     -- 第3章第5行
  {章节: 5, 位置: [30], 出现次数: 1}     -- 第5章第30行
]

这时候你想看Kuromi是不是简单多了,​无需阅读全文(遍历全表),直达跳转到对应章节​,就算有新增章节(增量),我们也无需重新构建索引,只需更新Kuromi的倒排列表即可。

对了,这时候还有一种情况会导致索引失效,比如PG中一个jsonb类型的列,你建了一个B-tree的索引,这时候如果你要单独匹配对象中某个字段(如 data->>'user'),则B-tree 索引将​完全失效​,除非你是要精准匹配整个jsonb对象

1.3 ​隐式类型转换​(如列用字符串查询)。
bash 复制代码
索引列是数字类型,但查询用字符串:WHERE str_col = '123'
--失败原因:数据库需隐式转换类型,无法直接使用索引(如 str_col = 123 才能命中索引)。
1.4 使用​OR 连接非索引列。
bash 复制代码
WHERE a = 1 OR b = 2(b 无索引)
-- 失败原因:优化器无法有效合并索引扫描,只能全表扫描。
**1.5 使用 NOT、!=、NOT IN
bash 复制代码
这个不是一定的,因为非等值查询可能无法高效使用索引(这具体取决于数据库优化器实现,所以不是一定的)
2 ​索引设计缺陷​
2.1 覆盖索引未遵循最左前缀原则​

所谓覆盖索引就是我们在建立索引时选择了多列

bash 复制代码
-- 示例:覆盖索引为 (name, age),但查询条件跳过name
SELECT * FROM table WHERE age = 22;
-- 失效原因:复合索引需从最左列开始匹配

优化方案:
调整查询条件顺序或重建索引:
CREATE INDEX idx_b ON table(age);  -- 单独为age建索引
2.2 索引未覆盖查询字段

索引未覆盖查询字段是指​索引中未包含查询所需的所有字段,这其实严格意义上算是命中了索引的,不算严格意义上的索引失效,但是数据库需要​回表查询(Bookmark Lookup)​​从数据页中获取额外的数据。这会增加 I/O 开销,降低查询性能。

bash 复制代码
-- 示例:索引仅包含id,但SELECT *需回表
SELECT * FROM products WHERE id > 100;
-- 失效原因:需要所有字段,但索引只有id

--优化方案:建议建覆盖索引,比如你经常查name age city,那你就给这几个建一个覆盖索引
CREATE INDEX idx_covering ON users(name, age, city);
相关推荐
剩下了什么4 小时前
MySQL JSON_SET() 函数
数据库·mysql·json
山峰哥5 小时前
数据库工程与SQL调优——从索引策略到查询优化的深度实践
数据库·sql·性能优化·编辑器
较劲男子汉5 小时前
CANN Runtime零拷贝传输技术源码实战 彻底打通Host与Device的数据传输壁垒
运维·服务器·数据库·cann
java搬砖工-苤-初心不变5 小时前
MySQL 主从复制配置完全指南:从原理到实践
数据库·mysql
山岚的运维笔记7 小时前
SQL Server笔记 -- 第18章:Views
数据库·笔记·sql·microsoft·sqlserver
roman_日积跬步-终至千里8 小时前
【LangGraph4j】LangGraph4j 核心概念与图编排原理
java·服务器·数据库
汇智信科8 小时前
打破信息孤岛,重构企业效率:汇智信科企业信息系统一体化运营平台
数据库·重构
野犬寒鸦8 小时前
从零起步学习并发编程 || 第六章:ReentrantLock与synchronized 的辨析及运用
java·服务器·数据库·后端·学习·算法
WHD3068 小时前
苏州数据库(SQL Oracle)文件损坏修复
hadoop·sql·sqlite·flume·memcached
晚霞的不甘9 小时前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d