MySQL为什么会选错索引

news2024/11/17 14:44:09

        在平时不知道一有没有遇到过这种情况,我明明创建了索引,但是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.coloradmin.cn/o/1550468.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【分布式计算框架】Hadoop伪分布式安装

🕺作者: 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux 😘欢迎 ❤️关注 👍点赞 🙌收藏 ✍️留言 文章目录 Hadoop伪分布式安装一、实验目的二、实验环境三、实验内容基本任务1:安装Linux虚拟机(至…

[Vue3] 配置 Pinia 并存储、读取、修改数据 | 集中式状态(数据)管理

安装 npm i pinia main.ts import ./assets/main.cssimport { createApp } from vue import App from ./App.vue import { createPinia } from pinia // 引入Pinia// 创建一个应用 const app createApp(App)const pina createPinia() app.use(pina) // 挂载整个应用到app容器…

『Apisix进阶篇』动态负载均衡:APISIX的实战演练与策略应用

🚀『Apisix系列文章』探索新一代微服务体系下的API管理新范式与最佳实践 【点击此跳转】 📣读完这篇文章里你能收获到 🎯 掌握APISIX中多种负载均衡策略的原理及其适用场景。📈 学习如何通过APISIX的Admin API和Dashboard进行负…

AIGC趋势下软件工程强智能编码来临了么?

一、背景 在AIGC(AI Generated Content,人工智能生成内容)的趋势下,软件工程领域的“强智能编码”是指通过深度学习、自然语言处理等前沿技术,使AI具备理解、学习、推理和生成代码的能力,从而实现自动化或…

操作系统:经典进程同步问题的高级探讨

✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭❤~✨✨ 🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢,在这里我会分享我的知识和经验。&am…

ChatGPT提升工作生产力方法和技巧ChatGPT enhances work productivity methods and techniques

使用ChatGPT提升工作效率的一些详细步骤和技巧: 1. 快速撰写和编辑文档 撰写文档:当需要撰写报告、方案、邮件等内容时,可以直接向ChatGPT提出请求,例如:“请帮我写一份关于第一季度销售业绩的总结报告。”之后&#x…

Mybatis-plus + 通用mapper(tk.mybatis)

推荐课程:MyBatisPlus实战教程02-课程介绍与案例演示_哔哩哔哩_bilibili 官网:MyBatis-Plus (baomidou.com) 目录 01 引言 1)MyBatis与MyBatis-Plus区别 2)Mybatis-plus入门案例 案例一:spring容器版本的案例 案例…

将二进制数a的每一位右移b位operator.rshift(a,b)

【小白从小学Python、C、Java】 【计算机等考500强证书考研】 【Python-数据分析】 将二进制数a的 每一位右移b位 operator.rshift(a,b) [太阳]选择题 请问执行operator.rshift(4, 1)的结果为? import operator print("【显示】二进制2:",bi…

Tuxera for Mac2024免费读写硬盘U盘工具

作为软件产品专家,我对各类软件都有较为深入的了解,下面介绍Tuxera for Mac这款读写硬盘/U盘工具的相关信息: Tuxera for Mac是一款高效稳定的NTFS读写工具,专为解决Mac系统无法直接读写NTFS格式驱动器的问题而设计。它提供了完整…

【文献分享】FLUNED(一种用于流体活化计算的开源工具)在水中的开发和验证

题目:Development and validation in water of FLUNED, an open-source tool for fluid activation calculations 链接:Redirecting FLUNED(一种用于流体活化计算的开源工具)在水中的开发和验证 在核聚变装置中,高…

【数据结构】树、二叉树与堆(长期维护)

下面是关于树、二叉树、堆的一些知识分享,有需要借鉴即可。 一、初识树(了解即可) 1.树的概念 概念:一种非线性数据结构,逻辑形态上类似倒挂的树 树的构成:由一个根左子树右子树构成,其中子树…

布隆过滤器详讲

本文旨在讲解布隆过滤器的原理以及实现方式,希望通过本文能使读者对布隆过滤器有一定的认识! 一、布隆过滤器的引入 在讲解布隆过滤器之前,我们还是先提及一下前面讲的位图行,位图可以处理大量的数据,广泛用于查找等…

Vue-vue3

一、Vue3简介二、Vue3有那些优化性能的提升源码升级拥抱TypeScript新的特性 三、创建Vue3.0工程四、Vue3工程结构(使用cli创建的vue3)五、常用的Composition API(组合式API)setupsetup的两个注意点 ref函数reactive函数Vue3.0中的…

【Java程序设计】【C00381】基于(JavaWeb)Springboot的爱心商城管理系统(有论文)

【C00381】基于(JavaWeb)Springboot的爱心商城管理系统(有论文) 项目简介项目获取开发环境项目技术运行截图 博主介绍:java高级开发,从事互联网行业六年,已经做了六年的毕业设计程序开发&#x…

SSH 连接工具-Tabby(使用教程)

文章目录 SSH 连接工具-Tabby(使用教程)1.Tabby简介2.Tabby安装3.实现 SSH 远程连接4.SFTP文件传输5.将 Anaconda Prompt 配置到 Tabby 中 SSH 连接工具-Tabby(使用教程) 提起 SSH 大家首先想到的应该是国内的一款 Xshell 工具&a…

UE4 根据任意多个点,生成最近的线条

UE4自带的SplineMesh特点:Tangent值为0的时候,会断开一段距离,起点和终点并不是同一个位置;Tangent值不为0的时候,会计算出转角的mesh 1.计算所有线条的组合 2.Clear0宏:清除掉数组Distance0的值。注意这…

git提交-分支开发合并-控制台操作

git提交-分支开发合并-控制台操作 git的基本概念工作区、暂存区和版本库工作区:就是你在电脑里能看到的目录(隐藏目录 .git不算工作区)。暂存区:英文叫 stage 或 index。一般存放在本地的.git目录下的index 文件(.git/…

Qt/QML编程之路:QPainter与OpenGL的共用(49)

在Qt编程中,有时会有这样一种场景:用OpenGL显示了一个3维立体图,但是想在右下角画一个2D的表格,里面写上几个字。那么这个时候就会出现QPainter与OpenGL共用或者说2D、3D共用。但是问题是调用了QPainter,drawline之后呢,OPenGL的状态被清空了丢失了,3D不显示了。 在Ope…

共射极放大电路理论计算

目录: 1、概述 2、理论计算 3、Multisim仿真验证 1)静态工作点与放大倍数 2)输入阻抗仿真 1、概述 如下图所示的共射极放大电路,本内容主要计算静态工作点电压、电压放大倍数与输入输出阻抗。 2、理论计算 列出方程如下&am…

helm 部署 Kube-Prometheus + Grafana + 钉钉告警部署 Kube-Prometheus

背景 角色IPK8S 版本容器运行时k8s-master-1172.16.16.108v1.24.1containerd://1.6.8k8s-node-1172.16.16.109v1.24.1containerd://1.6.8k8s-node-2172.16.16.110v1.24.1containerd://1.6.8 安装 kube-prometheus mkdir -p /data/yaml/kube-prometheus/prometheus &&…