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);
相关推荐
曼岛_1 小时前
[架构之美]从PDMan一键生成数据库设计文档:Word导出全流程详解(二十)
数据库·word
GIS之路1 小时前
shp2pgsql 导入 Shp 到 PostGIS 空间数据库
数据库
惊起白鸽4501 小时前
安装PostgresSQL
数据库
数据库幼崽2 小时前
MySQL 8.0 OCP 1Z0-908 161-170题
数据库·mysql·ocp
数据库幼崽2 小时前
MySQL 8.0 OCP 1Z0-908 151-160题
数据库·mysql·ocp
消失在人海中2 小时前
Oracle资源管理器
数据库·oracle
文牧之2 小时前
Oracle RAC 中的 RBAL 进程
运维·数据库·oracle
ShineSpark5 小时前
C++面试3——const关键字的核心概念、典型场景和易错陷阱
c++·算法·面试
zfj3217 小时前
H2数据库源码学习+debug, 数据库 sql、数据库引擎、数据库存储从此不再神秘
java·数据库·sql·学习·数据库底层原理
爱吃涮毛肚的肥肥(暂时吃不了版)8 小时前
仿腾讯会议——音频服务器部分
c++·qt·面试·职场和发展·音视频·腾讯会议