1. 引言
在 MySQL 中,索引是提升查询性能的关键手段,但索引并非总能生效。很多情况下,即使表上建立了合适的索引,SQL 语句依然会走全表扫描,导致查询变慢。理解索引失效的常见场景,有助于写出更高效的 SQL,也能帮助排查线上慢查询问题。下面是一些常见的索引失效的情况。
2. 索引失效的常见情况
下面列举 MySQL 索引失效的典型场景,并给出对应的示例和规避建议。
2.1 对索引列使用函数或表达式
当查询条件中对索引列使用了函数、运算或类型转换时,MySQL 通常无法使用索引,因为索引中存储的是原始值,而不是函数处理后的结果。
sql
-- 失效:对索引列使用函数
SELECT * FROM user WHERE DATE(create_time) = '2024-01-01';
-- 失效:对索引列进行运算
SELECT * FROM user WHERE age + 1 = 30;
-- 建议改写为:
SELECT * FROM user WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';
SELECT * FROM user WHERE age = 29;
2.2 隐式类型转换
当索引列是字符串类型,而查询条件传入的是数值类型(或反之),MySQL 会进行隐式类型转换,导致索引失效。这是一个很隐蔽的点,在实际的开发中需要格外的注意。
sql
-- 假设 phone 字段是 varchar 类型且有索引
-- 失效:传入数值,发生隐式转换
SELECT * FROM user WHERE phone = 13800138000;
-- 建议改写为:
SELECT * FROM user WHERE phone = '13800138000';
2.3 模糊查询以通配符开头
当 LIKE 查询以 % 或 _ 开头时,索引无法用于前缀匹配,只能全表扫描。
sql
-- 失效:以通配符开头
SELECT * FROM user WHERE name LIKE '%张';
-- 有效:以固定前缀开头
SELECT * FROM user WHERE name LIKE '张%';
2.4 联合索引未遵循最左前缀原则
联合索引 (a, b, c) 遵循最左前缀原则。如果查询条件没有包含最左列 a,索引通常无法使用。
sql
-- 假设联合索引 idx(a, b, c)
-- 失效:未使用最左列 a
SELECT * FROM t WHERE b = 1 AND c = 2;
-- 有效:包含最左列 a
SELECT * FROM t WHERE a = 1 AND b = 1;
2.5 使用 OR 连接非索引列
当 OR 连接的多个条件中,只要有一个列没有索引,整个查询就可能放弃使用索引。
sql
-- 假设 name 有索引,age 无索引
-- 失效:age 无索引导致整体无法走索引
SELECT * FROM user WHERE name = '张三' OR age = 30;
-- 建议改写为 UNION 或为 age 也建立索引
SELECT * FROM user WHERE name = '张三'
UNION
SELECT * FROM user WHERE age = 30;
2.6 对索引列进行排序或分组时使用不当
当 ORDER BY 或 GROUP BY 的字段顺序与索引顺序不一致,或排序方向不一致时,索引可能无法用于排序优化。
sql
-- 假设联合索引 idx(a, b)
-- 失效:排序顺序与索引不一致
SELECT * FROM t WHERE a = 1 ORDER BY b DESC;
-- 有效:与索引顺序一致
SELECT * FROM t WHERE a = 1 ORDER BY b ASC;
2.7 使用不等于、NOT IN、IS NOT NULL
对于范围查询和否定条件,MySQL 优化器往往认为走索引的代价高于全表扫描,从而放弃索引。
sql
-- 失效:不等于条件
SELECT * FROM user WHERE status != 1;
-- 失效:NOT IN
SELECT * FROM user WHERE id NOT IN (1, 2, 3);
-- 失效:IS NOT NULL
SELECT * FROM user WHERE email IS NOT NULL;
2.8 数据量过小或优化器选择全表扫描
当表中数据量很小,或者索引选择性很低(如性别字段只有两个值)时,MySQL 优化器可能认为全表扫描比走索引更快,从而主动放弃索引。
sql
-- 即使有索引,优化器也可能选择全表扫描
SELECT * FROM user WHERE gender = '男';
3. 如何确认索引是否生效
使用 EXPLAIN 关键字可以查看 SQL 的执行计划,重点关注 type 和 key 字段。
sql
EXPLAIN SELECT * FROM user WHERE name = '张三';
- type:如果为 ALL,说明走了全表扫描;如果为 ref、range、index 等,说明使用了索引。
- key:显示实际使用的索引名称;如果为 NULL,说明没有使用索引。
4. 总结
索引失效的原因多种多样,核心在于理解 MySQL 优化器的工作方式以及索引的数据结构。日常开发中,建议遵循以下原则:避免对索引列使用函数和运算、保持字段类型一致、遵循最左前缀原则、谨慎使用 OR 和否定条件,并善用 EXPLAIN 验证执行计划。只有让索引真正生效,才能充分发挥数据库的性能优势。对一些少量数据我们也不要胡用索引,我们要优先选择查询更快的方案。