找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

671

积分

0

好友

91

主题
发表于 19 小时前 | 查看: 0| 回复: 0

索引失效的常见情况

理解索引何时失效是进行高效数据库/中间件查询优化的关键。以下是一些典型的导致索引失效的场景。

1. 对索引列使用函数或表达式

当在WHERE条件中对索引列应用函数或进行运算时,索引将无法使用。

-- 对索引列使用函数
SELECT * FROM users WHERE UPPER(name) = 'ZHANGSAN';  -- 索引失效
SELECT * FROM orders WHERE YEAR(create_time) = 2024; -- 索引失效

-- 对索引列进行运算
SELECT * FROM products WHERE price + 100 > 200;      -- 索引失效
SELECT * FROM users WHERE id * 2 = 100;              -- 索引失效

2. 隐式类型转换

如果查询条件中的数据类型与索引列定义的类型不匹配,数据库会进行隐式转换,这通常会导致索引失效。这在Java等强类型语言与数据库交互时是需要特别注意的点。

-- 假设 user_id 是 VARCHAR 类型,但有索引
SELECT * FROM orders WHERE user_id = 123;           -- 索引失效(数字转字符串)
-- 假设 phone 是 VARCHAR 类型
SELECT * FROM users WHERE phone = 13800138000;      -- 索引失效

3. 不符合最左前缀原则(复合索引)

对于复合索引 (A, B, C),查询条件必须从最左边的列 A 开始连续使用,才能有效利用索引。

-- 复合索引 (status, create_time, user_id)
-- ✅ 有效:使用最左列
SELECT * FROM orders WHERE status = 'active';

-- ✅ 有效:使用前两列  
SELECT * FROM orders WHERE status = 'active' AND create_time > '2024-01-01';

-- ❌ 失效:跳过最左列
SELECT * FROM orders WHERE create_time > '2024-01-01';

-- ❌ 失效:跳过中间列
SELECT * FROM orders WHERE status = 'active' AND user_id = 123;

4. 使用OR条件且部分列无索引

如果OR连接的多个条件中,有一个列没有索引,优化器通常会放弃使用索引,选择全表扫描。

-- 假设 name 有索引,email 无索引
SELECT * FROM users WHERE name = '张三' OR email = 'zhangsan@email.com'; -- 索引失效
-- 即使都有索引,也可能导致全表扫描
SELECT * FROM users WHERE name = '张三' OR name = '李四'; -- 可能失效

5. LIKE以通配符开头

LIKE查询以%_开头时,无法利用索引的有序性进行快速定位。

-- ❌ 失效:以通配符开头
SELECT * FROM users WHERE name LIKE '%张三%';
SELECT * FROM users WHERE name LIKE '%张三';
-- ❌ 失效:开头使用单个字符通配符
SELECT * FROM users WHERE name LIKE '_张三%';

6. 使用NOT、!=、<>操作符

这些否定操作符通常会导致索引失效,因为需要检查所有非匹配值。

-- ❌ 通常失效
SELECT * FROM users WHERE status != 'active';
SELECT * FROM users WHERE name <> '张三';
SELECT * FROM users WHERE NOT status = 'active';

7. 索引列参与IS NULL/IS NOT NULL

IS NULL有时可能使用索引,但IS NOT NULL通常会触发全表扫描。

-- ❌ 可能失效,特别是 IS NOT NULL
SELECT * FROM users WHERE name IS NULL;      -- 可能使用索引
SELECT * FROM users WHERE name IS NOT NULL;  -- 通常失效,全表扫描

8. 数据分布导致优化器放弃索引

当优化器通过统计信息判断使用索引的成本高于全表扫描时(如数据重复度高或表很小),会放弃使用索引。

-- 假设 status 只有 'active' 和 'inactive',且 90% 都是 'active'
SELECT * FROM users WHERE status = 'active'; -- 优化器可能选择全表扫描
-- 表数据量很小
SELECT * FROM small_table WHERE id > 100;    -- 优化器可能选择全表扫描

9. 使用IN列表过长

IN列表过长时,优化器可能会认为遍历索引不如直接扫描全表高效。

-- IN 列表非常长时,优化器可能选择全表扫描
SELECT * FROM users WHERE id IN (1,2,3,4,5,...1000);

索引有效的情况

理解了失效场景,我们再来看看索引能够高效工作的典型模式。

1. 等值查询

等值匹配是索引最擅长的工作。

