记一次简单的SQL调优

news2025/2/8 23:12:25

记一次简单的SQL调优

  • 前言
  • 排查
  • 分析
    • 问题分析
      • SQL的执行流程
      • 存在的问题
      • 优化思路
    • 验证解决
      • 验证执行流程
      • 一些执行计划的基本知识
    • 优化
      • 先执行where条件过滤不需要的行,再进行左连接
      • 提示排序走索引
      • 为常用查询条件创建索引
      • 调整查询条件的顺序

前言

相信大家对后端数据库的SQL都不会陌生,但是有时候我们会无意间就写出一堆奇怪的SQL,可能当时还没有发现,但是没关系,客户后期会告诉你的,这也没有关系,只要后期不是你负责解决bug就好

可是很不幸有时候这种问题就是分配给你解决 。。。。。。

下面就是我遇到的某个问题:界面上某个查询数据的功能,被用户反馈响应越来越慢…从最初部署好的秒响应,到三四秒响应,再到现在的将近五六秒才响应…这对于一个查询而言可以说是慢的雅痞了。

用户:
在这里插入图片描述

经理:那个谁(指我),(抬头对视一眼)对,就你,去把这个问题解决下。

我:(飞身闪退几十米)不熟,别来沾边!

经理:(抬头皱眉)嗯?什么意思?

我:
在这里插入图片描述

经理掏出西瓜头,熟练带上
在这里插入图片描述
我: ?????????

经理:家人们,谁懂啊,遇到个下头程序员,九敏SOS。。。
在这里插入图片描述
经理:那你这个月的KPI可能不够工资可能会,emmm…你自己看着办吧

我:
在这里插入图片描述

排查

那就开始排查吧,哎,不相干也得干啊,不然窝囊费去哪里拿呢。不过有位将军曾经说过:
在这里插入图片描述
在这里插入图片描述
一想到他,我小腹突然升起一股暖流,莫名的力量让我突然的站起来!此时手机里响起工资到账的提示音
在这里插入图片描述

在这里插入图片描述
开干开干,先瞅一眼环境

系统数据库为ORACLE,索引结构为B+树,student表数据大概是120w行。

如果是查询慢的话,那定位到具体的查询语句就知道问题所在了,果然没过多久就定位到了查询语句(以下语句内容均为虚构 但执行遇到的情况一样):

		select * from student s
			left  join campus c on s.campusId = c.campusId 
        where 
			s.studentId = ,
			s.name like ,
			s.idCard = ,
			s.phone =   ,
			s.addmissonDate >= ,
			s.addmissionDate <= 
        order by s.admissionDate desc

先分析该SQL,上述SQL语句中显示了两个表的连接查询,一个是学生表 -- student ,存储了学生的基本信息,一个是校区表 - campus,存储了校区的基本信息

连接条件为学生表里面的校区id -- campusId ,对应校区表的campusId

查询条件为学生的学号,姓名,身份证,电话以及开学日期(addmissionDate)的起止时间,并且所有条件可选。

再以adminssionDate逆序排列

上述查询的目的是,根据条件查出对应学生及校区的信息

分页使用框架PageHelper,该框架处理后,在控制台输出的执行语句为:

		#默认查询
        SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID FROM (
			select * from student s
				left  join campus c on s.campusId = c.id
			order by s.admissionDate desc
        ) TMP_PAGE
        ) WHERE PAGEHELPER_ROW_ID <= 10 AND PAGEHELPER_ROW_ID > 0

		#带查询条件
         SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID FROM (
			select * from student s
				left  join campus c on s.campusId = c.campusId 
			where 
				#以下条件都是可选
				s.studentId = ,
				s.name like ,
				s.idCard = ,
				s.phone =  ,
				s.addmissonDate >= ,
				s.addmissionDate <= 
			order by s.admissionDate desc
        ) TMP_PAGE
        ) WHERE PAGEHELPER_ROW_ID <= 10 AND PAGEHELPER_ROW_ID > 0

