慢SQL优化笔记:从3041ms到10ms的优化之旅

news2024/9/22 2:09:14

1 背景

最近项目上要求开发一个查询接口,刚开发完成后,测试环境进行自测发现查询效率非常慢,通过打log计算得到SQL执行时间3041ms,实在太慢了。下面简单记录下本次慢SQL优化的过程。

2 相关数据

  • 新增客户表(其它无关字段省略)
create table new_customer_info(
	id bigint(32) not null auto_increment comment '唯一id' ,
	ent_name varchar(255) default null comment '企业名称',
	area_name varchar(50) defalut null comment '区域',
	register_date datetime default null comment '注册时间',
	phone_number varchar(50) defalut null comment '手机号',
	del_flag char(1) default '0' comment '删除标志,0:正常,1:删除',
	sms_flag char(1) defalut '0' comment '获取手机号标志,0:已获取,1:未获取',
	will_status char(1) default null comment '合作意向,0:无,1:未联系上,2:有合作意向',
	...
	primary key (id)
);
  • 区域表(其它无关字段省略)
create table area_data(
	area_code varchar(50) default null comment '地区代码',
	area_name varchar(100) default null comment '地区名称',
	parent_name varchar(100) default null comment '上级地区名称',
	...
	key idx_area_code (area_code)
);
  • 字典数据表(其它无关字段省略)
create table sys_dict_data(
	dict_label varchar(100) default '' comment '字典标签',
	dict_value varchar(100) default '' comment '字典键值',
	dict_type varchar(100) default '' comment '字典类型',
	...
	key idx_dict_type (dict_type) using btree,
	key idx_dict_value (dict_value) using btree
)
  • 开发的查询接口功能:
    该接口返回new_customer_info表中所有已经电话回访后的企业信息列表。

3 原始SQL

SELECT 
	nci.net_name entName,
	nci.area_name areaName,
	ad.parent_name cityName,
	add.dict_label industry,
	date_format(register_date,'%Y-%m-%d') registerDate,
	nci.phone_number phoneNumber,
	nci.will_status willStatus
FROM new_customer_info nci 
LEFT JOIN area_data ad ON ad.area_code = nci.area_name 
LEFT JOIN sys_dict_data sdd ON (nci.industry = sdd.dict_value AND ad.dict_type ='train_industry')
WHERE nci.del_flag = '0' AND nci.sms_flag = '0';

对应的xml语句:

<select id='getNewCustomerInfo' resultType='com.javacoder.domain.NewCustomerVo'>

	select 
		nci.net_name entName,
		nci.area_name areaName,
		ad.parent_name cityName,
		add.dict_label industry,
		date_format(register_date,'%Y-%m-%d') registerDate,
		nci.phone_number phoneNumber,
		nci.will_status willStatus
	FROM new_customer_info nci 
	LEFT JOIN area_data ad ON ad.area_code = nci.area_name 
	LEFT JOIN sys_dict_data sdd ON (nci.industry = sdd.dict_value AND ad.dict_type ='train_industry')
	WHERE nci.del_flag = '0' AND nci.sms_flag = '0'
	<if test="areaName != null and areaName != ''">
		and nci.area_name like concat('%',concat(#{entName}),'%'))
	</if>
	<if test="entName != null and entName != ''">
		and nci.ent_name like concat('%',concat(#{entName}),'%'))
	</if>
	<if test="willStatus != null and willStatus != ''">
		and nci.will_status like concat('%',concat(#{willStatus}),'%'))
	</if>
</select>

4 优化思路

  1. 分析SQL执行计划: 使用EXPLAIN命令查看SQL的执行计划,分析查询过程中涉及的表、索引、连接方式等信息,找出性能瓶颈

  2. 优化索引

    检查相关表的索引情况,确保new_customer_info表的del_flag、sms_flag、area_name、industry字段,area_data表的area_code、dict_type字段,sys_dict_data表的dict_value字段都建立了合适的索引。

    考虑创建组合索引,例如在new_customer_info表上创建(del_flag, sms_flag, area_name, industry)组合索引,以提高查询效率

  3. 优化连接方式

    将LEFT JOIN改为INNER JOIN,如果area_data和sys_dict_data表中没有匹配的数据,则不需要返回new_customer_info表中的数据。

    调整连接顺序,将过滤条件较多的表放在前面连接,以减少中间结果集的大小

  4. 优化查询条件

    将WHERE条件中的常量条件提前,例如将ad.dict_type ='train_industry’放在LEFT JOIN条件中。

    使用EXISTS或IN子查询代替JOIN操作,如果子查询结果集较小

  5. 优化代码逻辑
    通过优化代码执行逻辑,来提高SQL执行效率。

5 优化过程

5.1 分析SQL执行计划

explain SELECT 
	nci.net_name entName,
	nci.area_name areaName,
	ad.parent_name cityName,
	add.dict_label industry,
	date_format(register_date,'%Y-%m-%d') registerDate,
	nci.phone_number phoneNumber,
	nci.will_status willStatus
FROM new_customer_info nci 
LEFT JOIN area_data ad ON ad.area_code = nci.area_name 
LEFT JOIN sys_dict_data sdd ON (nci.industry = sdd.dict_value AND ad.dict_type ='train_industry')
WHERE nci.del_flag = '0' AND nci.sms_flag = '0';

explain 输出结果中各个字段的含义:

  • id 列:查询的标识符。

  • select_type 列:查询的类型。常见的类型有:

    SIMPLE:简单查询,不包含子查询或者 UNION 查询。
    PRIMARY:查询中如果包含子查询,则最外层查询被标记为 PRIMARY。
    SUBQUERY:子查询。
    DERIVED:派生表的 SELECT,FROM 子句的子查询。

  • table 列:查的哪个表。

  • type 列:表示 MySQL 在表中找到所需行的方式,性能从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL。

    system,表只有一行,一般是系统表,往往不需要进行磁盘 IO,速度非常快
    const、eq_ref、ref:这些类型表示 MySQL 可以使用索引来查找单个行,其中 const 是最优的,表示查询最多返回一行。
    range:只检索给定范围的行,使用索引来检索。在where语句中使用 bettween…and、<、>、<=、in 等条件查询 type 都是 range。
    index:遍历索引树读取。
    ALL:全表扫描,效率最低。

  • possible_keys 列:可能会用到的索引,但并不一定实际被使用。

  • key 列:实际使用的索引。如果为 NULL,则没有使用索引。

  • key_len 列:MySQL 决定使用的索引长度(以字节为单位)。当表有多个索引可用时,key_len 字段可以帮助识别哪个索引最有效。通常情况下,更短的 key_len 意味着数据库在比较键值时需要处理更少的数据。

  • ref 列:用于与索引列比较的值来源。

    const:表示常量,这个值是在查询中被固定的。例如在 WHERE column = 'value’中。
    一个或多个列的名称,通常在 JOIN 操作中,表示 JOIN 条件依赖的字段。
    NULL,表示没有使用索引,或者查询使用的是全表扫描。

  • rows 列:估算查到结果集需要扫描的数据行数,原则上 rows 越少越好。

  • Extra 列:附加信息。

    Using index:表示只利用了索引。
    Using where:表示使用了 WHERE 过滤。
    Using temporary :表示使用了临时表来存储中间结果。

5.2 优化索引

  • 在new_customer_info表上创建组合索引
CREATE INDEX idx_nci_del_sms_area_industry ON new_customer_info (del_flag, sms_flag, area_name, industry);
  • 在area_data表上创建组合索引
CREATE INDEX idx_ad_area_code_dict_type ON area_data (area_code, dict_type);
  • 在在sys_dict_data表上创建组合索引
CREATE INDEX idx_sdd_dict_value_dict_type ON sys_dict_data (dict_value,dict_type);
  • 优化结果:SQL执行耗时从3041ms降低到2780ms,效果并不明显,需继续优化。

5.3 优化连接方式

  • 将LEFT JOIN改为INNER JOIN
SELECT 
	nci.net_name entName,
	nci.area_name areaName,
	ad.parent_name cityName,
	add.dict_label industry,
	date_format(register_date,'%Y-%m-%d') registerDate,
	nci.phone_number phoneNumber,
	nci.will_status willStatus
FROM new_customer_info nci 
inner JOIN area_data ad ON ad.area_code = nci.area_name 
inner JOIN sys_dict_data sdd ON (nci.industry = sdd.dict_value AND ad.dict_type ='train_industry')
WHERE nci.del_flag = '0' AND nci.sms_flag = '0';
  • 优化结果:不可行,实际可能会存在new_customer_info表中area_name与area_data表中的area_code对应不上的情况,如果用INNER JOIN,会导致查询出的数据不完整。

5.4 优化代码逻辑

  • 优化前代码逻辑:先查询出全部的new_customer_info表数据,然后用Stream流过滤掉willStatus为null的数据,最后返回。在这个过程中,由于new_costomer_info表的数据量很大,area_data表和sys_dict_data表的数据量较小,通过LEFT JOIN连接三表查询效率很慢。
  • 优化后代码逻辑:先将过滤willStatus为空的逻辑放到SQL语句中,减少连接的数据量。最后无需再通过Stream流过滤数据,直接返回查询结果即可。
  • 优化后的SQL
SELECT 
	nci.net_name entName,
	nci.area_name areaName,
	ad.parent_name cityName,
	add.dict_label industry,
	date_format(register_date,'%Y-%m-%d') registerDate,
	nci.phone_number phoneNumber,
	nci.will_status willStatus
FROM new_customer_info nci 
inner JOIN area_data ad ON ad.area_code = nci.area_name 
inner JOIN sys_dict_data sdd ON (nci.industry = sdd.dict_value AND ad.dict_type ='train_industry')
WHERE nci.del_flag = '0' AND nci.sms_flag = '0' and nci.will_status is not null;
  • 对应的xml语句
<select id='getNewCustomerInfo' resultType='com.javacoder.domain.NewCustomerVo'>

	select 
		nci.net_name entName,
		nci.area_name areaName,
		ad.parent_name cityName,
		add.dict_label industry,
		date_format(register_date,'%Y-%m-%d') registerDate,
		nci.phone_number phoneNumber,
		nci.will_status willStatus
	FROM new_customer_info nci 
	LEFT JOIN area_data ad ON ad.area_code = nci.area_name 
	LEFT JOIN sys_dict_data sdd ON (nci.industry = sdd.dict_value AND ad.dict_type ='train_industry')
	WHERE nci.del_flag = '0' AND nci.sms_flag = '0'
	<if test="areaName != null and areaName != ''">
		and nci.area_name like concat('%',concat(#{entName}),'%'))
	</if>
	<if test="entName != null and entName != ''">
		and nci.ent_name like concat('%',concat(#{entName}),'%'))
	</if>
	<if test="willStatus != null and willStatus != ''">
		and nci.will_status like concat('%',concat(#{willStatus}),'%'))
	</if>
	and nci.will_status is not null
</select>
  • 优化结果:SQL执行耗时从2780ms降低到300ms,效果明显,但还有优化空间。

5.5 最后的优化

  • 再次使用explain分析SQL执行计划,发现SQL执行过程中new_customer_info表输出结果的type列返回的是ALL,表示全表扫描。
  • 优化方案:在new_customer_info表上创建索引
create index idx_will_status on new_customer_info (will_status);
  • 优化结果:SQL执行耗时从300ms降低到10ms以内,效果显著,达到了预期目标。

6 总结

SQL优化是一个需要不断尝试和调整的过程,需要结合具体的业务场景和数据库特性进行分析和优化。通过业务逻辑代码优化、合理的索引设计、连接方式优化、查询条件优化等手段,可以有效提升SQL语句的执行效率,提高系统性能。

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

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

相关文章

网络丢包定位记录(三)

网络IP层丢包 接口ip地址配置丢包 1. 本机服务不通&#xff0c;检查lo接口有没有配置地址是127.0.0.1&#xff1b; 2 .本机接收失败&#xff0c; 查看local路由表&#xff1a;ip r show table local|grep 子机ip地址&#xff1b;这种丢包一般会出现在多IP场景&#xff0c;子…

robomimic应用教程(一)——模型训练

Robomimic使用集中式配置系统来指定所有级别的(超)参数 本文介绍了配置&#xff08;推荐&#xff09;和启动训练运行的两种方法 目录 一、使用config json&#xff08;推荐&#xff09; 二、在代码中构造一个配置对象 三、查看运行结果 1. 实验结果会存在一个固定文件夹中…

S-Clustr-Simple 飞机大战:骇入现实的建筑灯光游戏

项目地址:https://github.com/MartinxMax/S-Clustr/releases Video https://www.youtube.com/watch?vr3JIZY1olro 飞机大战 这是一个影子集群的游戏插件&#xff0c;可以将游戏画面映射到现实的设备&#xff0c;允许恶意控制来完成游戏。亦或者设备部署在某建筑物中,来控制…

超实用的 Typora 插件

&#x1f33c;&#x1f4da;Typora 是一款高效、易用且跨平台的 Markdown 编辑器和阅读器&#xff0c;其具有小巧、快速、实时预览等特点&#xff0c;非常受大家的欢迎。今天给大家推荐一款如虎添翼的 Typora 插件(Typora Plugin)&#xff0c;它可以通过插件增强 Typora 的功能…

C++入门(03)萌新问题多(二)

文章目录 1. VS2022 控制台输出中文时&#xff0c;变成了一堆“&#xff1f;”1.1 字体、语言设置1.2 在程序中指定控制台编码1.3 修改注册表&#xff08;只能说试试吧&#xff09; 1. VS2022 控制台输出中文时&#xff0c;变成了一堆“&#xff1f;” 问题如下&#xff0c;Vi…

解决uniapp开发的app,手机预览,上下滑动页面,页面出现拉伸,抖动的效果问题,

在pages.json文件里“globalStyle”下面的"app-plus"里加入"bounce": "none"即可 "app-plus": { "bounce": "none", //关闭窗口回弹效果 }

[Leetcode] 227.基本计算器

标题&#xff1a;[Leetcode] 227.基本计算器 个人主页&#xff1a;水墨不写bug &#xff08;图片来源于网络&#xff09; // _ooOoo_ // // o8888888o // // …

linux 获取指定端口的PID netsat awk

使用netstat -ntpl 获取指定端口的PID #获取端口19000对应的PID netstat -ntpl | grep 19000 | awk {print $NF} | awk -F/ {print $1}

vcs/verdi常用命令(持续更新)

1. 操作rtl 1.1 加载rtl命令 verdi -dbdir simv.daidir的目录 1.2 显示某时刻rtl的值 首先鼠标左键在波形上选中某个特定时刻&#xff0c;然后鼠标选中rtl代码文件&#xff0c;按x就会显示&#xff0c;再按x就会退出显示。 1.3 查找字符串 按/ 1.4 vcs将rtl的信号加载到…

mockito+junit搞定单元测试(2h)

一&#xff0c;简介 1.1 单元测试的特点 配合断言使用(杜绝 System.out )可重复执行不依赖环境不会对数据产生影响spring 的上下文环境不是必须的一般都需要配合 mock 类框架来实现 1.2 mock 类框架使用场景 要进行测试的方法存在外部依赖(如 db, redis, 第三方接口调用等)…

HashMap扩容时机是插入前还是插入后?

结论 不管是HashMap还是ConcurrentHashMap都是插入后。 过程为&#xff1a; 先计算哈希值。对应的哈希槽插入数据&#xff0c;决定是红黑树还是链表插入完毕才计算是否需要扩容&#xff0c;假如需要则扩容 源码 源码如下&#xff1a; 其中addCount方法里面写入扩容。

如何设置 Django 错误邮件通知 ?

Django 是一个强大的 web 框架&#xff0c;非常适合那些想要完美快速完成任务的人。它有许多内置的工具和特性&#xff0c;一个有用的特性是 Django 可以在出现错误时发送电子邮件提醒。这对开发人员和管理员非常有用&#xff0c;因为如果出现问题&#xff0c;他们会立即得到通…

STM32F407单片机编程入门(十二) FreeRTOS实时操作系统详解及实战含源码

文章目录 一.概要二.什么是实时操作系统三.FreeRTOS的特性四.FreeRTOS的任务详解1.任务函数定义2.任务的创建3.任务的调度原理 五.CubeMX配置一个FreeRTOS例程1.硬件准备2.创建工程3.调试FreeRTOS任务调度 六.CubeMX工程源代码下载七.小结 一.概要 FreeRTOS是一个迷你的实时操…

智慧校园建设解决方案建设系统简介

一、建设背景 1.1 政策背景 1.2 班牌的演变 1.3 建设愿景 二、 智慧班牌简介 三、智慧班牌系统 3.1 系统概述 3.2 软件平台功能交互简介 3.2.1 智慧班牌与管理平台间的功能关联 3.2.2 手机客户端&#xff08;管理员、教师、家长端&#xff09; 3.2.3 手机客户端&#x…

数据采集与预处理,前后端结合案例(有代码),Python连接MySQL,对MySQL的增删改查

Python对MySQL的增删改查 通过Python连接MySQL """连接MySQL数据库&#xff0c;并进行增删改查&#xff0c;同时查询了MySQL版本号&#xff0c;并做了动态注册的账号&#xff0c;实现过程&#xff1a;先向userinfo当中添加account、password新字段&#xff0c…

数据结构:内部排序

文章目录 1. 前言1.1 什么是排序&#xff1f;1.2 排序的稳定性1.3 排序的分类和比较 2. 常见的排序算法3. 实现常见的排序算法3.1 直接插入排序3.2 希尔排序3.3 直接选择排序3.4 堆排序3.5 冒泡排序3.6 快速排序3.6.1 hoare思想3.6.2 挖坑法3.6.3 lomuto前后指针法3.6.4 非递归…

软考(中级-软件设计师)计算机系统篇(0921)

I 计算机系统知识 一、考纲要求 数值及其转换 二进制、十进制和十六进制等常用数制及其相互转换 计算机内数据的表示 数值的表示&#xff08;原码、反码、补码、移码表示&#xff0c;整数和实数的机内表示&#xff0c;精度和溢出&#xff09;非数值表示&#xff08;字符和汉字…

AI直播新浪潮:无人视频自动直播,出圈再造辉煌,创业者首选!

AI直播新浪潮:无人视频自动直播&#xff0c;出圈再造辉煌&#xff0c;创业者首选&#xff01; 在数字化浪潮的汹涌澎湃中&#xff0c;AI技术正以前所未有的速度重塑着各行各业的边界&#xff0c;而直播行业作为数字内容消费的前沿阵地&#xff0c;正迎来一场由AI驱动的深刻变革…

MQ(RabbitMQ)笔记

初识MQ 同步调用优缺点 异步调用优缺点 总结&#xff1a; 时效性要求高&#xff0c;需要立刻得到结果进行处理--->同步调用 对调用结果不关心&#xff0c;对性能要求高&#xff0c;响应时间短--->异步调用

2024年华为杯-研赛F题论文问题一二讲解+代码分享

X射线脉冲星光子到达时间建模 摘要 脉冲星是一类高速自转的中子星&#xff0c;其自转形成规律性脉冲信号&#xff0c;类似于“宇宙中的灯塔”&#xff0c;因此被认为是极为精确的时钟。X射线脉冲星导航利用脉冲星信号为航天器提供时间和空间参考。通过比较脉冲信号到达航天器…