MySQL实战解析底层---为什么这些SQL语句逻辑相同,性能却差异巨大

news2024/12/26 20:54:52

目录

前言

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

案例二:隐式类型转换

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


  • 前言

  • 在MySQL中,有很多看上去逻辑相同,但性能却差异巨大的SQL语句
  • 对这些语句使用不当的话,就会不经意间导致整个数据库的压力变大
  • 这里挑选了三个这样的案例
  • 希望再遇到相似的问题时,可以做到举一反三、快速解决问题
  • 案例一:条件字段函数操作

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

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

  • 由于t_modified字段上有索引,于是你就很放心地在生产库中执行了这条语句,但却发现执行了特别久,才返回了结果
  • 但如果对字段做了函数计算,就用不上索引了,这是MySQL的规定
  • 为什么条件是wheret_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的时候,在树的第一层就不知道该怎么办了
  • 也就是说,对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能
  • 需要注意的是,优化器并不是要放弃使用这个索引
  • 在这个例子里,放弃了树搜索功能,优化器可以选择遍历主键索引,也可以选择遍历索引t_modified,优化器对比索引大小后发现,索引t_modified更小,遍历这个索引比遍历主键索引来得更快
  • 因此最终还是会选择索引t_modified
  • 接下来使用explain命令,查看一下这条SQL语句的执行结果

  • key="t_modified"表示的是,使用了t_modified这个索引
  • 在测试表数据中插入了10万行数据,rows=100335,说明这条语句扫描了整个索引的所有值
  • Extra字段的Using index,表示的是使用了覆盖索引
  • 也就是说,由于在t_modified字段加了month()函数操作,导致了全索引扫描
  • 为了能够用上索引的快速定位能力,就要把SQL语句改成基于字段本身的范围查询
  • 按照下面这个写法,优化器就能按照我们预期的,用上t_modified索引的快速定位能力了

  • 当然,如果系统上线时间更早,或者后面又插入了之后年份的数据的话,就需要再把其他年份补齐
  • 到这里说明了,由于加了month()函数操作,MySQL无法再使用索引快速定位功能,而只能使用全索引扫描
  • 不过优化器在个问题上确实有“偷懒”行为,即使是对于不改变有序性的函数,也不会考虑使用索引
  • 比如,对于select *fromtradelog where id + 1 = 10000这个SQL语句,这个加1操作并不会改变有序性,但是MySQL优化器还是不能用id索引快速定位到9999这一行
  • 所以,需要在写SQL语句的时候,手动改写成 where id = 10000 -1才可以
  • 案例二:隐式类型转换

  • 一起看一下这条SQL语句:

  • 交易编号tradeid这个字段上,本来就有索引,但是explain的结果却显示,这条语句需要走全表扫描
  • tradeid的字段类型是varchar(32),而输入的参数却是整型,所以需要做类型转换
  • 那么,现在这里就有两个问题:
    • 1-数据类型转换的规则是什么?
    • 2-为什么有数据类型转换,就需要走全索引扫描?
  • 先来看第一个问题,那数据库里面类型这么多,这种数据类型转换规则更多,记不住应该怎么办呢?
  • 这里有一个简单的方法,看 select “10” > 9的结果:
    • 1-如果规则是“将字符串转成数字”,那么就是做数字比较,结果应该是1
    • 2-如果规则是“将数字转成字符串”,那么就是做字符串比较,结果应该是0
  • 验证结果如图所示

  • 从图中可知,select “10” > 9返回的是1
  • 所以就能确认MySQL里的转换规则了:在MySQL中,字符串和数字做比较的话,是将字符串转换成数字
  • 这时再看这个全表扫描的语句:

  • 就知道对于优化器来说,这个语句相当于:

  • 也就是说,这条语句触发了上面说到的规则:对索引字段做函数操作,优化器会放弃走树搜索功能
  • 案例三:隐式字符编码转换

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

  • 这时候,如果要查询id=2的交易的所有操作步骤信息,SQL语句可以这么写:

  • 语句的explain结果

  • 来看下这个结果:
    • 1-第一行显示优化器会先在交易记录表tradelog上查到id=2的行,这个步骤用上了主键索引,rows=1表示只扫描一行
    • 2-第二行key=NULL,表示没有用上交易详情表trade_detail上的tradeid索引,进行了全表扫描
  • 在这个执行计划里,是从tradelog表中取tradeid字段,再去trade_detail表里查询匹配字段
  • 因此把tradelog称为驱动表,把trade_detail称为被驱动表,把tradeid称为关联字段
  • 接下来看下这个explain结果表示的执行流程:

  • 图中:
    • 第1步,是根据id在tradelog表里找到L2这一行
    • 第2步,是从L2中取出tradeid字段的值
    • 第3步,是根据tradeid值到trade_detail表中查找条件匹配的行
    • explain的结果里面第二行的key=NULL表示的就是,这个过程是通过遍历主键索引的方式,一个一个地判断tradeid的值是否匹配
  • 进行到这里,会发现第3步不符合预期
  • 因为表trade_detail里tradeid字段上是有索引的,本来是希望通过使用tradeid索引能够快速定位到等值的行
  • 但,这里并没有
  • 这是因为这两个表的字符集不同,一个是utf8,一个是utf8mb4,所以做表连接查询的时候用不上关联字段的索引
  • 这个回答,也是通常你搜索这个问题时会得到的答案
  • 但是为什么字符集不同就用不上索引呢?
  • 问题是出在执行步骤的第3步,如果单独把这一步改成SQL语句的话,那就是:

  • 其中,$L2.tradeid.value的字符集是utf8mb4
  • 参照前面的两个例子,字符集utf8mb4是utf8的超集,所以当这两个类型的字符串在做比较的时候
  • MySQL内部的操作是,先把utf8字符串转成utf8mb4字符集,再做比较
  • 这个设定很好理解,utf8mb4是utf8的超集
  • 类似地,在程序设计语言里面,做自动类型转换的时候,为了避免数据在转换过程中由于截断导致数据错误,也都是“按数据长度增加的方向”进行转换的
  • 因此,在执行上面这个语句的时候,需要将被驱动数据表里的字段一个个地转换成utf8mb4,再跟L2做比较
  • 也就是说,实际上这个语句等同于下面这个写法:

  • CONVERT()函数,在这里的意思是把输入的字符串转成utf8mb4字符集
  • 这就再次触发了上面说到的原则:对索引字段做函数操作,优化器会放弃走树搜索功能
  • 到这里终于明确了,字符集不同只是条件之一,连接过程中要求在被驱动表的索引字段上加函数操作,是直接导致对被驱动表做全表扫描的原因
  • 作为对比验证,再提另外一个需求,“查找trade_detail表里id=4的操作,对应的操作者是谁”,再来看下这个语句和它的执行计划

  • explain结果

  • 这个语句里trade_detail 表成了驱动表,但是explain结果的第二行显示,这次的查询操作用上了被驱动表tradelog里的索引(tradeid),扫描行数是1
  • 这也是两个tradeid字段的join操作,为什么这次能用上被驱动表的tradeid索引呢?
  • 来分析一下
  • 假设驱动表trade_detail里id=4的行记为R4,那么在连接的时候,被驱动表tradelog上执行的就是类似这样的SQL 语句:

  • 这时候$R4.tradeid.value的字符集是utf8,按照字符集转换规则,要转成utf8mb4,所以这个过程就被改写成:

  • 这里的CONVERT函数是加在输入参数上的,这样就可以用上被驱动表的traideid索引
  • 理解了原理以后,就可以用来指导操作了
  • 如果要优化语句的执行过程,有两种做法:
  • 比较常见的优化方法是,把trade_detail表上的tradeid字段的字符集也改成utf8mb4,这样就没有字符集转换的问题了

  • 如果能够修改字段的字符集的话,是最好不过了
  • 但如果数据量比较大, 或者业务上暂时不能做这个DDL的话,那就只能采用修改SQL语句的方法了

  • SQL语句优化后的explain结果

  • 这里主动把 l.tradeid转成utf8,就避免了被驱动表上的字符编码转换,从explain结果可以看到,这次索引走对了

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

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

