前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Mysql索引-不会使用索引的场景

Mysql索引-不会使用索引的场景

作者头像
小土豆Yuki
发布2020-11-03 11:35:50
1.1K0
发布2020-11-03 11:35:50
举报
文章被收录于专栏:洁癖是一只狗洁癖是一只狗

在日常开发中,我们经常会发现,Mysql中一些逻辑上一样的sql,往往性能差异很大,至于为什么会发生这样的问题,今天我们就看看几个常见的案例

案例一:条件字段函数操作

假设我们有一张表如下建表语句

代码语言:javascript
复制
mysql> 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;

现在我们的需求就是获取年份中7月份的数据,逻辑上并不复杂,我们也回想很快想到下面语句

代码语言:javascript
复制
select count(*)  from tradelog where month(t_modified)=7

但是我们发现在数据量大的时候,这条语句返回的时间非常长,但是我们的确建立了t_modified的索引,为什么还是这么慢呢

此时我们可以看看t_modified的索引树,如下图

上面的数字是函数计算的值,此时我们发现,如果我们使用下面语句会快速的根据上图的绿色箭头找到对应的值

代码语言:javascript
复制
select *  from  tradelog where t_modified='2018-7-1'

实际上,B+树提供了快速定位的能力,来源于同一层节点的有序性。

但是,如果计算month()函数的话,你会看到传入7的时候,在树的第一层就不知道该怎么办了,也就是说,对索引做函数操作,会破坏索引值的有序性,因此优化器就决定放弃走树的搜索。

但事实上,优化器并没有放弃使用索引,但是优化器可以遍历索引,可以选择主键索引和t_modified字段索引,优化器发现t_modified索引树比较小,最终还是选择了字段索引,

可以看到extra的值是Using index,说明使用了覆盖索引。虽然使用了索引,但是也是全索引扫描。

此时我们可以使用下面语句,实现我们需要的结果,下面语句也用上了索引的快速定位能力,但是比较繁琐

代码语言:javascript
复制
mysql> select count(*) from tradelog where
    -> (t_modified >= '2016-7-1' and t_modified<'2016-8-1') or
    -> (t_modified >= '2017-7-1' and t_modified<'2017-8-1') or
    -> (t_modified >= '2018-7-1' and t_modified<'2018-8-1');

总结:发现如果我们对字段使用函数,会破坏索引的有序性,是无法使用索引快速定位的功能,而只能全索引扫描,需要注意的是,函数没有破坏索引的有序性,优化器也不会考虑使用索引的快速定位能力的,

案例二:隐式类型转换

我们先看看下面语句

代码语言:javascript
复制
mysql> select * from tradelog where tradeid=110717;

我们发现上面虽然我们的tradeid有索引,但是他还是会走全表扫描,这个又是为什么呢,

其实我们发现tradeid他的类型是varchar(32),而输入的参数值确实int,索引要做类型转换。在mysql中,字符串和数字做比较的话,是将字符串转换成数字.

上面的语句实际上在优化器里面就如下面语句

代码语言:javascript
复制
mysql> select * from tradelog where  CAST(tradid AS signed int) = 110717;

从上一个案例我们知道使用了函数是不会走索引的,优化器会放弃索引的快速定位能力。

案例三:隐式字符编码转换

我们建立下面表,且插入一些数据

代码语言:javascript
复制
mysql> 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;

insert into tradelog values(1, 'aaaaaaaa', 1000, now());
insert into tradelog values(2, 'aaaaaaab', 1000, now());
insert into tradelog values(3, 'aaaaaaac', 1000, now());

insert into trade_detail values(1, 'aaaaaaaa', 1, 'add');
insert into trade_detail values(2, 'aaaaaaaa', 2, 'update');
insert into trade_detail values(3, 'aaaaaaaa', 3, 'commit');
insert into trade_detail values(4, 'aaaaaaab', 1, 'add');
insert into trade_detail values(5, 'aaaaaaab', 2, 'update');
insert into trade_detail values(6, 'aaaaaaab', 3, 'update again');
insert into trade_detail values(7, 'aaaaaaab', 4, 'commit');
insert into trade_detail values(8, 'aaaaaaac', 1, 'add');
insert into trade_detail values(9, 'aaaaaaac', 2, 'update');
insert into trade_detail values(10, 'aaaaaaac', 3, 'update again');
insert into trade_detail values(11, 'aaaaaaac', 4, 'commit');

我们在执行下面插叙语句

代码语言:javascript
复制
mysql> select d.* from tradelog l, trade_detail d where d.tradeid=l.tradeid and l.id=2; /*语句Q1*/

看看是否使用了索引

我们发现第一行使用索引,这个索引是建立在主键索引上的,并且扫描了一行,但是第二行,我们发现没有使用索引,进行了全表扫描。

在这个执行计划中,是从tradelog表中取tradeid字段,在到trade_detail表查询匹配字段,因此我们称tradeLog叫驱动表,而trade_detail为被驱动表.把tradeid称为关联字段

他的具体执行步骤如下

  1. 根据id在tradelog表找到一行
  2. 获取到tradeid字段的值
  3. 在根据tradeid的值到trade_detail表中匹配符合的行,而在explain中第二行我们发现key=null,说明是按照遍历主键索引的方式,一个个判断tradeid的值是否符合

正常按照我们的理解,第二行的tradeid它也是有索引的,应该也是使用索引才对,为什么没有使用索引的

其实第三步,相当是下面语句

代码语言:javascript
复制
mysql> select * from trade_detail where tradeid=$L2.tradeid.value;

其中,$L2.tradeid.value的字符集是utf8mb4。

此时我们可以想到字符集uft8mb4是uft8的超集,索引当两个字段比较的时候,会把utf8装成utf8mb4字符集,再做比较.

因此在执行上面这个语句的时候,需要被驱动表的字段一个个转换成uft8mb4,再去比较。

代码语言:javascript
复制
select * from trade_detail  where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value; 

此时我们知道,如果字段使用了函数,将不会使用索引快速定位能力,这里我们就明白了,为什么被驱动表会全表扫描

作为对比我们可以下面语句看看效果

代码语言:javascript
复制
mysql>select l.operator from tradelog l , trade_detail d where d.tradeid=l.tradeid and d.id=4;

我们发现为什么执行计划里面的第二句使用了索引呢,我们来分析一下

依然像上面分析的一样,我们看到下面语句

代码语言:javascript
复制
select operator from tradelog  where traideid =$R4.tradeid.value; 

然后我们发现$R4.tradeid.value的字符集是utf8,此时mysql会把这个字符转成utf8mb4,因此变成了下面语句

代码语言:javascript
复制
select operator from tradelog  where traideid =CONVERT($R4.tradeid.value USING utf8mb4); 

这里看到我们使用的函数在使用在了参数上,因此此时是可以使用traideid索引。

说到了这里,我们看看如何对我们开始的sql进行优化,两种方案

  • 直接把被驱动表的tradeid的字段字符改成uft8mb4
代码语言:javascript
复制
alter table trade_detail modify tradeid varchar(32) CHARACTER SET utf8mb4 default null   2.
  • 如果数据量比较大的,可以使用下面语句,主动在驱动表转成utf8
代码语言:javascript
复制
mysql> select d.* from tradelog l , trade_detail d where d.tradeid=CONVERT(l.tradeid USING utf8) and l.id=2; 

总结:上面三个案例其实是一件事,就是对索引字段函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索能力.

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 洁癖是一只狗 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档