注:该语句在数据库直接执行的执行时间为2、3s左右

在上述语句中返回字段我这里就图个方便,简单写成*,全部返回了,具体现实里还是根据自己需要的字段返回即可。

两个表中的索引都只有对应的主键索引campusId 并非校区表主键),无其他索引

好了,大家可以找找上述SQL的问题。

分析

问题分析

下面将进行问题的分析

SQL的执行流程

首先我们来分析这个SQL的结构,可以看出执行顺序是:

  • 先进行左连接,得到两个表之间所有数据的连接结果集
  • 然后通过where子句过滤掉不符合条件的列(如果有查询条件的话)
  • 再进行一个admissionDate逆序排列
  • 最后是分页

存在的问题

该过程中存在的问题:

  1. 先左连接再通过过滤条件去筛选,这会导致无论查询条件如何都会导致两个表做一个全表扫描
  2. 默认查询排序未走索引,由于默认的SQL无查询条件,那么基本就不会走索引了
  3. 未给常用的查询条件组合创建组合索引
  4. 查询条件顺序问题要把越常用的条件放前面,用的越少的放后面。我们知道索引结构为B+树是遵从最左匹配原则的,但是对like这样的模糊查询,很大概率不会走索引。比如我要查一个名叫xx辉的人,那么我的查询条件就是一个辉这种,就无法使用后续索引了。当然上述是基于组合索引的情况,也可以创建一个为该列创建一个全文索引

优化思路

  • 先执行where条件过滤不需要的行,再进行左连接
  • 提示默认查询排序走索引,创建addmissionDate列相关的索引,可以通过设置查询条件addmissionDate的默认值来提示查询语句使用改索引
  • 为常用查询条件创建索引
  • 调整查询条件的顺序

验证解决

验证执行流程

我们先验证SQL的流程

看下执行计划:

#默认无字段查询
explain plan for 
        SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID FROM (
			select * from student s
				left  join campus c on s.campusId = c.campusId 
			order by s.admissionDate desc
        ) TMP_PAGE
        ) WHERE PAGEHELPER_ROW_ID <= 10 AND PAGEHELPER_ROW_ID > 0


#全字段查询 - 这里先不看
explain plan for 
        SELECT * FROM ( SELECT TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID FROM (
			select * from student s
				left  join campus c on s.campusId = c.campusId 
			where 
				s.studentId = ,
				s.name like ,
				s.idCard = ,
				s.phone =  ,
				s.addmissonDate >= ,
				s.addmissionDate <= ,
				
			order by s.admissionDate desc
        ) TMP_PAGE
        ) WHERE PAGEHELPER_ROW_ID <= 10 AND PAGEHELPER_ROW_ID > 0

我们先以无查询条件的语句为例,运行以下命令查看:

	select * from table(DBMS_XPLAN.DISPLAY);

可以看到以下输出:

 Id  | Operation                 | Name              | Rows  |  Bytes |TempSpc| Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT          |                   |   130M|    18G|       |  2927K  (1)| 00:01:55 |
|*  1 |  VIEW                     |                   |   130M|    18G|       |  2927K  (1)| 00:01:55 |
|   2 |   COUNT                   |                   |       |       |       |            |          |
|   3 |    VIEW                   |                   |   130M|    16G|       |  2927K  (1)| 00:01:55 |
|   4 |     SORT ORDER BY         |                   |   130M|    11G|    13G|  2927K  (1)| 00:01:55 |
|*  5 |      HASH JOIN RIGHT OUTER|                   |   130M|    11G|       | 10268   (4)| 00:00:01 |
|   6 |       TABLE ACCESS FULL   | CAMPUS            |   231 |  9009 |       |     3   (0)| 00:00:01 |
|   7 |       TABLE ACCESS FULL   | STUDENT           |  1131K|    63M|       |  9936   (1)| 00:00:01 |