相关文章

动态规划V (85、91、97)-最近都开始摆烂

CP85 最大矩形 题目描述: 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 学习记录: 对每一个地方都去统计最大举行的话,会有很多多余的计算,题…

windows操作系统线程结构体

上一篇我们介绍了进程结构体,这节我们介绍下线程结构体:ETHREAD。还是去windbg里面去看一下这个结构体的长相: 依旧是一大堆成员,我们只关注一些比较重要的结构体成员。在进程结构体中的第一个成员是一个子结构体Pcb,在线程结构体中&#xff…

【Simulink】基于FCS-MPC的带阻感负载的三相逆变器控制(Matlab Function)

之前写过三相并网逆变器FCS-MPC的博客 👉【Simulink】基于FCS-MPC的三相并网逆变器控制(Matlab Function) 应用的对象是并网的,用一个电压源(Three-Phase Programmable Voltage Source)模拟交流电网。 本篇…

6.S081——设备中断与驱动部分(串口驱动与Console)——xv6源码完全解析系列(7)

0.briefly speaking 之前我们研究过Xv6中的陷阱机制,并搞懂了系统调用的全部流程,接下来我们以UART和console为研究对象,深入研读一下Xv6内核中有关设备中断驱动的代码,并对UART、shell、console、键盘、显示器等设备的协同运作过…

