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);
相关推荐
yanlele14 分钟前
我用爬虫抓取了 25 年 5 月掘金热门面试文章
前端·javascript·面试
互联网搬砖老肖37 分钟前
运维打铁: MongoDB 数据库集群搭建与管理
运维·数据库·mongodb
典学长编程1 小时前
数据库Oracle从入门到精通!第四天(并发、锁、视图)
数据库·oracle
小兵张健2 小时前
武汉拿下 23k offer 经历
java·面试·ai编程
积跬步,慕至千里2 小时前
clickhouse数据库表和doris数据库表迁移starrocks数据库时建表注意事项总结
数据库·clickhouse
爱莉希雅&&&2 小时前
技术面试题,HR面试题
开发语言·学习·面试
极限实验室2 小时前
搭建持久化的 INFINI Console 与 Easysearch 容器环境
数据库
天天扭码2 小时前
《很全面的前端面试题》——HTML篇
前端·面试·html
星辰离彬3 小时前
Java 与 MySQL 性能优化:Java应用中MySQL慢SQL诊断与优化实战
java·后端·sql·mysql·性能优化
白仑色3 小时前
Oracle PL/SQL 编程基础详解(从块结构到游标操作)
数据库·oracle·数据库开发·存储过程·plsql编程