LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

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

zhenglin
2026年2月2日 16:1 本文热度 132

背景

同事说他的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的值不是有序的。


优化建议

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


七、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:预估扫描行数


2.看possible_keys和key

  • possible_keys有值但key是NULL:索引存在但没用上


3.开启optimizer_trace

SET optimizer_trace = 'enabled=on'; SELECT * FROM orders WHERE ...; SELECT * FROM information_schema.optimizer_trace\G
可以看到MySQL为什么选择了某个执行计划。


总结

索引失效的常见原因:

 

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


参考文章:原文链接


该文章在 2026/2/2 16:01:38 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved