MySQL 索引失效的常见场景

MySQL 索引失效的常见场景

目标:知道什么时候本来可以用索引但没用上,并学会避免。


一、先说一句前提

MySQL 有没有用到索引,不要靠猜,用:

EXPLAIN SELECT ...;

typekeyrows 等字段,才是判断是否“走索引”的标准。
下面这些都是高概率导致索引失效或索引利用不佳的场景


二、常见索引失效场景

1. 没有满足最左前缀原则

场景:

CREATE INDEX idx_user_status_time ON t_order(user_id, status, create_time);

以下 SQL 使用情况:

-- ✅ 能用上 idx_user_status_time(用到 user_id)
SELECT * FROM t_order
WHERE user_id = 1;

-- ✅ 能用上 idx_user_status_time(用到 user_id, status)
SELECT * FROM t_order
WHERE user_id = 1 AND status = 0;

-- ✅ 能用上 idx_user_status_time(user_id + status + 范围 create_time)
SELECT * FROM t_order
WHERE user_id = 1 AND status = 0 AND create_time > '2025-01-01';

-- ❌ 很可能用不上 idx_user_status_time(跳过了最左 user_id)
SELECT * FROM t_order
WHERE status = 0;

-- ❌ 很可能用不上 idx_user_status_time(WHERE 里没有 user_id)
SELECT * FROM t_order
WHERE status = 0 AND create_time > '2025-01-01';

**原因:**联合索引是按 (user_id, status, create_time) 的顺序排的,
必须从最左边开始连续使用,否则这个索引就相当于废了。

优化建议:

  • 设计联合索引时按照常用查询条件顺序来排;
  • WHERE 条件尽量包含联合索引的最左列。

2. 在索引列上使用函数或表达式

场景:

-- create_time 上有普通索引 idx_create_time

-- ❌ 索引往往失效
SELECT * FROM t_order
WHERE DATE(create_time) = '2025-01-01';

-- ❌ 索引往往失效
SELECT * FROM t_order
WHERE YEAR(create_time) = 2025;

-- ❌ 索引往往失效
SELECT * FROM t_user
WHERE LEFT(name, 3) = 'Tom';

原因:

  • 索引是基于列原始值建立的;
  • 对列使用函数/表达式后,MySQL 无法利用已有 B+Tree 的顺序,只好全表扫描。

正确写法示例:

-- ✅ 转换为范围查询,索引可用
SELECT * FROM t_order
WHERE create_time >= '2025-01-01 00:00:00'
  AND create_time < '2025-01-02 00:00:00';

优化建议:

  • 避免对索引列做函数、计算、表达式;
  • 优先将函数挪到常量一侧,写成范围条件。

3. 在索引列上进行运算(+、-、*、/ 等)

场景:

-- age 有索引

-- ❌ 索引基本失效
SELECT * FROM t_user
WHERE age + 1 = 30;

-- ❌ 索引基本失效
SELECT * FROM t_user
WHERE salary * 12 > 100000;

原因:

  • 和使用函数类似,索引是建立在列的原值上的,运算改变了表达式形态。

优化建议:

  • 改写为:
-- ✅ 索引可用
SELECT * FROM t_user
WHERE age = 29;

-- ✅ 索引可用(能改则改,尽量写到常量这边)
SELECT * FROM t_user
WHERE salary > 100000 / 12;

4. 隐式类型转换导致索引失效

场景:

-- mobile 字段类型为 VARCHAR,有索引 idx_mobile

-- ❌ 字面量是数字,可能触发隐式转换
SELECT * FROM t_user
WHERE mobile = 13800138000;

MySQL 会在内部做类型转换:

  • 可能会把 mobile 转成数字后比较;
  • 一旦对列做转换,就和函数一样,会破坏索引利用

正确写法:

-- ✅ 使用字符串字面量
SELECT * FROM t_user
WHERE mobile = '13800138000';

反过来的情况也可能出现:

-- id 是 INT,有主键索引

-- ✅ 索引可用
SELECT * FROM t_user WHERE id = 123;

-- ❌ 不推荐这样写(虽然很多情况下优化器能兜底)
SELECT * FROM t_user WHERE id = '123';

**建议:**字段什么类型,就用什么类型的常量,不要让 MySQL 帮你“猜”。


5. 前置模糊匹配:LIKE ‘%xxx’ 或 ‘%xxx%’

场景:

-- name 上有索引

-- ✅ 可能利用索引(前缀匹配)
SELECT * FROM t_user
WHERE name LIKE 'Tom%';

-- ❌ 索引基本失效(前面有 %)
SELECT * FROM t_user
WHERE name LIKE '%Tom';

