一些sql语句的使用不当会导致MySQL的索引失效,而索引失效,不管在面试,还是在日常开发过程中,都是要值得注意的。下面我们来看看具体有哪些。
在验证下面的场景时,请准备足够多的数据量,因为数据量少时,MySQL的优化器有时会判定全表扫描无伤大雅,就不会命中索引了。
**1\. where语句中包含or时,可能会导致索引失效
**
使用or并不是一定会使索引失效,你需要看or左右两边的查询列是否命中相同的索引。
假设USER表中的user\_id列有索引,age列没有索引。
下面这条语句其实是命中索引的(据说是新版本的MySQL才可以,如果你使用的是老版本的MySQL,可以使用explain验证下)。
~~~
select * from `user` where user_id = 1 or user_id = 2;
~~~
但是这条语句是无法命中索引的。
~~~
select * from `user` where user_id = 1 or age = 20;
~~~
假设age列也有索引的话,依然是无法命中索引的。
~~~
select * from `user` where user_id = 1 or age = 20;
~~~
因此才有建议说,尽量避免使用or语句,可以根据情况尽量使用union all或者in来代替,这两个语句的执行效率也比or好些。
**2\. where语句中索引列使用了负向查询,可能会导致索引失效**
负向查询包括:NOT、!=、、NOT IN、NOT LIKE等。
某“军规”中说,使用负向查询一定会索引失效,笔者查了些文章,有网友对这点进行了反驳并举证。
其实负向查询并不绝对会索引失效,这要看MySQL优化器的判断,全表扫描或者走索引哪个成本低了。
**3\. 索引字段可以为null,使用is null或is not null时,可能会导致索引失效
**
其实单个索引字段,使用is null或is not null时,是可以命中索引的,但网友在举证时说两个不同索引字段用or连接时,索引就失效了,笔者认为确实索引失效,但这个锅应该由or来背,属于第一种场景~~
假设USER表中的user\_id列有索引且允许null,age列有索引且允许null。
~~~
select * from `user` where user_id is not null or age is not null
~~~
不过某些“军规”和规范中都有强调,字段要设为not null并提供默认值,是有原因值得参考的。
* null的列使索引/索引统计/值比较都更加复杂,对MySQL来说更难优化。
* null 这种类型MySQL内部需要进行特殊处理,增加数据库处理记录的复杂性;同等条件下,表中有较多空字段的时候,数据库的处理性能会降低很多。
* null值需要更多的存储空,无论是表还是索引中每行中的null的列都需要额外的空间来标识。
* 对null 的处理时候,只能采用is null或is not null,而不能采用=、in、、!=、not in这些操作符号。如:where name!='shenjian',如果存在name为null值的记录,查询结果就不会包含name为null值的记录。
**4\. 在索引列上使用内置函数,一定会导致索引失效**
比如下面语句中索引列login\_time上使用了函数,会索引失效:
~~~
select * from `user` where DATE_ADD(login_time, INTERVAL 1 DAY) = 7;
~~~
优化建议,尽量在应用程序中进行计算和转换。
其实还有网友提到的两种索引失效场景,应该都归于索引列使用了函数。
**4.1 隐式类型转换导致的索引失效**
比如下面语句中索引列user\_id为varchar类型,不会命中索引:
~~~
select * from `user` where user_id = 12;
~~~
这是因为MySQL做了隐式类型转换,调用函数将user\_id做了转换。
~~~
select * from `user` where CAST(user_id AS signed int) = 12;
~~~
**4.2 隐式字符编码转换导致的索引失效**
当两个表之间做关联查询时,如果两个表中关联的字段字符编码不一致的话,MySQL可能会调用CONVERT函数,将不同的字符编码进行隐式转换从而达到统一。作用到关联的字段时,就会导致索引失效。
比如下面这个语句,其中d.tradeid字符编码为utf8,而l.tradeid的字符编码为utf8mb4。因为utf8mb4是utf8的超集,所以MySQL在做转换时会用CONVERT将utf8转为utf8mb4。简单来看就是CONVERT作用到了d.tradeid上,因此索引失效。
~~~
select l.operator from tradelog l , trade_detail d where d.tradeid=l.tradeid and d.id=4;
~~~
这种情况一般有两种解决方案。
方案1: 将关联字段的字符编码统一。
方案2: 实在无法统一字符编码时,手动将CONVERT函数作用到关联时=的右侧,起到字符编码统一的目的,这里是强制将utf8mb4转为utf8,当然从超集向子集转换是有数据截断风险的。
如下:
~~~
select d.* from tradelog l , trade_detail d where d.tradeid=CONVERT(l.tradeid USING utf8) and l.id=2;
~~~
**5\. 对索引列进行运算,一定会导致索引失效**
运算如+,-,\*,/等,如下:
~~~
select * from `user` where age - 1 = 10;
~~~
优化的话,要把运算放在值上,或者在应用程序中直接算好,比如:
~~~
select * from `user` where age = 10 - 1;
~~~
**6\. like通配符可能会导致索引失效**
like查询以%开头时,会导致索引失效。解决办法有两种:
将%移到后面,如:
~~~
select * from `user` where `name` like '李%';
~~~
利用覆盖索引来命中索引。
~~~
select name from `user` where `name` like '%李%';
~~~
**7\. 联合索引中,where中索引列违背最左匹配原则,一定会导致索引失效**
当创建一个联合索引的时候,如(k1,k2,k3),相当于创建了(k1)、(k1,k2)和(k1,k2,k3)三个索引,这就是最左匹配原则。
比如下面的语句就不会命中索引:
~~~
select * from t where k2=2;select * from t where k3=3;slect * from t where k2=2 and k3=3;
~~~
下面的语句只会命中索引(k1):
~~~
slect * from t where k1=1 and k3=3;
~~~
**8\. MySQL优化器的最终选择,不走索引**
上面有提到,即使完全符合索引生效的场景,考虑到实际数据量等原因,最终是否使用索引还要看MySQL优化器的判断。当然你也可以在sql语句中写明强制走某个索引。
**优化索引的一些建议**
**1 禁止在更新十分频繁、区分度不高的属性上建立索引。**
更新会变更B+树,更新频繁的字段建立索引会大大降低数据库性能。
“性别”这种区分度不大的属性,建立索引是没有什么意义的,不能有效过滤数据,性能与全表扫描类似。
**2 建立组合索引,必须把区分度高的字段放在前面。**
- 面试
- php
- laravel
- 安装
- 创建数据
- 创建控制器
- 错误
- 权限认证
- 图形验证码
- 管理员
- 无限分类
- 视频播放
- 推流
- pc页面
- thinkphp5
- 表单提交安全验证
- crontab
- 获取下级所有数据
- 过滤敏感词
- 二维码
- 邀请码
- jwt token
- 笛卡尔
- 高并发解决优化
- 设置多语言
- 计算经纬度两点距离
- 分片上传
- centos swoole
- 查看swoole版本
- centos 安装 redis
- 把整数随机分成n份或者红包
- 生成随机字符串
- 获取上级
- 宝塔搭建nginx推拉流
- 生成二维码海报
- 二维数组去重
- 经纬度 ---边距 和判断是不是范围内
- workerman
- 扩展
- redis
- mysql
- mysql 主从复制
- 定位问题语句
- 列的选择规则
- Enum介绍
- 索引介绍
- 延迟关联
- in 查询陷阱
- limit 优化
- 事务
- 读写分离
- mysql 权限错误
- mysql计算距离
- mysql数据导出导入
- 索引失效
- mysql 语句技巧
- linux
- 配置nginx 高可用
- Nginx内核参数优化
- 配置优化
- nginx-keepalived实现高可用负载
- lvs+keepalived
- centos5关闭防火墙
- nginx操作
- linux操作
- mysql 安装
- php
- 安装phpadmin
- php7安装mongodb
- 查看php位置
- 查找开启的进程端口
- 守护进程
- centos安装php7.2
- centos 错误解决
- 安装php7错误
- mysql 错误
- 支付宝支付
- html
- js 倒计时
- go vscode 配置
- go util
- 获取两个数之间随机数
- java
- lambda
- 随机抽取
- lombok
- springboot
- 常见注解
- 接受参数常用注解
- 文件上传
- 上传错误日志拦截
- 拦截器
- 定时任务
- mybatis
- 关联查询
- 全局配置
- 缓存
- git
- 微信相关
- 体现到零钱
- 服务商转账
- 公众号小程序相关
- 小程序二维码
- svn
- layui