可以看到执行流程以及关键的点,跟我们上面的猜测差不多。值得注意的是,执行计划受很多因素影响,并不一定准确。我的建议是看前面几列主要的信息即可,后半截的可以忽略

一些执行计划的基本知识

此部分用于介绍上述得执行计划中各列的信息
在 Oracle 的执行计划中,各列的含义如下:

  • Id操作的唯一标识符,用于标识执行计划中的每个操作,带*表示关键操作。
  • Operation:执行的具体操作类型。例如,"TABLE ACCESS"表示对表进行访问,"INDEX SCAN"表示对索引进行扫描,"SORT"表示排序操作,等等。
  • Name与操作相关的对象的名称。例如,表名、索引名或者视图名。
  • Rows:操作返回的行数估计
  • Bytes:操作返回的字节数估计
  • TempSpc:操作使用的临时表空间大小(如果需要)。
  • Cost (%CPU):操作的成本估计,通常用于衡量操作执行的相对代价。较小的成本表示较低的开销。这个值的大小反映了操作执行所需的资源、IO 开销、CPU 消耗等方面的估计。由于成本值是相对的,没有具体的单位,它主要用于比较不同操作的开销,而不是表示实际的执行时间或资源消耗。
  • Time:操作的估计执行时间

Operation中常见的操作:

  • VIEW:表示执行计划中的步骤涉及到对一个或多个视图的访问和处理

  • TABLE ACCESS:表示对表进行访问。这可能包括全表扫描FULL)或根据索引进行访问(BY INDEX ROWID、BY INDEX ROWID BATCHED等)。

  • INDEX SCAN:表示对索引进行扫描,通常是通过索引键值的顺序访问索引中的数据。

  • INDEX RANGE SCAN:表示通过索引范围扫描,根据索引的范围条件来选择索引键值。

  • NESTED LOOPS:表示嵌套循环连接,通常用于执行连接操作,其中对于外部输入的每一行,内部表被扫描一次。

  • HASH JOIN:表示哈希连接,用于执行连接操作。它使用哈希算法将两个输入数据集的行组合在一起。

  • SORT:表示排序操作,用于对结果进行排序。可能是排序输入数据或为了执行连接或分组操作而进行的排序。

  • AGGREGATE:表示聚合操作,用于计算聚合函数(如SUM、COUNT、AVG)的结果。

  • FILTER:表示过滤操作,用于根据条件筛选行。

  • UNION-ALL:表示合并多个查询结果的操作,包括所有行,不去重。

一些单位的说明:

  • M” 表示 “百万”。当在 “Rows” 列中看到以 “M” 结尾的值时,它表示行数的估计是以百万为单位的。例如,如果 “Rows” 列显示 “10M”,表示行数估计为 10 百万行。

  • K” 表示 “”。当在 “Cost” 列中看到以 “K” 结尾的值时,它表示成本的估计是以千为单位的。例如,如果 “Cost” 列显示 “100k”,表示成本估计为 100 千,也就是10万。

优化

我们按照上面的思路来,一条一条来优化

先执行where条件过滤不需要的行,再进行左连接

这个简单,修改SQL语句,调整执行顺序,先过滤再左连接

   SELECT * FROM (
		SELECT TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID FROM (
			select * from  (
				select * from student where 
					studentId = ,
					name like ,
					idCard = ,
					phone =  ,
					addmissonDate >= ,
					addmissionDate <= 
				order by s.admissionDate desc
			) s left  join campus c on s.campusId = c.campusId
      ) TMP_PAGE
  ) WHERE PAGEHELPER_ROW_ID <= 10 AND PAGEHELPER_ROW_ID > 0

提示排序走索引