【算法总结】——排列型回溯

文章目录 排列型回溯例题1——46. 全排列例题2——N皇后 分析回溯时间复杂度的另一种技巧 排列型回溯 相比于组合,排列型回溯对于元素的顺序是有要求的。 为了告诉回溯下面还可以选择哪些数字,可以: 记录已经被选择的数字用一个集合存储还…

【Linux】16. 动静态库

1. 库概念的引出 但是如果只是单纯的将多个.o文件提供给使用者,那么如果.o文件过多链接就会变得非常复杂,于是我们考虑将所有的.o文件打包给使用者提供一个库文件即可。 库的本质就是.o文件的集合 2. 动静态库概念 在之前的学习过程中我们认识到动静态…

免费:5000个高清视频素材 (个人免费版权,含9个利基)

免费:5000个高清视频素材 (个人免费版权,含9个利基) 嘿!你喜欢制作视频吗?总是在寻找一些酷炫的素材,但又担心会侵犯版权吗?别担心,我有一个超级好消息要告诉你!现在,我…

代码随想录算法训练营第39天 | 62.不同路径 + 63.不同路径 II

今日任务 目录 62.不同路径 - Medium 63.不同路径 II - Medium 62.不同路径 - Medium 题目链接:力扣-62. 不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。 机器人每次只能向下或者向右移动一步。机器…

【数据结构】排序

