MySQL为什么会选错索引

        在平时不知道一有没有遇到过这种情况,我明明创建了索引,但是MySQL为何不用索引呢?为何要进行全索引扫描呢?

一、对索引进行函数操作

        假设现在维护了一个交易系统,其中交易记录表 tradelog 包含交易流水号(tradeid)、交易员id(operator)、交易时间(t_modified)等字段。为了便于描述,我们先忽略其他字段。这个表的建表语句如下:

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;

        假设,现在已经记录了从 2016 年初到 2018 年底的所有数据,运营部门有一个需求是,要统计发生在所有年份中 7 月份的交易记录总数。这个逻辑看上去并不复杂,你的 SQL 语句可能会这么写:

SELECT count(*) from tradelog WHERE MONTH(t_modified) = 7;

        由于 t_modified 字段上有索引,于是你就很放心地在生产库中执行了这条语句,但却发现执行了特别久,才返回了结果。

        如果你问 DBA 同事为什么会出现这样的情况,他大概会告诉你:如果对字段做了函数计算,就用不上索引了,这是 MySQL 的规定。

        现在你已经学过了 InnoDB 的索引结构了,可以再追问一句为什么?为什么条件是 where t_modified='2018-7-1’的时候可以用上索引,而改成 where month(t_modified)=7 的时候就不行了?

        下面是这个 t_modified 索引的示意图。方框上面的数字就是 month() 函数对应的值。

        如果你的 SQL 语句条件用的是 where t_modified='2018-7-1’的话,引擎就会按照上面绿色箭头的路线,快速定位到 t_modified='2018-7-1’需要的结果。

        实际上,B+ 树提供的这个快速定位能力,来源于同一层兄弟节点的有序性。但是,如果计算 month() 函数的话,你会看到传入 7 的时候,在树的第一层就不知道该怎么办了。

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

        需要注意的是,优化器并不是要放弃使用这个索引。

        执行计划如下:

        key="t_modified"表示的是,使用了 t_modified 这个索引;我在测试表数据中插入了 10 万行数据,rows=99960(思考题:这里为什么是99960呢?),说明这条语句扫描了整个索引的所有值;Extra 字段的 Using index,表示的是使用了覆盖索引。

        由于在 t_modified 字段加了 month() 函数操作,导致了全索引扫描。为了能够用上索引的快速定位能力,我们可以把 SQL 语句改成基于字段本身的范围查询。

        比如,对于 select * from tradelog where id + 1 = 10000 这个 SQL 语句,这个加 1 操作并不会改变有序性,但是 MySQL 优化器还是不能用 id 索引快速定位到 9999 这一行。所以,需要你在写 SQL 语句的时候,手动改写成 where id = 10000 -1 才可以。

二、隐私类型转换

        看一下这条 SQL 语句:

 select * from tradelog where tradeid = 110717;

        交易编号 tradeid 这个字段上,本来就有索引,但是 explain 的结果却显示,这条语句需要走全表扫描。你可能也发现了,tradeid 的字段类型是 varchar(32),而输入的参数却是整型,所以需要做类型转换。

        优化器执行SQL语句时相当于执行了:

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

        也就是说,这条语句触发了我们上面说到的规则:对索引字段做函数操作,优化器会放弃走树搜索功能

三、隐式字符编码转换

        假设系统里还有另外一个表 trade_detail,用于记录交易的操作细节。为了便于量化分析和复现,我往交易日志表 tradelog 和交易详情表 trade_detail 这两个表里插入一些数据。

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 的交易的所有操作步骤信息,SQL 语句可以这么写:

select d.* from tradelog l, trade_detail d where d.tradeid=l.tradeid and l.id=2;

        执行计划如下

