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

前言

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


一、测试表设计

创建测试表结构的DDL语句

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_iduser_id唯一索引隐式类型转换
idx_username_age_cityusername+age+city联合索引最左前缀原则
idx_create_timecreate_time普通索引函数操作
idx_scorescore普通索引范围查询/负向查询
idx_phonephone普通索引NULL值查询
idx_statusstatus普通索引低区分度索引

随机生成 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 ... AGAINSTSELECT * 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:索引列参与运算

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

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

SQL解释如下图
在这里插入图片描述
在这里插入图片描述
原理:索引存储的是原始值,经过函数处理后无法匹配索引结构。


场景2:隐式类型转换

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

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

SQL解释如下图
在这里插入图片描述
在这里插入图片描述

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


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

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

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

SQL解释如下图
在这里插入图片描述
在这里插入图片描述

类比:就像查字典时直接翻到第100页找单词,而不按字母顺序定位章节。


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

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

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

SQL解释如下图
在这里插入图片描述
在这里插入图片描述


场景5:使用负向查询

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

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

SQL解释如下图
在这里插入图片描述
在这里插入图片描述


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

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

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

SQL解释如下图
在这里插入图片描述
在这里插入图片描述


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

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

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


场景8:数据量过小时

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

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


场景9:低选择性索引

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

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


场景10:隐式字符集转换

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

建议:连表条件使用相同字符集和类型。


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

关键指标解析

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

结语

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

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

四七伵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值