-- ✅ 主键等值查询
SELECT * FROM users WHERE id = 123;
-- ✅ 唯一索引等值查询
SELECT * FROM users WHERE email = 'zhangsan@email.com';
-- ✅ 普通索引等值查询
SELECT * FROM users WHERE name = '张三';

2. 范围查询

B+Tree索引的有序性使其非常适合范围查询。

-- ✅ 范围查询(B+Tree 索引特性)
SELECT * FROM users WHERE id > 100 AND id < 200;
SELECT * FROM orders WHERE amount >= 1000 AND amount <= 5000;
SELECT * FROM products WHERE price BETWEEN 50 AND 100;

3. 前缀匹配LIKE查询

不以通配符开头的LIKE查询可以利用索引。

-- ✅ 前缀匹配
SELECT * FROM users WHERE name LIKE '张%';
SELECT * FROM products WHERE name LIKE 'iPhone%';

4. 复合索引的最左前缀匹配

只要遵循最左前缀原则,复合索引就能发挥作用。

-- 复合索引 (department, level, salary)
-- ✅ 使用最左列
SELECT * FROM employees WHERE department = 'IT';
-- ✅ 使用前两列
SELECT * FROM employees WHERE department = 'IT' AND level = 'Senior';
-- ✅ 使用所有列
SELECT * FROM employees WHERE department = 'IT' AND level = 'Senior' AND salary > 10000;
-- ✅ 范围查询在最后一列
SELECT * FROM employees WHERE department = 'IT' AND level = 'Senior' AND salary BETWEEN 8000 AND 12000;

5. IN查询(列表合理时)

合理长度的IN列表查询通常可以使用索引。

-- ✅ IN 查询(列表较短)
SELECT * FROM users WHERE status IN ('active', 'pending');
SELECT * FROM products WHERE category_id IN (1, 2, 3, 4, 5);

6. 排序操作

如果ORDER BY子句的顺序与索引顺序一致,可以避免文件排序。

-- ✅ 利用索引排序
SELECT * FROM users ORDER BY id;                    -- 主键索引
SELECT * FROM products ORDER BY category_id, price; -- 复合索引
-- ✅ WHERE + ORDER BY 都使用索引
SELECT * FROM orders WHERE status = 'completed' ORDER BY create_time DESC;  -- 需要索引 (status, create_time)

7. 覆盖索引查询

当查询的所有字段都包含在索引中时,数据库可以直接从索引中获取数据,无需回表,效率极高。

-- ✅ 覆盖索引:查询字段都在索引中
-- 索引 (name, email)
SELECT name, email FROM users WHERE name = '张三';
-- 索引 (user_id, create_time)
SELECT user_id, COUNT(*) FROM orders WHERE create_time > '2024-01-01' GROUP BY user_id;

8. 连接查询

JOIN操作的关联条件如果使用了索引,可以大幅提升连接性能。

-- ✅ JOIN 条件使用索引
SELECT u.name, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id  -- u.id (主键), o.user_id (外键索引)
WHERE u.status = 'active';

特殊情况分析

1. 字符串索引与比较

字符串类型索引的比较需要特别注意类型一致性。

-- 假设 name 有索引,类型为 VARCHAR
-- ✅ 有效:字符串比较
SELECT * FROM users WHERE name = '张三';
-- ❌ 失效:隐式类型转换(如果比较数字)
SELECT * FROM users WHERE name = 123;
-- ✅ 有效:LIKE 前缀匹配
SELECT * FROM users WHERE name LIKE '张%';
-- ❌ 失效:LIKE 通配符开头
SELECT * FROM users WHERE name LIKE '%张';

2. 多列范围查询

在复合索引中,范围查询列之后的索引列将无法被使用。

-- 复合索引 (create_time, status)
-- ✅ 有效:第一列范围,第二列等值
SELECT * FROM orders WHERE create_time > '2024-01-01' AND status = 'completed';
-- ❌ 失效:第一列范围,第二列范围
SELECT * FROM orders WHERE create_time > '2024-01-01' AND status > 'active';
-- ✅ 有效:两列都是等值
SELECT * FROM orders WHERE create_time = '2024-01-01' AND status = 'completed';

3. 函数索引(MySQL 8.0+)

MySQL 8.0 引入了函数索引,可以针对表达式创建索引,从而解决部分函数导致索引失效的问题。

