MySQL为什么会索引失效?十大常见场景及避坑指南

前言

索引是数据库查询性能优化重要的手段,但许多开发者都曾遇到这样的困惑:明明创建了索引,查询速度却依然很慢。这背后往往是由于索引失效导致的。本文将通过10个真实场景的SQL示例,带你了解索引失效的原因!


一、测试表设计

创建测试表结构的DDL语句

sql 复制代码
CREATE TABLE `user` (
  `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
  `user_id` VARCHAR(20) NOT NULL COMMENT '用户ID(字符串类型)',
  `username` VARCHAR(50) NOT NULL COMMENT '用户名',
  `age` TINYINT NOT NULL COMMENT '年龄',
  `city` VARCHAR(20) NOT NULL COMMENT '城市',
  `salary` INT NOT NULL COMMENT '薪资',
  `phone` CHAR(11) COMMENT '手机号',
  `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态',
  `score` INT NOT NULL DEFAULT 0 COMMENT '分数',
  `create_time` DATETIME NOT NULL COMMENT '创建时间',
  `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  -- 索引配置
  UNIQUE KEY `uniq_user_id` (`user_id`),
  KEY `idx_username_age_city` (`username`,`age`,`city`),
  KEY `idx_create_time` (`create_time`),
  KEY `idx_score` (`score`),
  KEY `idx_phone` (`phone`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

索引说明

索引名称 字段 索引类型 场景覆盖
uniq_user_id user_id 唯一索引 隐式类型转换
idx_username_age_city username+age+city 联合索引 最左前缀原则
idx_create_time create_time 普通索引 函数操作
idx_score score 普通索引 范围查询/负向查询
idx_phone phone 普通索引 NULL值查询
idx_status status 普通索引 低区分度索引

随机生成 100w 测试数据


二、十大索引失效场景详解

Tips :以下是 MySQL EXPLAINtype 字段的常见类型及其说明的表格总结,按性能从优到劣排序。

类型 (type) 说明 触发条件 示例
system 表中仅有一行数据(const 的特例)。 表仅有一行记录(如系统表或初始化后的 MyISAM/MEMORY 表)。 SELECT * FROM user;
const 通过主键或唯一索引直接定位单行记录。 主键或唯一索引的等值查询。 SELECT * FROM user WHERE id = 1;
eq_ref 在 JOIN 中,使用主键或唯一索引关联另一张表,每行只匹配一条记录。 关联字段是另一表的主键或唯一索引。 SELECT * FROM order JOIN user ON orders.user_id = user.id;
ref 使用非唯一索引的等值查询,可能返回多行。 普通索引的等值查询。 SELECT * FROM user WHERE name = '张三';name 是普通索引)
fulltext 使用全文索引查询。 表有全文索引,且查询使用 MATCH ... AGAINST SELECT * FROM articles WHERE MATCH(content) AGAINST('MySQL');
ref_or_null 类似 ref,但额外处理 NULL 值。 索引列的查询包含 NULL 条件(如 WHERE col = 'value' OR col IS NULL)。 SELECT * FROM user WHERE age = 25 OR age IS NULL;
index_merge 合并多个索引的结果(如 ORAND 条件的联合)。 查询同时使用多个索引。 SELECT * FROM user WHERE id = 1 OR name = '张三';
range 在索引范围内扫描(如 BETWEENIN>)。 索引的范围查询。 SELECT * FROM user WHERE age BETWEEN 20 AND 30;
index 全索引扫描(遍历索引树),通常比全表扫描快。 查询仅需读取索引列。 SELECT name FROM user;name 是索引列)
ALL 全表扫描,性能最差。 无可用索引或索引失效。 SELECT * FROM user WHERE address = 'Beijing';address 无索引)

补充说明:

  1. 性能排序system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > range > index > ALL
  2. 优化目标 :尽量让查询的 type 达到 consteq_refref,避免出现 ALL
  3. 索引设计 :合理设计索引(如覆盖索引、联合索引)可显著优化 type 类型。

场景1:索引列参与运算

sql 复制代码
-- ❌ 错误写法:对索引列使用函数
SELECT * FROM user WHERE YEAR(create_time) = 2025;

-- ✅ 优化方案:保持索引列原始值
SELECT * FROM user 
WHERE create_time BETWEEN '2025-01-01' AND '2025-12-31';

SQL解释如下图 原理:索引存储的是原始值,经过函数处理后无法匹配索引结构。


场景2:隐式类型转换

sql 复制代码
-- ❌ user_id是字符串,但用数字查询(触发类型转换)
SELECT * FROM user WHERE user_id = 10086;

-- ✅ 严格类型匹配
SELECT * FROM user WHERE user_id = '10086';

SQL解释如下图

原理 :MySQL会将所有user_id转换为数字再比较,相当于全表扫描。


场景3:违反最左前缀原则

sql 复制代码
-- ❌ 跳过username字段查询(联合索引失效)
SELECT * FROM user WHERE age = 25 AND city = '北京';

-- ✅ 正确使用联合索引
SELECT * FROM user 
WHERE username = '张三' AND age = 25;

SQL解释如下图


场景4:OR连接非索引字段

sql 复制代码
-- ❌ age字段无索引时(全表扫描)
SELECT * FROM user WHERE score > 90 OR age = 1;

-- ✅ 优化方案:拆分查询或为age添加索引
ALTER TABLE `user` 
ADD INDEX `idx_age`(`age`);

SQL解释如下图


场景5:使用负向查询

sql 复制代码
-- ❌ 不等号导致索引失效
SELECT * FROM user WHERE score != 90000;

-- ✅ 优化方案:限定范围或结合其他索引字段
SELECT * FROM user 
WHERE score > 90000 AND city = '上海'; -- 利用city索引缩小范围

SQL解释如下图


场景6:模糊查询以%开头

sql 复制代码
-- ❌ 前导通配符导致全表扫描
SELECT * FROM user WHERE username LIKE '%张%';

-- ✅ 允许索引的模糊查询
SELECT * FROM user WHERE username LIKE '张%';

SQL解释如下图


场景7:使用NOT IN或者NOT EXISTS

sql 复制代码
-- ❌ 子查询的集合过大
SELECT * FROM user
WHERE id NOT IN (SELECT id FROM table_b); 

原理:子查询的集合过大(或者包含NULL值,会导致整个表达式结果为 UNKNOWN),优化器可能放弃使用索引。


场景8:数据量过小时

sql 复制代码
-- 当表数据量<1000行时或者数据更少时,可能直接全表扫描
SELECT * FROM user WHERE phone = '13800138000';

原理 :数据量较小时,可能出现type=ALL,因为维护索引的代价高于全表扫描,优化器会放弃使用索引。


场景9:低选择性索引

sql 复制代码
-- ❌ 数据大量重复时,状态只有0和1两种
SELECT * FROM user WHERE status = 1; -- 可能全表扫描更优

建议:低区分度字段与其他字段组成联合索引(或者去掉索引,节省空间)。


场景10:隐式字符集转换

sql 复制代码
-- ❌ 两个表的字符集不同时
SELECT * FROM table1 JOIN table2 
ON table1.utf8_col = table2.latin1_col; -- 字符集转换导致索引失效

建议:连表条件的字段,要使用相同字符集和数据类型。


三、诊断工具:EXPLAIN执行计划

关键指标解析

sql 复制代码
EXPLAIN SELECT * FROM user 
WHERE username = '王五' AND age > 25;
字段 说明 优化目标
type 访问类型(至少达到range) 避免ALL和index
key_len 使用的索引长度 值越大说明索引利用率越高
rows 预估扫描行数 值越小越好

结语

索引失效的本质是查询逻辑与索引结构不匹配

  1. 保持查询条件与索引结构的一致性:避免对索引列进行运算、类型转换或破坏最左前缀原则。
  2. 合理设计索引策略:优先使用覆盖索引,对低区分度字段谨慎建索引,定期清理冗余索引。
  3. 善用分析工具 :通过EXPLAINANALYZE TABLE持续监控索引使用效率。

最后:索引优化是一个动态过程。随着数据量增长和业务变化,曾经有效的索引可能会逐渐失效。 随着数据量大幅度的变化,要多次进行索引健康检查,结合慢查询日志分析,确保数据库始终保持最佳性能状态。

相关推荐
橙子家7 小时前
浏览器缓存之【基础键值存储】:Local storage 和 Session storage
前端
星星在线9 小时前
MusicFree:一个「All in One」的个人音乐服务器,让听歌回归简单
前端·后端
IT_陈寒10 小时前
Redis的SETNX并发问题让我加了三天班
前端·人工智能·后端
demo007x11 小时前
Docling 文档转换以及技术架构分析
前端·后端·程序员
京东云开发者11 小时前
京东市民服务又“上新”!这次是黑龙江“龙易办”
前端
袋鱼不重12 小时前
我的神奇同事,AI 用多了居然写了个 Open In Codex
前端·后端·ai编程
摇滚侠12 小时前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
Fireworks12 小时前
深入vue3源码解读 -- 1、响应式的基础概念
前端
程序员黑豆12 小时前
JDK 下载安装与配置详细教程
java·前端·ai编程
hunterandroid12 小时前
文件存储:内部存储与外部存储
前端