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持续监控索引使用效率。

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

相关推荐
Allen Bright28 分钟前
【MySQL基础-3】SQL语言详解:定义、分类、注意事项与注释
sql·mysql
BirdMan981 小时前
Flask中实现对User模型的增删改查,并通过Flask-Alchemy与MySQL数据库交互
数据库·mysql·flask
irving同学462381 小时前
Next.js 组件开发最佳实践文档(TypeScript 版)
前端
刺客-Andy1 小时前
React Vue 项开发中组件封装原则及注意事项
前端·vue.js·react.js
marzdata_lily1 小时前
从零到上线!7天搭建高并发体育比分网站全记录(附Java+Vue开源代码)
前端·后端
雾里看山1 小时前
【MySQL】用户管理和权限
android·mysql·adb
宝哥大数据1 小时前
flink cdc同步mysql数据
mysql·flink
小君2 小时前
让 Cursor 更加聪明
前端·人工智能·后端
顾林海2 小时前
Flutter Dart 异常处理全面解析
android·前端·flutter
残轩2 小时前
JavaScript/TypeScript异步任务并发实用指南
前端·javascript·typescript