-- 创建函数索引
CREATE INDEX idx_name_upper ON users ((UPPER(name)));
-- ✅ 有效:使用函数索引
SELECT * FROM users WHERE UPPER(name) = 'ZHANGSAN';

诊断工具:EXPLAIN

使用EXPLAIN命令是分析SQL执行计划、验证索引是否生效的标准方法。

关键字段解读

EXPLAIN SELECT * FROM users WHERE name = '张三';
字段 说明 优化目标
type 访问类型 const>eq_ref>ref>range>index>ALL
key 使用的索引 不应该为NULL
rows 预估扫描行数 越小越好
Extra 额外信息 避免Using filesort,Using temporary

索引有效 vs 失效的 EXPLAIN 对比

索引有效示例:

EXPLAIN SELECT * FROM users WHERE id = 1;
-- type: const, key: PRIMARY, rows: 1, Extra: NULL

索引失效示例:

EXPLAIN SELECT * FROM users WHERE UPPER(name) = 'ZHANGSAN';
-- type: ALL, key: NULL, rows: 10000, Extra: Using where

实战优化示例

假设我们有一个需要优化的查询:

SELECT * FROM orders 
WHERE DATE(create_time) = '2024-01-01' 
  AND status = 'completed'
ORDER BY amount DESC;

优化方案一:避免函数操作

核心思路是改写查询,避免对索引列create_time使用DATE()函数。

-- 首先,创建一个合适的复合索引
CREATE INDEX idx_status_createtime ON orders(status, create_time);

-- 改写查询,将函数操作转换为范围查询
SELECT * FROM orders 
WHERE create_time >= '2024-01-01' 
  AND create_time < '2024-01-02'
  AND status = 'completed'
ORDER BY amount DESC;  -- 注意:amount 排序可能仍有性能问题

优化方案二:覆盖索引 + 应用层排序

对于同时涉及筛选和排序的复杂查询,可以考虑使用覆盖索引,甚至将排序移至应用层处理。

-- 创建覆盖索引,包含筛选和排序字段
CREATE INDEX idx_status_createtime_amount ON orders(status, create_time, amount, id);

-- 第一步:利用索引快速获取ID和排序字段
SELECT id, amount FROM orders 
WHERE create_time >= '2024-01-01'
  AND create_time < '2024-01-02'
  AND status = 'completed'
ORDER BY amount DESC
LIMIT 100; -- 根据情况分页

-- 第二步:根据获取的ID主键,快速获取行详细信息(避免了文件排序)
SELECT * FROM orders WHERE id IN (...);

面试回答技巧与核心原则

当被问及“MySQL索引在什么情况下会失效?”时,可以这样结构化回答,并总结出一个便于记忆的“三不原则”。

开场:
“MySQL索引失效的核心原因,可以总结为‘三不原则’:不能计算、不能跳过、不能模糊开头,这些都是破坏了B+Tree索引有序性特征的操作。”

分类阐述失效情况:

  1. 不能计算:对索引列进行函数处理、表达式运算或类型转换,例如 YEAR(create_time)price+100 或字符串与数字比较导致的隐式转换。
  2. 不能跳过:使用复合索引时,必须遵守最左前缀原则。跳过了索引前面的列,后面的列就无法使用索引。
  3. 不能模糊开头LIKE查询以 %_ 开头时,索引无法定位。
  4. 其他常见情况:使用OR且部分条件无索引、使用NOT/!=/<>否定操作符、数据分布不均导致优化器认为全表扫描更快等。

对比说明有效情况:
“相对应的,等值查询、范围查询、前缀匹配的LIKE、符合最左前缀的复合索引查询,索引都能高效工作。特别地,‘覆盖索引’是一种高级优化技巧,即使WHERE条件不理想,但如果查询的字段全部包含在索引中,数据库可以直接从索引获取结果,性能极佳。”

诊断与总结:
“在实际运维/DevOps和开发工作中,我习惯使用EXPLAIN命令来验证索引的使用情况,重点关注typekeyExtra字段。理解索引失效的本质,就是理解B+Tree索引的有序性。任何破坏这种存储顺序的查询方式,都可能导致索引失效,而我们要做的就是让查询条件与索引的排序规则保持一致。”




上一篇:潘通2026年度代表色解析:云上舞白在UI设计与前端开发中的应用参考
下一篇:讲真,没见过技术这么差的架构师!
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-10 20:24 , Processed in 0.091359 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表