来看下这个结果:

  1. 第一行显示优化器会先在交易记录表 tradelog 上查到 id=2 的行,这个步骤用上了主键索引,rows=1 表示只扫描一行;
  2. 第二行 key=NULL,表示没有用上交易详情表 trade_detail 上的 tradeid 索引,进行了全表扫描。

        如果你去问 DBA 同学,他们可能会告诉你,因为这两个表的字符集不同,一个是 utf8,一个是 utf8mb4,所以做表连接查询的时候用不上关联字段的索引。这个回答,也是通常你搜索这个问题时会得到的答案。

        参照前面的两个例子,你肯定就想到了,字符集 utf8mb4 是 utf8 的超集,所以当这两个类型的字符串在做比较的时候,MySQL 内部的操作是,先把 utf8 字符串转成 utf8mb4 字符集,再做比较。

        因此, 在执行上面这个语句的时候,需要将被驱动数据表里的字段一个个地转换成 utf8mb4,再做比较。

        等同于执行如下SQL

select * from trade_detail  where CONVERT(traideid USING utf8mb4)=$L2.tradeid.value; 

        CONVERT() 函数,在这里的意思是把输入的字符串转成 utf8mb4 字符集。这就再次触发了我们上面说到的原则:对索引字段做函数操作,优化器会放弃走树搜索功能

        综上所述:这三种类型其实都是触发了一个规则,平时要避免,提高查询效率。

四、优化器的逻辑

        优化器选择索引的目的,是找到一个最优的执行方案,并用最小的代价去执行语句。在数据库里面,扫描行数是影响执行代价的因素之一。扫描的行数越少,意味着访问磁盘数据的次数越少,消耗的 CPU 资源越少。

        扫描行数是怎么判断的?

        MySQL 在真正开始执行语句之前,并不能精确地知道满足这个条件的记录有多少条,而只能根据统计信息来估算记录数。

        这个统计信息就是索引的“区分度”。显然,一个索引上不同的值越多,这个索引的区分度就越好。而一个索引上不同的值的个数,称之为“基数”(cardinality)。也就是说,这个基数越大,索引的区分度越好。

        可以使用 show index 方法,看到一个索引的基数。

        MySQL 是怎样得到索引的基数的呢?这里,简单介绍一下 MySQL 采样统计的方法。

        为什么要采样统计呢?因为把整张表取出来一行行统计,虽然可以得到精确的结果,但是代价太高了,所以只能选择“采样统计”。

        采样统计的时候,InnoDB 默认会选择N个数据页,统计这些页面上的不同值,得到一个平均值,然后乘以这个索引的页面数,就得到了这个索引的基数。

        而数据表是会持续更新的,索引统计信息也不会固定不变。所以,当变更的数据行数超过 N/M 的时候,会自动触发重新做一次索引统计。

        从图中看到,tradelog 表中有10万行数据,索引统计值(cardinality 列)虽然不够精确,但大体上还是差不多的,选错索引一定还有别的原因。

        其实索引统计只是一个输入,对于一个具体的语句来说,优化器还要判断,执行这个语句本身要扫描多少行。

        如果选择的事普通索引,那么还需要拿着 ID 进行回表来查询整行数据,这个代价优化器也会计算在内,而如果直接扫描主键索引,是没有额外的代价。优化器会估算这两个代价来进行评估选择。

        我们可以通过 analyze table table 命令来重新进行统计。所以在实践中,如果发现explain的结果预估的rows值跟实际情况差距比较大,可以采用这个方法来处理。

往期经典推荐

MySQL索引优化实战宝典-CSDN博客

深入JVM内核揭示Java多态背后的神秘机制-CSDN博客

TiDB内核解密:揭秘其底层KV存储引擎如何玩转键值对_tidb 的key value是如何做到的-CSDN博客

MySQL计数优化探秘:COUNT(*)、COUNT(主键)与索引字段,谁是性能王者?-CSDN博客

MySQL中order by原来是这么工作的-CSDN博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/573605.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[STM32] Keil 创建 HAL 库的工程模板

Keil 创建 HAL 库的工程模板 跟着100ASK_STM32F103_MINI用户手册V1.1.pdf的第7章步骤进行Keil工程的创建。 文章目录 1 创建相关文件夹2 创建“main.c/h”和“stm32f1xx_clk.c/h”3 复制CMSIS和HAL库4 创建新的Keil工程5 添加组文件夹和工程文件6 配置Keil设置 1 创建相关文件…

[激光原理与应用-77]:基于激光器加工板卡的二次开发软件的系统软硬件架构