-- ❌ 索引基本失效(前后都有 %)
SELECT * FROM t_user
WHERE name LIKE '%Tom%';

原因:

  • B+Tree 索引按从左到右的前缀有序;
  • LIKE 'Tom%' 可以转化为 'Tom' <= name < 'Ton' 范围;
  • LIKE '%Tom' 前缀不确定,无法利用树的前缀有序性。

优化建议:

  • 尽量使用前缀匹配 LIKE 'xxx%'
  • 真正需要任意位置模糊查询:
    • 考虑 全文索引 FULLTEXT
    • 或者借助搜索引擎(ES 等)。

6. 不等于、NOT IN、NOT LIKE 等“反向条件”

场景:

-- status 上有索引

-- ❌ 一般情况下索引利用度很低
SELECT * FROM t_order
WHERE status <> 1;

-- ❌ 一般情况下索引利用度较差
SELECT * FROM t_order
WHERE status NOT IN (1, 2);

-- ❌ 一般情况下索引利用度较差
SELECT * FROM t_user
WHERE name NOT LIKE 'Tom%';

原因:

  • 当条件表示“绝大部分值”时(比如 <> 1),
    使用索引反而比全表扫描成本更高,优化器通常会放弃索引。

备注:
优化器会根据统计信息估算成本,某些极端情况下仍可能部分使用索引,具体要看 EXPLAIN

建议:

  • 能用“正向条件”表示,就尽量不用 NOT

7. OR 条件导致索引失效

场景:

-- idx_a(a)、idx_b(b) 各自都有单列索引

-- ❌ 可能导致索引失效
SELECT * FROM t_table
WHERE a = 1 OR b = 2;

在很多版本和场景下:

  • 如果 OR 两边使用的是不同的字段,MySQL 可能选择不使用索引,直接全表扫描;
  • 新版本可能会做索引合并(index_merge),但性能未必理想。

优化建议:

  • 能拆成 UNION 的可以拆:
SELECT * FROM t_table WHERE a = 1
UNION
SELECT * FROM t_table WHERE b = 2;
  • 或者用 IN 重写(视业务场景而定);
  • 或建立合适的联合索引。

8. 复合索引中,范围查询后面的列失效

场景:

CREATE INDEX idx_a_b_c ON t_table(a, b, c);

-- a 精确匹配,b 是范围,c 精确匹配
SELECT * FROM t_table
WHERE a = 1 AND b > 10 AND c = 5;

通常情况:

  • 索引可以利用 (a, b) 这两列;
  • c 由于位于范围列 b 之后,往往无法继续利用这个联合索引;
  • 实际执行可能是:利用索引找到满足 a = 1 AND b > 10 的行,再对 c 进行过滤。

原因:

  • 对 B+Tree 来说,一旦中间列是范围条件,后面的列就难以继续利用“有序性”;
  • 索引的有序性被范围打断。

优化建议:

  • 根据查询场景调整索引顺序;
  • 尽量把等值匹配列放在联合索引前面。

9. 低选择性字段上的索引可能被放弃

场景:

-- is_deleted 只有 0/1 两种值

CREATE INDEX idx_deleted ON t_order(is_deleted);

-- ❌ 很可能直接全表扫描
SELECT * FROM t_order
WHERE is_deleted = 0;

原因:

  • 索引列的区分度太低(不同值太少,数据太集中);
  • 用索引需要先“遍历大量索引项 + 回表”,成本反而更高;
  • 优化器会估算“需要访问的行数”,如果发现绝大多数行都要访问,就可能放弃索引。

优化建议:

  • 低选择性字段单独索引意义不大;
  • 常见做法是与其它字段组合建立联合索引:
CREATE INDEX idx_deleted_status ON t_order(is_deleted, status);

10. ORDER BY / GROUP BY 与索引不匹配

场景:

CREATE INDEX idx_a_b ON t_table(a, b);

-- ✅ 可以使用 idx_a_b 完成排序
SELECT * FROM t_table
WHERE a = 1
ORDER BY b;

-- ❌ 可能无法利用 idx_a_b 做排序
SELECT * FROM t_table
WHERE a > 1
ORDER BY b;

-- ❌ ORDER BY 字段与索引顺序不一致
SELECT * FROM t_table
ORDER BY b, a;

原因:

  • ORDER BY 想利用索引,要求:
    • WHERE 条件 & ORDER BY 顺序要与索引定义匹配;
    • 且中间不能出现“破坏有序性”的条件(如范围);
  • 一旦不满足,MySQL 需要额外排序(filesort),可能放弃索引。

优化建议:

  • 设计索引时兼顾 WHEREORDER BY
  • 尽量遵守“按索引顺序排序”。

11. 使用不合适的字符集/排序规则导致索引利用异常

场景:

  • 表和连接使用的字符集不一致;
  • 或排序规则(collation)不一致。

可能导致:

  • MySQL 在比较时需要做字符集/排序规则转换;
  • 实际上也是一种“对列做函数处理”,从而使索引利用不佳。

建议:

  • 表、库、连接的字符集/排序规则尽量保持一致;
  • 避免无谓的字符串转换。

12. 统计信息不准确导致优化器选错索引

场景:

  • 索引本身是好的;
  • SQL 也写得没问题;
  • 但执行计划(EXPLAIN)显示走了一个“奇怪的索引”甚至不走索引。

可能原因:

  • 表数据变化较大,统计信息未更新;
  • 估算行数严重偏差,导致优化器误判。

解决方式:

  • 对表进行 ANALYZE TABLE,更新统计信息;
  • 必要时调整或删除干扰性的索引;
  • 极端情况下可以使用 FORCE INDEX 强制指定索引使用(慎用)。

三、排查与优化套路

  1. 怀疑索引失效时,第一步一定是:

    EXPLAIN SELECT ...;
    
  2. 关注几个关键字段:

    • type:访问类型(ALLindexrangerefconst 等);
    • key:实际使用的索引;
    • rows:预估扫描行数;
    • extra:是否有 Using indexUsing whereUsing filesort 等。
  3. 遇到问题时,从以下几个点排查:

    • 有没有对索引列使用函数 / 运算 / 隐式类型转换;
    • 联合索引是否符合最左前缀原则;
    • WHERE 条件是否高选择性;
    • ORDER BY / GROUP BY 是否与索引顺序匹配;
    • 是否使用了 OR / NOT / %xxx 这类索引不友好的写法。

四、总结

  • 索引失效往往不是“没建索引”,而是:
    • SQL 写法破坏了索引的可用性;
    • 条件本身不适合走索引;
    • 或优化器基于成本选择了全表扫描。
  • 关键是理解:B+Tree 的前缀有序性,并且:
    • 遵守最左前缀原则;
    • 避免在索引列上乱用函数 / 运算 / 隐式转换;
    • 谨慎对待 ORNOT、低选择性列;
    • 使用 EXPLAIN + 统计信息来验证和调整。

掌握这些常见“坑位”,基本就能避免绝大部分索引失效问题。