这里的话可以设置默认值为合适的值即可

   SELECT * FROM (
		SELECT TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID FROM (
			select * from  (
				select * from student where 
					#为默认查询设置默认查询入学时间为7个月前  
					#保证查询会有数据  用户自定义查询可以自己选择时间
					addmissonDate >= (sysdate - 222),
				order by s.admissionDate desc
			) s left  join campus c on s.campusId = c.campusId
      ) TMP_PAGE
  ) WHERE PAGEHELPER_ROW_ID <= 10 AND PAGEHELPER_ROW_ID > 0

创建入学日期的索引student_index_amDate

 create index index_student_amDate on student (addmissionDate)
  tablespace student
  pctfree 5
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );

通过上述操作,通过索引去查找并排序,就能很快地得到结果

为常用查询条件创建索引

其实这条应该放在调整查询条件顺序后面,因为只有确定了查询顺序才好设计索引。但是之前那么写了,我又懒得改,大家这里注意下即可。

可以通过翻看日志记录信息或者别的第三方工具得到常用的查询语句统计结果

比如常用的查询中有一个查询只有名字的模糊查询,那么可以为其单独创建一个全文索引

CREATE INDEX index_student_name ON student(name)
  INDEXTYPE IS CTXSYS.CONTEXT;

还可以是常用学号来查数据,那么就可以再建一个学号的索引:

 create index index_student_sId on student (studentId)
  tablespace student
  pctfree 5
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );

常用电话的话,创建电话索引:

 create index index_student_phone on student (phone)
  tablespace student
  pctfree 5
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );

又或者他们常用学号和入学日期来查数据,那么还可以在创建一个索引

 create index index_student_sIdAndAmDate on student (studentId , addmissionDate)
  tablespace student
  pctfree 5
  initrans 2
  maxtrans 255
  storage
  (
    initial 64K
    next 1M
    minextents 1
    maxextents unlimited
  );

一定要记住,索引存在的目的就是加快查询速度为一个查询创建一个或者多个索引是很常见的事,索引占的大小一般是表的几倍大小,千万不要舍不得创建索引。

调整查询条件的顺序

最后将模糊查询调整到最后,让查询大都多能走索引即可。这里我只调整了name的位置,其他的条件并未调整,具体的查询条件顺序还是要根据自己的任务需求来定

   SELECT * FROM (
		SELECT TMP_PAGE.*, ROWNUM PAGEHELPER_ROW_ID FROM (
			select * from  (
				select * from student where 
					studentId = ,
					idCard = ,
					phone =  ,
					addmissonDate >= ,
					addmissionDate <= ,
					name like 
				order by s.admissionDate desc
			) s left  join campus c on s.campusId = c.campusId
      ) TMP_PAGE
  ) WHERE PAGEHELPER_ROW_ID <= 10 AND PAGEHELPER_ROW_ID > 0

至此该功能调优算是告一段落了,查询时间从5-6秒降到了0.3-0.6秒左右

在这里插入图片描述
能不能再优化?

答案是可以,那样的话就不走框架的分页,将分页逻辑自己丢到内层的student表那里,这样的话内部查询,student表返回的数据会更少,执行速度也会更快。但是返回数据给前端的页数据信息,比如总共多少数据,总共有多少页什么的信息,就不是框架处理了,而是我自己处理,又不加钱,就这样了。

下班!!!

在这里插入图片描述

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

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

相关文章

【Uniapp】小程序携带Token请求接口+无感知登录方案2.0

本次改进原文《【Uniapp】小程序携带Token请求接口无感知登录方案》&#xff0c;在实际使用过程中我发现以下bug&#xff1a; 若token恰好在用户访问接口时到期&#xff0c;就会直接查询为空&#xff0c;不反映token过期问题&#xff08;例如&#xff1a;弹窗显示订单查询记录…

使用python脚本将视频素材转化为图片数据集并打标

1.前言 在深度学习的训练过程中&#xff0c;第一步要做的制作数据集。本文所介绍的内容就是在面临视频素材时&#xff0c;如何将这个视频素材转换成图片&#xff0c;并将生成的图片给打上标签。 2.视频转图片的代码展示 # codingutf-8 import cv2 import osroot "F:\H…

