学习总结录 学习总结录
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
首页
归档
分类
标签
  • Java基础
  • Java集合
  • MySQL
  • Redis
  • JVM
  • 多线程
  • 计算机网络
  • 操作系统
  • Spring
  • Kafka
  • Elasticsearch
  • Python
  • 面试专题
  • 案例实践
  • 工具使用
  • 项目搭建
  • 服务治理
  • ORM框架
  • 分布式组件
  • MiniSpring
  • 设计模式
  • 算法思想
  • 编码规范
友链
关于
GitHub (opens new window)
  • Java基础

  • Java集合

  • MySQL

    • MySQL深入01-查询语句
    • MySQL深入02-更新语句
    • MySQL深入03-事务隔离
    • MySQL深入04-深入浅出索引
    • MySQL深入05-全局锁、表锁、行锁
    • MySQL深入06-事务隔离再探
    • MySQL深入07-普通索引和唯一索引
    • MySQL深入08-为什么会选错索引
    • MySQL深入09-字符串字段加索引
    • MySQL深入10-脏页刷新
    • MySQL深入11-数据库表空间回收
    • MySQL深入12-count()
    • MySQL深入13-order by
    • MySQL深入14-正确显示随机消息
    • MySQL深入15-索引失效案例分析
      • MySQL深入-索引失效案例分析
      • 条件字段函数操作
      • 隐式类型转换
      • 隐私字符编码转换
      • 小结
      • 参考
    • MySQL深入16-查询一行数据执行慢
    • MySQL深入17-幻读
    • MySQL深入18-改一行语句锁问题
    • MySQL深入19-暂时提高数据库性能方案
    • MySQL深入20-这么保证数据不丢
    • MySQL深入21-主备一致的保证
    • MySQL深入22-高可用性的保证
    • MySQL深入23-备库延迟好几个小时
    • MySQL深入24-主库出问题,从库这么办
    • MySQL深入25-读写分离的过期读问题
    • MySQL深入26-判断数据库是否出问题
    • MySQL深入27-误删数据的处理方案
    • MySQL深入28-kill不掉的语句
    • MySQL深入29-查询对内存的影响
    • MySQL深入30-join深入
    • MySQL深入31-join语句优化
    • MySQL深入32-临时表深入
    • MySQL深入33-内部临时表何时使用
    • MySQL深入34-InnoDB和Memory
    • MySQL深入35-自增主键为什么不连续
    • MySQL深入36-insert语句的锁
    • MySQL深入37-如何快速复制一张表
    • MySQL深入38-grant和flush privileges
    • MySQL深入39-分区表
    • MySQL深入40-自增id用完如何处理
  • Redis

  • JVM

  • 多线程

  • 计算机网络

  • Spring

  • Kafka

  • Elasticsearch

  • Python

  • 面试专题

  • 知识库
  • MySQL
旭日
2023-03-31
目录

MySQL深入15-索引失效案例分析

# MySQL深入-索引失效案例分析

相关文章:

MySQL高级-索引使用 (opens new window)

有时候一条SQL语句,看上去逻辑差不多,由于不正确使用索引可能导致某一些我们认为很快的语句结果走了全表扫描导致速度很慢。

# 条件字段函数操作

现有如下表结构定义:

CREATE TABLE `tradelog` (
  `id` int(11) NOT NULL,
  `tradeid` varchar(32) DEFAULT NULL,
  `operator` int(11) DEFAULT NULL,
  `t_modified` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `tradeid` (`tradeid`),
  KEY `t_modified` (`t_modified`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

对于这条SQL语句:

select count(*) from tradelog where month(t_modified)=7;

由于我们在t_modified字段上有索引,我们简单认为该条SQL语句已经能很快执行,但是却发现执行了很长时间才返回结果。

如果对字段做了函数计算,就用不上索引了,这是MySQL的规定

那为什么条件是where t_modified='2018-7-1'的时候就可以用上索引,而month(t_modified)=7就用不上索引?

image-20220617085437729

  • 如果是where t_modified='2018-7-1':由于B+树的快速定位能力,这个索引会按照上图绿色箭头的方法,快速定位到2018-7-1,这是因为同一层兄弟节点是有序的。

  • 如果是month(t_modified)=7:相当于第一层我传入的是一个7,而这个7月MySQL并不知道是那一年的7月。

对索引字段做函数操作,可能会破环索引值的有序性,因此优化器决定放弃走树搜索功能。

当然在这个例子中,MySQL也不是放弃使用这个索引。优化器放弃了树搜索功能,但是可以选择主键索引遍历或者t_modified索引遍历,比较两者索引大小后,索引t_modified更小,因此最终还是会选择遍历t_modified。

 EXPLAIN select count(*) from tradelog where month(t_modified)=7;

image-20220617091036144

# 隐式类型转换

select * from tradelog where tradeid=110717;

交易编号tradeid上是存在索引的,但是通过explain命令分析出来这条SQL语句是没有走索引的。由于tradeid 的字段类型是 varchar(32),而输入的参数却是整型,所以需要做类型转换。

数据类型转换规则

select "10" > 9
  • 如果规则是“将字符串转成数字”,那么就是做数字比较,结果应该是 1
  • 如果规则是“将数字转成字符串”,那么就是做字符串比较,结果应该是 0

经过验证,结果为1,说明MySQL在数字和字符串比较的时候,是把字符串转为数字。

对于上面的SQL语句由于传入的数字,而本身字段定义为字符串,就需要把字符串转换为数字。

select * from tradelog where  CAST(tradid AS signed int) = 110717;

也就是说,这条语句就和上面说到的相同:对索引字段做函数操作,优化器会放弃走树搜索功能。

如果id类型是int,执行如下语句:

select * from tradelog where id="83126";

这个语句就需要把条件的字符串转换为数字,不会触发索引字段函数操作,所以会走索引。

# 隐私字符编码转换

现在我们来进行一个连表操作,但是两个表的字符编码不相同。

CREATE TABLE `trade_detail` (
	`id` INT ( 11 ) NOT NULL,
	`tradeid` VARCHAR ( 32 ) DEFAULT NULL,
	`trade_step` INT ( 11 ) DEFAULT NULL,
	`step_info` VARCHAR ( 32 ) DEFAULT NULL,
PRIMARY KEY ( `id` ),
KEY `tradeid` ( `tradeid` )) ENGINE = INNODB DEFAULT CHARSET = utf8;

这时候我们来查询id=2的交易所有操作步骤信息:

SELECT
d.* 
FROM
	tradelog l,
	trade_detail d 
WHERE
	d.tradeid = l.tradeid 
	AND l.id = 2;

下面来分析这条SQL语句:

EXPLAIN SELECT
d.* 
FROM
	tradelog l,
	trade_detail d 
WHERE
	d.tradeid = l.tradeid 
	AND l.id = 2;

image-20220617094346641

  • 第一行显示优化器会先在交易记录表 tradelog 上查到 id=2 的行,这个步骤用上了主键索引,rows=1 表示只扫描一行;

  • 第二行 key=NULL,表示没有用上交易详情表 trade_detail 上的 tradeid 索引,进行了全表扫描。

image-20220617094733337

执行过程如下:

  1. 根据id在tradelog表找到L2这一行。
  2. 从L2这一行中取出tradeid字段的数值。
  3. 根据取出tradeid的数值到trade-detail表中找到匹配的行,由于没有使用的索引,所以是遍历主键索引的方式,一行一行进行判断。

但是我们本来希望在trade-detail中使用 tradeid 索引能够快速定位到等值的行。

字符集 utf8mb4 是 utf8 的超集,所以当这两个类型的字符串在做比较的时候,MySQL 内部的操作是,先把 utf8 字符串转成 utf8mb4 字符集,再做比较。由于我们在trade-detail中输入的是utf-8的字符串,需要进行转换成utf8mb4字符集,这就触发了原则:对索引字段做函数操作,优化器会放弃走树搜索功能。

也就是字符串不同只是外在,内在还是在于我们对索引字段进行了函数操作。

下面我们把两表操作反过来一下:

EXPLAIN select l.operator from tradelog l , trade_detail d where d.tradeid=l.tradeid and d.id=4;

image-20220617100533509

由于现在驱动表变成了trade_detail,被驱动表是tradelog,由于第二次传给tradelog是uft8mb4的字符集,所以是将=之后的进行转换,就不涉及到对索引字段进行函数操作。

# 小结

对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。 无论是直接进行操作,还是隐式类型转换,或者隐式字符编码转换,都是在索引字段做函数操作导致了全索引扫描。

# 参考

MySQL 实战 45 讲-极客时间 (opens new window)

#MySQL
上次更新: 2024/06/29, 15:13:44
MySQL深入14-正确显示随机消息
MySQL深入16-查询一行数据执行慢

← MySQL深入14-正确显示随机消息 MySQL深入16-查询一行数据执行慢→

最近更新
01
基础概念
10-31
02
Pytorch
10-30
03
Numpy
10-30
更多文章>
Theme by Vdoing | Copyright © 2021-2024 旭日 | 蜀ICP备2021000788号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式