目录 一、1个板卡、1个激光器、1个振镜的应用架构、1个工位 (1)PLC (2)MES (3)加工板卡 (4)激光加工板卡与激光器之间的转接卡 (5)DB25、DB15 &#x…

基于51单片机的厨房一氧化碳温湿度烟雾粉尘监测报警Proteus仿真

地址:https://pan.baidu.com/s/19tp61m5fOORP47RNh8TWGA 提取码:1234 仿真图: 芯片/模块的特点: AT89C52/AT89C51简介: AT89C52/AT89C51是一款经典的8位单片机,是意法半导体(STMicroelectroni…

桶排序:原理、实现与应用

桶排序:原理、实现与应用 一、桶排序的基本原理二、桶排序的实现步骤三、桶排序的伪代码实现四、桶排序的C语言实现示例 在日常生活和工作中,排序是一个经常遇到的需求。无论是对一堆杂乱的文件进行整理,还是对一系列数据进行统计分析&#x…

基于深度学习的心律异常分类算法

基于深度学习的心律异常分类系统——算法设计 第一章 研究背景算法流程本文研究内容 第二章 心电信号分类理论基础心电信号产生机理MIT-BIH 心律失常数据库 第三章 心电信号预处理心电信号噪声来源与特点基线漂移工频干扰肌电干扰 心电信号读取与加噪基于小波阈值去噪技术的应用…

Wi-Fi 标准的演进

在数字时代的今天,Wi-Fi已经成为了我们生活中不可或缺的一部分,但这一无线通信技术的演进却是一个精彩而丰富的历程。从最初迈出的第一步,到如今的Wi-Fi 7高速数据传输,每一个Wi-Fi标准的诞生都伴随着无数创新和技术的突破。 802.…

《科学技术创新》是什么级别的期刊?是正规期刊吗?能评职称吗?

问题解答: 问:《科学技术创新》期刊是哪个级别? 答:省级 主管单位:黑龙江省科学技术协会 主办单位:黑龙江省科普事业中心 问:《科学技术创新》期刊影响因子? 答:(2…

Mysql数据库:高级SQL语言详解

目录 前言 一、按关键字排序查询 1、单字段排序 1.1 按某一字段升序排序 1.2 按某一字段降序排序 1.3 结合where进行条件进行排序 2、多字段排序 2.1 按多字段升序排序 2.2 按多字段降序排序 2.3 案例操作 3、区间判断及查询不重复记录 3.1 区间判断 3.1.1 AND/OR…

Linux——磁盘与文件系统管理

目录 磁盘分区的表示 硬盘分区 分区类型 确认系统中的磁盘设备——fdisk 规划硬盘中的分区——fdisk 文件系统 文件系统类型: 在分区中创建文件系统——mkfs,mkswap 挂载文件系统 mount命令 umount命令 查看分区挂载情况 设置启动载入&…

振弦采集仪在预防地质灾害监测中的作用与应用前景

振弦采集仪在预防地质灾害监测中的作用与应用前景 振弦采集仪(String Vibrating Sensor,简称SVM)是一种用于地质灾害监测的重要仪器,它通过测量地面振动信号来预测和预警地质灾害的发生。SVM的作用在于提供实时、准确的地质灾害监…

Jmeter 从登录接口提取cookie 并 跨线程组调用cookie (超详细)

文章目录 一、开始前的准备二、 业务场景介绍三、从登录接口提取cookies四、跨线程组调用cookies 一、开始前的准备 1、安装Jmeter,参考文章:JMeter 3.1 和JMeterPlugin的下载安装 2、设置配置文件使Cookie管理器保存cookie信息。 修改apache-jmeter-x…

Docker进阶:Docker-compose 实现服务弹性伸缩

Docker进阶:Docker-compose 实现服务弹性伸缩 一、Docker Compose基础概念1.1 Docker Compose简介1.2 Docker Compose文件结构 二、弹性伸缩的原理和实现步骤2.1 弹性伸缩原理2.2 实现步骤 三、技术实践案例3.1 场景描述3.2 配置Docker Compose文件3.3 使用 docker-…