MySQL数据库及基础操作

MySQL数据库 一、数据库的基本概念二、数据库发展史1、第一代数据库2、第二代数据库3、第三代数据库 三、当今主流数据库介绍四、关系型数据库1、结构2、理解关系数据库 五、非关系数据库六、数据库基本操作1、常用的数据类型2、查看数据库结构3、SQL语句3.1 DDL3.2 DML3.3 DQL…

pycharm 命令行创建版本库

pycharm中已建好的项目如何上传自已的git服务器中 通过命令行创建一个新的版本库 touch README.mdgit initgit add README.mdgit commit -m "first commit"git remote add origin ssh://XXXXip:29418/newbb.gitgit push -u origin master 通过命令行推送一个已存在的…

数据挖掘课件01-07

1、数挖的定义、产生原因&#xff0c;解决问题 数据挖掘就是寻找数据中隐含的知识并用于产生商业价值。 数据挖掘产生动因&#xff1a;海量数据、维度众多、问题复杂 解决问题&#xff1a;分类问题&#xff0c;聚类问题、回归问题、关联问题、 分类算法&#xff1a;C4.5&…

微信如何设置快速回复?

目前微信线上沟通已经成为绝大多数人士的首选&#xff0c;尤其不管是企业还是个人&#xff0c;都会利用微信打造私域。而做微信沟通的话&#xff0c;大家最为关心的就是怎么做到最快捷的回复对方。 可以看看这个功能&#xff0c;提前设置好常见问题的回复话术&#xff0c;可个…

JS + 浮动 + 递归实现图片瀑布流懒加载

思路 页面 pege 分成左浮动的数列 lineBox&#xff0c;每列中图片 sinImg 依次向下摆放每加载一张图片时&#xff0c;判断页面中哪列的高度最小&#xff0c;将当前图片放到最小的那列尾部监听当前图片 onload 事件&#xff0c;当前图片加载完成后&#xff0c;再加载下一张图片…

【python】前方弹幕高能:教你一键实现自动化指定直播间发送弹幕,为你喜欢的女主播疯狂打call叭~

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 不知道你们平时看不看直播&#xff0c;每次看到界面中的滚动弹幕&#xff0c;还挺有意思。 无中生事—— 前几天看到喜欢的主播打比赛呢~ 精彩时刻恨不得双手打字打call的飞快&#xff0c;那激动的无语轮次了都&#…

Android生态下的Kotlin有哪些更新#GoogleIO 2023

Android生态下Kotlin有哪些更新#GoogleIO 2023 自Android官方宣布Kotlin作为Android开发的第一语言&#xff0c;过去将近6年时间。下面是GoogleIO 2023宣布的Android生态的4项重要更新。 Kotlin编译器2.0版本 如果你是一位Kotlin开发者&#xff0c;你可能会对听到这个消息感…

PMP®增持CSPM-2等级证书,免重新考试与学习,申请简单!

好消息&#xff01;好消息&#xff01;好消息&#xff01; 2023年起&#xff0c;持有PMP证书的朋友可以直接增持一个同等级证书CSPM-2&#xff0c;不用重新考试&#xff0c;不用重新学习&#xff0c;原PMP证书不影响正常使用&#xff0c;相当于多了一个国标项目管理领域的证书…

前端和后端分别是什么?

从技术工具来看&#xff1a; 前端&#xff1a;常见的 html5、JavaScript、jQuery... 后端&#xff1a;spring、tomcet、JVM&#xff0c;MySQL... 毕竟&#xff0c;如果这个问题问一个老后端&#xff0c;他掰掰手指可以给你罗列出一堆的名词来&#xff0c;比如设计模式、数据库…

Golang gin middleware的编写与使用 context.Next函数

