news 2026/4/3 2:10:30

MySQL索引明明建了,查询还是慢,排查发现踩了这些坑

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MySQL索引明明建了,查询还是慢,排查发现踩了这些坑

背景

同事说他的SQL查询很慢,但他明明建了索引。

我过去一看:

SELECT * FROM orders WHERE user_id = 10086 AND status = 1;

表有500万数据,user_idstatus都有索引,但这条SQL执行要3秒。

用EXPLAIN一看:

EXPLAIN SELECT * FROM orders WHERE user_id = 10086 AND status = 1; +----+-------------+--------+------+---------------+------+---------+------+---------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+------+---------+------+---------+-------------+ | 1 | SIMPLE | orders | ALL | idx_user_id | NULL | NULL | NULL | 5000000 | Using where | +----+-------------+--------+------+---------------+------+---------+------+---------+-------------+

type=ALL,全表扫描,索引根本没用上。

为什么?总结了索引失效的常见原因。

一、对索引列做运算或函数

错误示例

-- 对索引列使用函数 SELECT * FROM users WHERE YEAR(create_time) = 2024; -- 对索引列做运算 SELECT * FROM orders WHERE order_id + 1 = 10087;

原因

B+树索引存的是列的原始值,你用函数或运算处理后,MySQL没法直接用索引查找。

正确写法

-- 改成范围查询 SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01'; -- 把运算移到右边 SELECT * FROM orders WHERE order_id = 10087 - 1;

二、隐式类型转换

错误示例

-- phone是VARCHAR类型 SELECT * FROM users WHERE phone = 13800138000;

phone是字符串,但传入的是数字,MySQL会做隐式转换。

EXPLAIN结果

type: ALL key: NULL

索引失效了。

原因

MySQL的转换规则是:字符串转数字,而不是数字转字符串。

所以实际执行的是:

SELECT * FROM users WHERE CAST(phone AS SIGNED) = 13800138000;

等于对索引列做了函数处理,索引失效。

正确写法

SELECT * FROM users WHERE phone = '13800138000';

记住:类型要匹配,字符串就传字符串。

三、LIKE以%开头

错误示例

SELECT * FROM products WHERE name LIKE '%手机%';

原因

B+树索引是按顺序排列的,%手机%没法利用索引定位,只能全表扫描。

可以用索引的写法

-- 前缀匹配可以用索引 SELECT * FROM products WHERE name LIKE '手机%';

全文搜索怎么办

如果业务需要中间匹配:

  1. 全文索引:MySQL 5.7+支持中文全文索引
  2. Elasticsearch:专业的搜索引擎
  3. 搜索优化:用其他条件先过滤,再LIKE

四、OR条件只有部分有索引

错误示例

-- user_id有索引,remark没索引 SELECT * FROM orders WHERE user_id = 10086 OR remark = '测试';

EXPLAIN结果

type: ALL

全表扫描。

原因

OR的两个条件,只要有一个没索引,就没法用索引。

正确写法

-- 方案1:给remark也加索引 -- 方案2:改成UNION SELECT * FROM orders WHERE user_id = 10086 UNION SELECT * FROM orders WHERE remark = '测试';

五、联合索引没遵循最左前缀

假设有联合索引

CREATE INDEX idx_abc ON orders(a, b, c);

能用上索引的查询

WHERE a = 1 WHERE a = 1 AND b = 2 WHERE a = 1 AND b = 2 AND c = 3 WHERE a = 1 AND c = 3 -- 只能用到a

用不上索引的查询

WHERE b = 2 -- 没有a,最左前缀断了 WHERE b = 2 AND c = 3 -- 没有a WHERE c = 3 -- 没有a

原理

联合索引的B+树是按(a, b, c)顺序排列的,先按a排序,a相同的按b排序,b相同的按c排序。

如果查询不包含a,就没法利用这个排序结构。

六、范围查询后的列失效

联合索引

CREATE INDEX idx_abc ON orders(a, b, c);

查询

SELECT * FROM orders WHERE a = 1 AND b > 10 AND c = 3;

实际用到的索引

只用到了abc没用上。

原因

范围查询(>、<、BETWEEN、LIKE)会导致后面的列无法使用索引。

因为在b > 10的范围内,c的值不是有序的。

优化建议

把等值查询的列放前面,范围查询的列放后面:

CREATE INDEX idx_acb ON orders(a, c, b);

七、NOT IN和NOT EXISTS

示例

SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM blacklist);

情况

  • IN通常可以用索引
  • NOT IN某些情况会导致全表扫描

优化

-- 用LEFT JOIN替代 SELECT u.* FROM users u LEFT JOIN blacklist b ON u.id = b.user_id WHERE b.user_id IS NULL;

八、使用!=或<>

示例