<think>好的,我现在需要帮助用户总结MySQL索引失效常见场景及原因。首先,我要回忆用户提供的引用内容,确保信息准确。用户提到了四个引用,涉及索引失效的不同情况,比如OR条件、范围查询后的失效、LIKE操作符的使用等。 首先,我应该整理这些引用中的关键点。引用[1]提到了索引失效的几种常见场景,但没有具体说明;引用[2]指出OR关键字导致失效的情况,特别是当其中一个字段没有索引或使用范围查询时;引用[3]提到范围查询后索引可能部分使用,但后续条件可能失效;引用[4]强调LIKE以通配符开头导致索引失效。 接下来,我需要将用户的问题结构化,分点说明常见的失效场景。每个场景需要包含原因和示例,这样用户更容易理解。例如,OR条件的情况,需要说明当OR连接的字段中有一个无索引时,整个查询会失效。或者使用LIKE以%开头时,无法利用索引,因为最左前缀原则。 同时,要注意用户提到的系统级指令,比如行内数学表达式用$...$,独立公式用$$...$$,但这个问题可能不需要数学公式,所以可能不需要使用。另外,用中文回答,结构清晰,分点列出。 然后,根据引用中的信息,我需要确保每个失效场景都有对应的引用标注。例如,当提到OR条件失效时,引用[2]和引用[3]。对于范围查询后的索引失效,引用[3]指出优化器可能部分使用索引,但后续条件可能失效。LIKE的情况引用[4]。 可能还需要补充其他常见场景,比如对索引列使用函数或表达式,这会导致索引失效,但用户提供的引用中没有提到这点。不过用户的问题可能希望基于提供的引用,所以是否需要包括?根据引用内容,用户提供的引用里没有提到函数导致失效的情况,所以可能不在这里讨论。需要严格按照引用内容来组织答案。 检查是否有遗漏的引用内容。引用[2]还提到OR两边为范围查询时失效。引用[3]提到范围查询后索引可能部分使用,但其他条件可能失效。引用[4]主要讲LIKE的通配符开头问题。引用[1]是结语,没有具体场景。 总结起来,用户提供的引用中的场景包括: 1. OR条件导致失效(引用2) 2. 范围查询后的索引失效(引用3) 3. LIKE以%开头导致失效(引用4) 此外,可能还有其他场景如不遵循最左前缀原则、数据类型隐式转换等,但根据用户提供的引用,可能需要集中在提到的几点。或者用户可能希望更全面的总结,但根据提供的引用,需要基于引用内容来回答。 不过,用户的问题是想了解常见场景及原因,而提供的引用可能没有涵盖所有情况。但根据指令,回答中引用的段落末尾需要添加引用标识,所以必须确保每个点都有对应的引用来源。因此,我需要严格按照提供的四个引用中的信息来组织答案,可能无法涵盖其他常见场景如函数使用、数据类型转换等,因为引用中没有提到这些。 现在,开始组织回答结构: 第一部分:引言,说明索引失效的重要性,引用[1]的结语部分。 然后分点列出场景: 1. 使用OR条件导致索引失效(引用2) 2. 范围查询后的索引失效(引用3) 3. LIKE操作符以通配符开头(引用4) 4. 其他可能的场景,如果引用中有的话? 检查引用内容: 引用2提到OR条件,其中字段无索引或范围查询导致失效;引用3补充了范围查询后可能部分使用索引;引用4是LIKE的情况。引用1只是结语,没有具体场景。可能还有其他场景如联合索引不遵循最左前缀,但用户提供的引用中没有提到,所以可能不在回答范围内。 因此,主要分三个场景: 1. OR条件导致索引失效 2. 范围查询后的索引部分失效 3. LIKE操作符以通配符开头 每个场景需要详细说明原因,并附上示例和对应的引用。例如: 对于OR条件,当其中一个字段没有索引,整个查询无法使用索引,必须全表扫描,如select * from table where a=1 or b=2,如果b无索引,则索引失效(引用2)。此外,如果OR两边是范围查询,也会失效(引用2)。 范围查询后的情况,例如where a>10 and b=5,如果a和b是联合索引,a的范围查询导致b无法使用索引(引用3)。 LIKE的情况,如where name like '%abc',无法使用索引(引用4)。 确保每个点都有对应的引用标识。然后,在最后生成相关问题,如: 相关问题可能包括如何避免索引失效、如何优化查询语句、索引设计的最佳实践等。 现在,把这些整理成结构清晰的回答,使用中文,分点说明,并正确标注引用。</think>### MySQL索引失效常见场景及原因总结 MySQL索引失效会严重影响查询性能,了解常见场景可帮助优化数据库设计和查询语句[^1]。以下是主要场景及分析: --- #### 1. **使用`OR`条件导致索引失效** - **原因**: - 如果`OR`连接的字段中**至少有一个字段未建立索引**,则整个查询会退化为全表扫描。 - 即使所有字段都有索引,若`OR`两侧为范围查询(如`>`、`<`),索引也可能失效[^2]。 - **示例**: ```sql SELECT * FROM users WHERE age = 25 OR address = 'Beijing'; ``` 若`address`字段无索引,即使`age`有索引,整体查询仍无法使用索引。 --- #### 2. **范围查询后的索引部分失效** - **原因**: - 在联合索引中,如果对某一列进行范围查询(如`BETWEEN`、`>`),其后的列无法继续使用索引。 - MySQL优化器可能仅使用索引的部分条件,后续过滤需在内存中完成[^3]。 - **示例**: ```sql SELECT * FROM orders WHERE create_time > '2023-01-01' AND status = 'paid'; ``` 若索引为`(create_time, status)`,范围查询`create_time > '2023-01-01'`会导致`status`无法利用索引。 --- #### 3. **`LIKE`操作符以通配符开头** - **原因**: - `LIKE`以`%`开头时(如`%abc`),违反**最左前缀匹配原则**,导致索引失效。 - 若通配符仅出现在末尾(如`abc%`),索引仍可能被使用[^4]。 - **示例**: ```sql SELECT * FROM products WHERE name LIKE '%apple%'; -- 索引失效 SELECT * FROM products WHERE name LIKE 'apple%'; -- 可能使用索引 ``` --- #### 4. **其他常见场景** - **数据类型隐式转换**: 若查询条件与索引字段类型不一致(如字符串字段使用数字值),会导致索引失效。 ```sql SELECT * FROM users WHERE phone = 13800138000; -- phone为VARCHAR类型 ``` - **对索引列使用函数或表达式**: 对索引列进行运算或函数调用(如`WHERE YEAR(create_time) = 2023`)会破坏索引结构。 --- ### 优化建议 1. 避免在`WHERE`子句中使用`OR`,改用`UNION`拆分查询。 2. 合理设计联合索引的顺序,将等值查询列放在范围查询列之前。 3. 对高频查询字段建立覆盖索引,减少回表操作。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值