中间件 在web应用服务中&#xff0c;完整的一个业务处理在技术上包含客户端操作、服务器端处理、返回处理结果给客户端三个步骤。 在实际的业务开发和处理中&#xff0c;会有更负责的业务和需求场景。一个完整的系统可能要包含鉴权认证、权限管理、安全检查、日志记录等多维度…

【软件分析/静态分析】chapter3 课程03/04 数据流分析的应用(Data Flow Analysis)

&#x1f517; 课程链接&#xff1a;李樾老师和谭天老师的&#xff1a;南京大学《软件分析》课程03&#xff08;Data Flow Analysis I&#xff09;_哔哩哔哩_bilibili 南京大学《软件分析》课程04&#xff08;Data Flow Analysis II&#xff09;_哔哩哔哩_bilibili 这篇文章总结…

识别一切模型RAM(Recognize Anything Model)及其前身 Tag2Text 论文解读

img 总览 大家好&#xff0c;我是卷了又没卷&#xff0c;薛定谔的卷的AI算法工程师「陈城南」~ 担任某大厂的算法工程师&#xff0c;带来最新的前沿AI知识和工具&#xff0c;欢迎大家交流~ 继MetaAI 的 SAM后&#xff0c;OPPO 研究院发布识别一切模型&#xff08;Recognize Any…

MySQL如何保证数据的可靠性(保证数据不丢失)

1. 结论&#xff1a; 只要redo log 和 binlog 保证持久化到磁盘&#xff0c;就能确保MySQL异常重启后&#xff0c;数据可以恢复。 2. 机制 WAL机制&#xff0c;&#xff08;Write Ahead Log&#xff09;&#xff1a; 事务先写入日志&#xff0c;后持久化到磁盘。 3. binlog…

华为OD机试真题 JavaScript 实现【非严格递增连续数字序列】【2022Q4 100分】

一、题目描述 输入一个字符串仅包含大小写字母和数字&#xff0c;求字符串中包含的最长的非严格递增连续数字序列的长度&#xff0c;比如122889属于非严格递增连续数字序列。 二、输入描述 输入一个字符串仅包含大小写字母和数字&#xff0c;输入的字符串最大不超过255个字符…

合金氢化物动力学与瞬时流量计算

在经典的合金氢化物动力学描述中&#xff0c;有一种是用JMAK方程来描述和拟合合金的吸放氢过程&#xff0c;方程很简洁&#xff1a;&#xff0c;其中是反应程度或者百分比&#xff0c;表示合金氢化物吸氢或者放氢的程度&#xff0c;是该合金吸氢或放氢的一种特征常数&#xff0…

57、基于51单片机智能硬币分拣分类机电子存钱罐报警系统设计(程序+原理图+PCB源文件+Proteus仿真+参考论文+参考PPT+元器件清单等)

摘 要 近年来&#xff0c;随着我国经济的发展和社会的进步&#xff0c;邮政事业得到了空前发展。邮政通信网的技术含量不断增加&#xff0c;技术装备水平也在不断的提高&#xff0c;邮件处理已基本实现机械化&#xff0c;并且朝着自动化的方向迈进。本文着眼于我国当前邮政事…

Unity编辑器扩展-第一集-在菜单栏加入自己的按钮

一、概述 unity自己本身就是一个大的程序&#xff0c;我们看见的所有功能&#xff0c;都是用程序写出来的&#xff0c;但是根据各行各业不同的需求&#xff0c;有些时候我们制作时&#xff0c;想要自己编辑一些原有的功能。 二、本节目标效果展示 1.在菜单栏加入属于自己的一…

【前端基础篇】CSS选择器 和 CSS属性

前言&#xff1a;CSS 简介 CSS 概述 CSS ( Cascading Style Sheet ) 层叠样式表&#xff0c;用来修饰 HTML&#xff0c;使得效果更加多样化CSS 在 HTML4.0 中引入&#xff0c;一般在开发过程中&#xff0c;会使用单独的 CSS 文件进行开发&#xff0c;然后将这个独立 CSS 文件引…