Mysql杂志(三十)——索引失效情况
1. 不符合最左前缀原则
这个不用多说了吧,大家应该懂的都懂,但是还是在下面附上示范吧,最左前缀原则就是想使用某一个索引,必须要使用这个索引的最左测的一列,不管是否需要,如果不使用就Mysql的优化器就不会选择使用这个索引。
-- 创建索引:INDEX idx_name_age_gender(name, age, gender)-- 有效使用(符合最左前缀)
SELECT * FROM users WHERE name = '张三' AND age = 25;-- 失效情况:
SELECT * FROM users WHERE age = 25; -- 跳过name列
SELECT * FROM users WHERE name = '张三' AND gender = 'M'; -- 跳过age列
2. 对索引列使用函数或计算
这个也很好理解,就是字面上的意思,如果对条件列使用了函数或者计算那就会对索引失效,因为索引是等值查询,如果对他进行计算或者使用函数,那就是会对所以的数据对应的列进行相应的处理,就不会使用到索引。
-- 失效案例:
SELECT * FROM users WHERE YEAR(create_time) = 2023; -- 对索引列使用函数
SELECT * FROM products WHERE price + 100 > 2000; -- 对索引列进行计算-- 优化方案:
SELECT * FROM users WHERE create_time BETWEEN '2023-01-01' AND '2023-12-31';
SELECT * FROM products WHERE price > 1900;
3.使用不等于(!=或<>)或NOT IN
这个也是很好理解的,刚刚上面有说到索引是一个等值查询,如果是不等于这种就会全部扫描一遍然后得到满足条件的列,记住索引喜好等于!讨厌不等于!
-- 失效案例:
SELECT * FROM orders WHERE status != 'completed';
SELECT * FROM customers WHERE id NOT IN (1, 2, 3);-- 部分优化方案:
SELECT * FROM orders WHERE status IN ('pending', 'processing');
SELECT * FROM customers WHERE id > 3; -- 如果ID连续
4.LIKE 通配符开头
这个其实也是和刚刚说的索引喜好有关系,如果是模糊查询就会对每一条数据进行比对,从而得到所有满足条件的数据,毋庸置疑这肯定就是全表扫描,索引必然失效,但使用前缀匹配或者对varchar类型的数据列使用前缀索引或者全文索引就没有那么糟,不然Mysql也不会推出这两个索引,虽然主包日常开发没有使用到。
-- 失效案例:
SELECT * FROM products WHERE name LIKE '%手机%'; -- 前导通配符
SELECT * FROM products WHERE name LIKE '_苹果%'; -- 前导通配符-- 有效使用:
SELECT * FROM products WHERE name LIKE '苹果%'; -- 前缀匹配
5. 隐式类型转换
这个怎么说呢,我们写Java的时候好像也有这个隐式转换,比如我们字符串转数字,数字转字符串一样,Mysql同意也是支持的,但是这样会造成索引的失效,因为无法等值匹配,需要每一个条数据对应的索引列转化成对应的类型再进行比较,如下:
-- 失效案例(phone是varchar类型):
SELECT * FROM users WHERE phone = 13800138000; -- 字符串转数字-- 正确写法:
SELECT * FROM users WHERE phone = '13800138000';
这个phone是一个varchar类型,那么每一条数据的phone列都会先转数字对比,那么不就是全扫描了。而且这种类型转化查询出来的数据也不一定正确,因为涉及到精度的问题还有编码规则(如UTF—8)的影响。
6.OR 连接非索引列
这个其实也很好理解,我们InooDB的索引都是先对比第一索引列,即使想对比第二索引列也是在第一索引列成功命中的时候才会对比第二索引列,所以我们日常使用的时候如果有OR的场景尽量还是使用UNION进行数据集合合并,不然可能降低SQL性能。
-- 失效案例(age有索引,address无索引):
SELECT * FROM users WHERE age = 25 OR address = '北京';-- 优化方案:
SELECT * FROM users WHERE age = 25
UNION
SELECT * FROM users WHERE address = '北京';-- 不会失效(会被优化器优化成in):
SELECT * FROM users WHERE age = 25 OR age = 26
-- 优化后:
SELECT * FROM users WHERE age in (25,26)-- 复合索引,可能会使用到部分索引(age)不会使用到(address):
CREATE INDEX idx_age_address ON users(age, address);
SELECT * FROM users WHERE age = 25 OR address = '北京';
7.个人认为不会索引失效的情况
1.范围查询>,<
主包个人认为是不会,因为我们InnoDB的索引都是B+树的格式,复合索引都遵守最左侧索引列 排序后再到第二列索引列排序,所以第一索引列相同的情况下,第二索引一点也是升序排序的,而且目录节点存的也是第一索引列和对应叶子节点指针,所以都是先按照第一索引找到满足条件的最左侧目录节点的行数据,然后顺序加载叶子节点对比每一条行数据第二索引是否满足。
网上说会造成索引失效或者部分索引失效的原因有的说是数据库不支持,有点说只会使用到第一索引,但是主包认为,没有使用到索引的情况可能和Myslq的优化器有关,Mysql优化器只会选择执行成本最低的,然后索引建的有问题导致成本增加,那么Mysql就不会使用这个索引。当然这个是主包个人认为哈。
2.Select * 索引失效
其实主包个人认为不存在这个失效,因为我们的聚簇索引是根据主键来的,假如你查name,那么没有这个了列的索引,怎么能被称为索引失效呢?没用到聚簇索引吗?但本来就不现实好吧。如果加了name这一列的索引,那么select * 还要回表也就是查聚簇索引找多数据,那么这也叫索引失效吗?主包认为也不是。
MySQL 四种 EXPLAIN 命令对比分析
这里再对前两篇的内容进行一个补充吧,执行计划有四种格式我们已经说了2种了,其实后面的2种字段名都是相同的,所以就不冗余的去锁了,这边就给个表格大家看看吧。这个是基本功能的对比:
命令类型 | 输出格式 | 版本要求 | 是否执行查询 | 主要用途 |
---|---|---|---|---|
EXPLAIN | 表格格式 | 全版本 | 否 | 基础执行计划分析 |
EXPLAIN FORMAT=JSON | JSON格式 | 5.6+ | 否 | 详细执行计划分析 |
EXPLAIN FORMAT=TREE | 树形结构 | 8.0+ | 否 | 可视化执行流程 |
EXPLAIN ANALYZE | 表格+实际数据 | 8.0.18+ | 是 | 真实执行统计 |
这个是详细功能的对比:
对比维度 | EXPLAIN | EXPLAIN FORMAT=JSON | EXPLAIN FORMAT=TREE | EXPLAIN ANALYZE |
---|---|---|---|---|
输出内容 | 基础执行计划 | 详细执行计划+成本估算 | 执行流程树形图 | 实际执行统计 |
执行成本 | 预估成本 | 预估成本 | 预估成本 | 实际成本 |
扫描行数 | 预估行数 | 预估行数 | 预估行数 | 实际行数 |
索引信息 | 基础索引使用 | 详细索引使用情况 | 索引使用路径 | 实际索引效率 |
是否执行查询 | 否 | 否 | 否 | 是 |
适用场景 | 快速检查 | 深度分析 | 流程可视化 | 精准优化 |
这个是优缺点分析:
命令类型 | 优点 | 缺点 | 最佳使用场景 |
---|---|---|---|
EXPLAIN | 简单直观,全版本支持 | 信息有限,只有预估数据 | 日常快速检查 |
EXPLAIN FORMAT=JSON | 信息全面,可编程解析 | 输出冗长,需解析工具 | 自动化分析系统 |
EXPLAIN FORMAT=TREE | 直观展示执行流程 | 缺少详细成本数据 | 复杂查询理解 |
EXPLAIN ANALYZE | 真实执行数据,精准 | 会实际执行查询,有性能影响 | 生产环境精准调优 |
总结
本篇主要讲的就是Mysql索引失效的几种情况,以及对前两篇执行计划的补充。