插入排序 把当前遍历到的元素前的元素序列是排好序的,把当前元素放到前边的序列中进行排序。 直接插入排序 不带哨兵 void InsertSort(int A[],int n) { int i,j,temp; for(i1;i<n;i) if(A[i]<A[i-1]) { tempA[i]; for(ji-1;j>0 && A[j]>temp;--j) A[j…

深入理解深度学习——BERT派生模型:参数共享ALBERT

分类目录&#xff1a;《深入理解深度学习》总目录 预训练语言模型的一个趋势是使用更大的模型配合更多的数据&#xff0c;以达到“大力出奇迹”的效果。随着模型规模的持续增大&#xff0c;单块GPU已经无法容纳整个预训练语言模型。为了解决这个问题&#xff0c;谷歌提出了ALBE…

深度学习训练营之文本分类识别

深度学习训练营之文本分类识别 原文链接环境介绍前置工作设置环境设置GPU加载数据 构建词典生成数据批次和迭代器模型定义定义实例 定义训练函数和评估函数模型训练模型评估 原文链接 &#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f366; 参考…

面具下的flag

打开文件是你的名字动漫的一张图片 用010打开文件&#xff0c;比较可疑的有三处 首先文件头是正确的&#xff0c;只是这边显示的Exif&#xff0c;之前没有特别注意jpg文件有这个头 其次是文件中包含两处flag信息&#xff0c;一个是类似隐藏的文件&#xff0c;一个疑似是第二段f…

Mysql高级查询语句

数据库是用来存储数据&#xff0c;更新&#xff0c;查询数据的工具&#xff0c;而查询数据是一个数据库最为核心的功能&#xff0c;数据库是用来承载信息&#xff0c;而信息是用来分析和查看的。所以掌握更为精细化的查询方式是很有必要的。本文将围绕数据的高级查询语句展开。…

题集-栈和队列的相互转化

这里&#xff0c;队列的性质是先入先出&#xff0c;但是栈的性质是后入先出。两个队列就可以通过相互捯实现数据的后入先出。 typedef int QDataType&#xff1b; //这是一个队列结点的结构 typedef struct QueueNode { struct QueueNode* next; QDataType data; }QNode; //这是…

C++之工厂模式

目录 一、为什么要使用工厂模式 优点 缺点 二、简单工厂&#xff08;Simple Factory&#xff09; 好处&#xff1a; 不足&#xff1a; 三、工厂方法&#xff1a; 好处&#xff1a; 不足&#xff1a; 四、抽象工厂&#xff08;Abstract Factory&#xff09; 一、为什…

RWA 成下一轮加密大叙事,PoseiSwap 的价值正在凸显

PoseiSwap是Nautilus Chain上的首个DEX&#xff0c;在Nautilus Chain模块化开发的支持下&#xff0c;PoseiSwap能够以更具延展性的方式来构建应用&#xff0c;并能够为交易者提供极佳的交易体验。基于Nautilus Chain支持下的Zk-Rollup方案&#xff0c;PoseiSwap构建了基于零知识…

文心一言 VS 讯飞星火 VS chatgpt (45)-- 算法导论6.1 3题

三、证明:在最大堆的任一子树中&#xff0c;该子树所包含的最大元素在该子树的根结点上。 文心一言&#xff1a; 在最大堆中&#xff0c;根结点是整个堆中最大元素的孩子&#xff0c;因此它包含的最大元素是在该子树的根结点上。 我们可以通过递归的方式证明任意子树中的最大…

C++11 线程库—互斥锁

前言 多线程因其调度的随机性和时间片分配&#xff0c;如果没有限制的访问临界资源&#xff0c;会导致出现无法预测的结果&#xff0c;也无法达到预期。 所以&#xff0c;访问临界区&#xff0c;需要是原子性的&#xff0c;在一个线程完成之前&#xff0c;不能有其他线程访问&…

找bug号召

我的博客是为了完成这个找 bug 号召&#xff1a; https://bbs.csdn.net/topics/614134877。 文章目录 测试场景之三&#xff1a; 选择你的身份 测试场景之三&#xff1a; 选择你的身份 同样在个人中心的最下面&#xff0c;你可以看到身份选择的界面。 注意&#xff0c;你要把鼠…

Python元组、集合、字典(超详细举例、讲解和区分)

总有一天你要一个人在暗夜中&#xff0c;向那座桥走过去 文章目录 一、元组 字符串、元组、列表的总结 二、集合 1.定义空集合 2.创建集合 &#xff08;1&#xff09;直接创建 &#xff08;2&#xff09;set函数 3.列表、元组、字符串、字典的去重 4.向集合中添加元素…