SELECT * FROM orders WHERE status != 1;

情况

不等于查询有时候会导致索引失效,取决于数据分布。

如果status != 1的数据占大多数,MySQL可能认为全表扫描更快。

建议

用EXPLAIN看实际执行计划,如果数据分布合适,可以改成:

SELECT * FROM orders WHERE status IN (0, 2, 3, 4);

九、IS NULL的情况

老版本MySQL

IS NULL可能导致索引失效。

MySQL 5.7+

IS NULL可以使用索引:

SELECT * FROM users WHERE phone IS NULL; -- 可以用索引

建议

  • 尽量不要用NULL,用默认值代替
  • 如果必须用NULL,确保MySQL版本较新

十、ORDER BY没用上索引

联合索引

CREATE INDEX idx_abc ON orders(a, b, c);

能用索引排序的

ORDER BY a ORDER BY a, b ORDER BY a, b, c ORDER BY a DESC, b DESC, c DESC -- 方向一致

不能用索引排序的

ORDER BY b -- 没有a ORDER BY a ASC, b DESC -- 方向不一致 ORDER BY a, c -- 跳过了b

排查索引问题的流程

  1. EXPLAIN看执行计划
  • type:ALL是全表扫描,ref/range/const是用了索引
  • key:实际使用的索引
  • rows:预估扫描行数
  1. 看possible_keys和key
  • possible_keys有值但key是NULL:索引存在但没用上
  1. 开启optimizer_trace
    SET optimizer_trace = 'enabled=on'; SELECT * FROM orders WHERE ...; SELECT * FROM information_schema.optimizer_trace\G
    可以看到MySQL为什么选择了某个执行计划。

总结

索引失效的常见原因:

原因示例解决方案
对索引列做函数WHERE YEAR(date)=2024改成范围查询
隐式类型转换WHERE phone=138xxx(数字)类型匹配
LIKE %开头WHERE name LIKE '%xxx'改前缀或用ES
OR部分无索引WHERE a=1 OR b=2都加索引或UNION
最左前缀联合索引(a,b,c)查WHERE b=1调整索引或查询
范围查询后的列WHERE a>1 AND b=2调整索引顺序

记住:写完SQL先EXPLAIN,养成习惯。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/29 13:16:43

JAVA源码:同城羽毛球馆线上自助预约新方案

以下是一个基于JAVA的同城羽毛球馆线上自助预约新方案的源码设计&#xff0c;该方案整合了高并发处理、实时交互、多端适配以及物联网联动等特性&#xff0c;旨在为用户提供“一键预约、智能匹配、全流程数字化”的运动服务平台。一、系统架构设计后端技术选型核心框架&#xf…

作者头像 李华
网站建设 2026/3/21 13:29:39

计算机毕业设计springboot基于SpringBoot的童车销售平台 基于Spring Boot的儿童推车在线销售系统设计与实现 Spring Boot框架下的童车电商销售平台开发

计算机毕业设计springboot基于SpringBoot的童车销售平台ms1r5&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着互联网技术的飞速发展&#xff0c;传统的童车销售模式逐渐暴露出…

作者头像 李华
网站建设 2026/3/30 21:33:55

AI绘画商业化捷径:一天内上线你的Z-Image-Turbo图像生成服务

AI绘画商业化捷径&#xff1a;一天内上线你的Z-Image-Turbo图像生成服务 为什么选择Z-Image-Turbo&#xff1f; 创业者想要快速切入AI绘画市场&#xff0c;最头疼的就是技术门槛和部署成本。Z-Image-Turbo作为一款高性能图像生成工具&#xff0c;特别适合需要快速验证商业模式的…

作者头像 李华
网站建设 2026/3/20 0:37:26

通达信机构能量指标操盘必备精品

{}AAA:VOL/((HIGH-LOW)*2-ABS(CLOSE-OPEN)); 主买:IF(CLOSE>OPEN,AAA*(HIGH-LOW),IF(CLOSE<OPEN,AAA*((HIGH-OPEN)(CLOSE-LOW)),VOL/2)),LINETHICK0; 主卖:-IF(CLOSE>OPEN,0-AAA*((HIGH-CLOSE)(OPEN-LOW)),IF(CLOSE<OPEN,0-AAA*(HIGH-LOW),0-VOL/2)),LINETHICK0; 主…

作者头像 李华
网站建设 2026/3/26 7:44:09

试油和试采有什么关系

试油和试采是油气田勘探开发过程中紧密衔接的两个核心测试环节&#xff0c;二者的核心关系是&#xff1a;试油是试采的前提和基础&#xff0c;试采是试油的延伸与深化&#xff0c;共同服务于对油藏的认识和开发可行性的验证。 一、 核心概念与定位试油 试油是指在钻井完成后&am…

作者头像 李华