SQL优化案例教程0基础(小白必看)

news2024/11/13 12:04:47

前提准备:本案例准备了100W的数据进行SQL性能测试,数据库采用的是MySQL,

总共介绍了常见的14种SQL优化方式,每一种优化方式都进行了实打实的测试,

         逐行讲解,通俗易懂!

一、前提准备

提前准备一张学生表数据和一张特殊学生表数据,用于后面的测试用。

1.1  创建表结构

创建一个学生表:

CREATE TABLE student (
  id int(11) unsigned NOT NULL AUTO_INCREMENT,
  name varchar(50) DEFAULT NULL,
  age tinyint(4) DEFAULT NULL,
  id_card varchar(20) DEFAULT NULL,
  sex tinyint(1) DEFAULT '0', 
  address varchar(100) DEFAULT NULL,
  phone varchar(20) DEFAULT NULL, 
  create_time timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  remark varchar(200) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

再创建一个特殊学生表:

CREATE TABLE special_student (
  id int(11) unsigned NOT NULL AUTO_INCREMENT,
  stu_id int(11) DEFAULT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

1.2  创建存储过程 

在学生表中插入100w条数据,手动开启和提交事务,每插入1w条记录后,手动COMMIT一次事务,最后再COMMIT一次以提交剩下的记录,这样可以让插入速度更快,因为不需要为每条记录都 COMMIT,从而降低 IO 次数。

CREATE PROCEDURE insert_student_data()
BEGIN
  DECLARE i INT DEFAULT 0; 
  DECLARE done INT DEFAULT 0; 
  DECLARE continue HANDLER FOR NOT FOUND SET done = 1;
  START TRANSACTION;  
   WHILE i < 1000000 DO
     INSERT INTO student(name,age,id_card,sex,address,phone,remark)
     VALUES(CONCAT('姓名_',i), FLOOR(RAND()*100),
         FLOOR(RAND()*10000000000),FLOOR(RAND()*2),
         CONCAT('地址_',i), CONCAT('12937742',i),
         CONCAT('备注_',i));
     SET i = i + 1; 
     IF MOD(i,10000) = 0 THEN 
       COMMIT;
       START TRANSACTION;
     END IF;     
   END WHILE;    
   COMMIT;
END

执行学生表的存储过程:

CALL insert_student_data();

在特殊学生表中随机插入100条学生表中的id:

CREATE PROCEDURE insert_special_student()
BEGIN
  DECLARE i INT DEFAULT 0; 
  WHILE i < 100 DO
    INSERT INTO special_student (stu_id) VALUES (FLOOR(RAND()*1000000));  
    SET i = i + 1;   
  END WHILE;
END

执行特殊学生表的存储过程: 

CALL insert_special_student();

二、SQL优化案例详细介绍

2.1  返回必要的行

如果数量较大,可以使用 LIMIT 子句来限制返回的行数

select id,name from student limit 10

2.2  limit 优化

平日开发工作中,我们对于分页的处理一般是这样的:

SELECT * FROM student LIMIT 900000,10

执行结果如图所示: 

耗时0.56s。当id为自增的情况下可以进行优化,优化的SQL如下: 

SELECT * FROM student WHERE ID >= 900000 LIMIT 10

优化后执行结果如图所示:

耗时0.02s,速度提升很多!

2.3  返回必要的列,避免使用SELECT *

有的时候,我们为了图方便,会直接使用SELECT * 一次性查出表中所有的数据:

SELECT * FROM student

执行结果如图所示:

可以看到,执行时间花了2s左右,耗时很长!

在实际开发中,我们给页面展示的数据可能就只要2-3个字段,如果直接全部查出来了,岂不是白白浪费了字段,同时也损耗了性能,这是因为SELECT * 不会走覆盖索引,会出现大量的回表操作,从而导致SQL性能大幅度降低。

我们上面建立了联合索引,我们就可以只查询索引列,这样会大幅度提升查询效率,优化的SQL如下:

SELECT name,address,phone FROM student

优化后执行结果如图所示:

耗时0.780s,速度提升很多!

2.4  or连接的条件(注意)

当使用OR操作符将多个条件组合在一起时,如果其中一个条件的列没有索引,那么涉及的索引不会被用到。

为了解决这个问题,可以考虑以下方案:

  • 确保所有涉及的条件列都有适当的索引,以提高查询性能。
  • 对于大型表,可以考虑重构查询,将OR操作符拆分成多个独立的查询,并使用UNION或UNION ALL来合并结果。这样可以确保每个子查询都能够使用适当的索引,并避免OR操作符导致的索引失效问题。

2.5  避免使用or条件,使用UNION或UNION ALL替代(有争议)

如果我们要查询指定的性别或者指定的身份证号码的学生,执行SQL如下:

SELECT * FROM student WHERE sex = 0 OR id_card = '7121877527789'

执行结果如图所示:

总共查询了近50w条数据,耗时1.4s左右,我们改用UNION ALL关键字查询:

SELECT * FROM student WHERE sex = 0 
UNION ALL 
SELECT * FROM student WHERE id_card = '7121877527789'

改用后执行结果如图所示:

速度没有提升,反而慢了,故有争议

分析SQL: 

使用EXPLAIN关键字分析一下使用OR关键字的这段SQL:

EXPLAIN SELECT * FROM student WHERE SEX = 0 OR id_card = '7121877527789'

执行结果如图所示: 

很明显,虽然可能会用到建立id_card的索引,正因为sex这个字段没有建立索引,还是走了一次全表扫描。

使用EXPLAIN关键字执行这段SQL:

EXPLAIN
SELECT * FROM student WHERE sex = 0 
UNION ALL 
SELECT * FROM student WHERE id_card = '7121877527789'

执行结果如图所示:

很明显条件是sex的走了全表,但是id_card走了索引,所以依旧还是走了一次全表扫描,所以网上说的关于UNION ALL代替OR的,我这边实测感觉还是存在争议的!

2.6  非必要情况下,慎用UNION关键字,使用UNION ALL替代

例如我们根据性别去查询所有学生的信息,虽然这种操作多此一举,直接SELECT *就好了,为了演示这2个关键字的详细区别,使用UNION关键字执行的SQL如下:

SELECT * FROM student WHERE sex = 0
UNION 
SELECT * FROM student WHERE sex = 1

执行结果如图所示: 

查了100w条足足整整等了32s左右,这个速度要是放到系统上,查个数据等到娃娃菜都凉了!

这是因为在使用UNION执行完SQL后,会帮我们获取所有数据并去掉重复的数据,性能的损耗就在这里,而UNION ALL和UNION相反,帮我们获取所有数据但会保留重复的数据。

我们改用UNION ALL关键字,优化的SQL如下:

SELECT * FROM student WHERE sex = 0
UNION ALL
SELECT * FROM student WHERE sex = 1

替换后执行结果如图所示:

同样查询100w条数据,这边执行速度大大提高了,只用到了3s左右! 

速度提升很多!

2.7  LIKE语句优化

平时我们日常开发用到的LIKE关键字进行模糊匹配会非常多,但是有的情况会使索引失效,导致查询效率变慢,例如:

只要身份证字段包含50就查出来,执行SQL如下:

SELECT * FROM student WHERE id_card like '%50%'

执行结果如图所示:

用了0.8s左右。

只要身份证号码以50结尾就查出来,执行SQL如下:

SELECT * FROM student WHERE id_card like '%50'

执行结果如图所示:

用了0.4s左右。

只要身份证号码以50开头的就查出来,执行SQL如下:

SELECT * FROM student WHERE id_card like '50%'

执行结果如图所示:

这次执行非常快,0.08s左右。

分析SQL:

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card like '%50%'

执行结果如图所示:

很明显走了全表扫描!

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card like '%50'

执行结果如图所示:

依旧走了全表扫描!

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card like '50%'

执行结果如图所示:

这次便走了索引!速度快很多

2.8  尽量避免使用!=,导致索引失效

尽量避免使用!=或<>操作符,下面直接分析SQL:

SQL分析:

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card != '5031520645'

执行结果如图所示:

虽然我们给了id_card字段建立了索引,但还是走了全表扫描!

2.9  尽量避免使用NULL值,IS NOT NULL会导致索引失效,IS NULL则不会

为了确保没有NULL值,我们可以设定一个默认值,下面直接分析SQL:

SQL分析:

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card IS NOT NULL

 执行结果如图所示:

依旧还是走了全表扫描。

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card IS NULL

执行结果如图所示:

这样是走索引的!

2.10  使用小表驱动大表 ,避免大表驱动小表

言简意赅,意思就是让小表查出来的数据去再查询大表当中的数据。比如我们想查询学生表当中特殊学生的信息,我们就可以使用以special_student这个小表去驱动student这个大表,SQL如下:

SELECT * FROM student WHERE id 
IN (SELECT stu_id FROM special_student)

执行结果如图所示:

只用了0.02s,速度很可观!因为IN关键字中的子查询语句,子查询语句的数据量很少,所以查询速度会很快!

2.11  避免字符串不加引号,导致索引失效

如果在查询条件或创建索引时字符串没有加上引号,会导致索引失效。

查询指定的身份证号码的学生,如果我们平时疏忽了给身份证号码加上单引号,执行SQL如下:

SELECT * FROM student WHERE id_card = 5040198345

执行结果如图所示:

耗时0.4s左右。

给身份证号码加上单引号,优化的SQL如下:

SELECT * FROM student WHERE id_card = '5040198345'

执行结果如图所示:

耗时0.02s左右,这次明显快多了!

分析SQL:

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card = 5040198345

执行结果如图所示:

可能用到了id_card的索引,但是还是走了全表扫描!

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE id_card = '5040198345'

执行结果如图所示: 

加上引号,走了索引,速度快了很多!

2.12  避免对索引列上字段操作,导致索引失效

为了避免索引失效的问题,应该尽量避免在查询条件或者索引创建时对索引列进行运算。如果确实需要使用运算,可以考虑以下解决方案:

  • 对索引列进行逆转运算:如果运算是可逆的,可以通过将运算应用到查询参数上,而不是索引列上来维持索引的有效性。
  • 使用函数索引:某些数据库管理系统提供了函数索引的功能,可以根据特定的函数操作创建索引,以满足特定的查询需求。

2.13  遵循最左匹配原则(重要)

上面我们按照name,address和phone这个顺序建立了复合索引,相当于建立了(name),(name、address)和(name、address、phone)三个索引,如果我们查询的where条件违背了建立的顺序,则复合索引就失效了,下面直接进行SQL分析:

分析SQL:

使用EXPLAIN关键字执行这段SQL:

EXPLAIN SELECT * FROM student WHERE name = '姓名_4' and phone = '7121877527' and address = '地址_4'

执行结果如图:

为什么明明违背了最左匹配原则,依旧还是走了复合索引呢?可能是如下原因:

1、通过索引过滤性能足够好,所以还是选择利用索引。

2、联合索引中前几个字段过滤效果较好,所以仍然选择利用索引。

可能的执行计划大概是:

1、优先通过phone字段过滤,将要扫描的记录减少一部分。
2、然后通过address字段继续过滤,再减少一部分记录。
3、最后通过name字段过滤,已经剩下很少的记录需要扫描。
4、尽管违反了最左匹配,解释器可能认为仍然利用索引效率比较高。

所以总的来说,就是解释器会根据实际情况进行权衡,即使是违反最左匹配原则,也可能会选择利用索引。但这并不是一个良好的查询优化,最好还是严格遵守最左匹配原则。

以下是严格遵守最左匹配原则的SQL:

SELECT * FROM student WHERE name = '姓名_4' 
SELECT * FROM student WHERE name = '姓名_4' and address = '地址_4' 
SELECT * FROM student WHERE name = '姓名_4' and address = '地址_4' and phone = '7121877527' 

2.14  提升GROUP BY的效率

我们平日写SQL需要多多少少会使用GROUP BY关键字,它主要的功能是去重和分组。 通常它会跟HAVING一起配合使用,表示分组后再根据一定的条件过滤数据,常规执行的SQL如下:

SELECT age,COUNT(1) FROM student GROUP BY age HAVING age > 18

执行结果如图所示:

耗时总计0.53s左右,不过还可以进行优化,我们可以在分组之前缩小筛选的范围,然后再进行分组,优化的SQL如下:

SELECT age,COUNT(1) FROM student where age > 18 GROUP BY age 

执行结果如图所示:

耗时0.51s左右,虽然不明显,也是一种不错的思路。

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

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

相关文章

Sqoop实操案例-互联网招聘数据迁移

&#x1f947;&#x1f947;【大数据学习记录篇】-持续更新中~&#x1f947;&#x1f947; 个人主页&#xff1a;beixi 本文章收录于专栏&#xff08;点击传送&#xff09;&#xff1a;【大数据学习】 &#x1f493;&#x1f493;持续更新中&#xff0c;感谢各位前辈朋友们支持…

mybatis plus 3.4以上分页无效问题,limit一直加不上,MybatisPlusInterceptor无效

解决方案 1、已注册 Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor new MybatisPlusInterceptor();PaginationInnerInterceptor paginationInnerInterceptor new PaginationInnerInterceptor(DbType.MYSQL);paginationIn…

idea全局搜索失效,Ctrl+shift+F快捷键不起作用

方法1&#xff1a;是否与搜狗等输入法软件存在快捷键冲突&#xff0c;当然也可能是你新下载的什么软件导致的快捷键冲突导致IDEA全局搜索失效。比如下图&#xff1a; 可以改掉输入法的快捷键或者直接关闭输入法的快捷键&#xff0c;这样idea的全局搜索功能就恢复了。 方法2&…

一文掌握光模块知识,成为网络工程师的必备技能

在这个信息爆炸的时代&#xff0c;数据传输已经成为我们生活中不可或缺的一部分。而在众多的数据传输方式中&#xff0c;光纤通信以其高速、高带宽、低损耗的特点&#xff0c;成为了现代通信的主流。而在这个光纤通信的背后&#xff0c;有一个神奇的器件在默默地发挥着作用&…

qt creater11 翻译国际化教程教程:

先出效果图。 闲聊几句&#xff1a;qt这个翻译很方便&#xff0c;能直接导出项目里所有文字。 具体步骤如下&#xff1a; 在Qt中&#xff0c;我们可以使用QTranslator类来实现多语言切换。以下是一般步骤&#xff1a; 1. 在你的源代码中&#xff0c;所有需要翻译的字符串都…

每日一题——下一个排列

下一个排列 题目链接 读懂题目 要理解题目的意思&#xff0c;主要是要读懂这一句&#xff1a;整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。 我们来逐词分析&#xff1a; 其整数&#xff0c;即我们要将这个数组的数字构成一个十进制整数&#xff0c;例如数组…

Android之自定义时间选择弹框

文章目录 前言一、效果图二、实现步骤1.自定义Dialog2.xml布局3.背景白色转角drawable4.取消按钮背景drawable5.确定按钮背景drawable6.NumberPicker样式和弹框样式7.弹框动画8.Activity使用 总结 前言 随着产品人员不断变态下&#xff0c;总是会要求我们的界面高大上&#xf…

JVM之堆和方法区

目录 1.堆 1.1 堆的结构 1.1.1 新生代&#xff08;Young Generation&#xff09; 1.1.2 年老代&#xff08;Old Generation&#xff09; 1.1.3 永久代/元空间&#xff08;Permanent Generation/Metaspace&#xff09; 1.2 堆的内存溢出 1.3 堆内存诊断 1.3.1 jmap 1.3.2…

五大优势,让你坚定选择低代码开发平台

随着数字化时代的到来&#xff0c;企业纷纷寻求新的方式来提高业务效率、降低成本&#xff0c;并满足不断变化的客户需求。在这个过程中&#xff0c;低代码平台逐渐成为一种备受瞩目的技术&#xff0c;因为其具有五大特殊优势&#xff0c;能够极大地提高企业数字化转型的效率。…

如何用代理IP解决Tik Tok直播时卡顿的问题?

Tik Tok如今已经成为了一种非常流行的社交互动模式&#xff0c;但是在直播过程中突然的卡顿往往会让人抓狂&#xff0c;流失很多客户。除非您的内容足够吸引人&#xff0c;不然很少有人会有耐心等下去。每每遇到这种情况&#xff0c;运营Tik Tok的朋友就会开始挠头&#xff0c;…

算法通关村——从40个亿中产生一个不存在的整数

Titile: 海量数据场景下的热门算法题 从40个亿中产生一个不存在的整数 题目要求&#xff1a;给定一个输入文件&#xff0c;包含40亿个非负整数&#xff0c;请设计一个算法&#xff0c;产生一个不存在该文件中的整数&#xff0c;假设你有1GB的内存来完成这项任务。 进阶&…

总线:特性、分类、性能指标、系统总线的结构、总线仲裁、总线定时、总线标准

总线&#xff08;Bus&#xff09;&#xff0c;是一组为各功能部件之间进行信息传送的公共线路。 总线的特性&#xff1a; 机械特性&#xff08;物理特性&#xff09;&#xff1a;尺寸、形状、引脚数、排列顺序。电气特性&#xff1a;每根信号线上的信号传输方向、表示信号有效…

六、员工信息分页+启用/禁用员工账号(前端经典大数/精度丢失问题)

员工信息分页 整体流程&#xff1a; 1、创建mybatisplus配置类 在config包下创建mybatisplusconfig /*** 配置MybatisPlus分页插件*/ Configuration //既然是配置类&#xff0c;要加配置类的注解 public class MybatisPlusConfig {Beanpublic MybatisPlusInterceptor mybatis…

【VirtualBox】安装Ubuntu

一、新建虚拟系统 点击新建 输入名称&#xff0c;点击下一步 配置内存大小和处理器&#xff0c;点击下一步 选择不添加虚拟硬盘&#xff0c;点击下一步 点击完成 点击继续 二、修改虚拟机硬件配置 选择虚拟机&#xff0c;点击 “设置” 进入 “系统 -> 主板” 页面&…

131页8万字数字化矿山整体解决方案WORD(矿山资料合集)

本资料来源公开网络&#xff0c;仅供个人学习&#xff0c;请勿商用&#xff0c;如有侵权请联系删除&#xff0c;更多浏览公众号&#xff1a;智慧方案文库 目 录 1、煤矿综合自动化系统概述 1.1、煤矿数字化系统发展方向 1.2、建设必要性和意义 1.3、矿井综合自动化系统设计…

应用案例 | 3D视觉引导解决方案汽车零部件上下料

Part.1 行业背景 三维视觉引导技术在国内外汽车零部件领域得到了广泛应用。随着汽车制造业的不断发展和创新&#xff0c;对于零部件的加工和装配要求越来越高&#xff0c;而三维视觉引导技术能够帮助企业实现更精确、更高效的零部件上下料过程。 纵览国外&#xff0c;部分汽车…

【仿写spring之ioc篇】二、bean生命周期中的创建以及属性赋值

扫描类 这个类就不多说了&#xff0c;基本所有框架都要有这一步&#xff0c;这里主要关注我们目前要实现的方法&#xff0c;其他的具体方法可以查看源码 isComponent方法 /*** 扫描所有带有Component注解的java类&#xff0c;放入到BeanRegistry** return boolean*/public bo…

【前端demo】圣诞节灯泡 CSS动画实现轮流闪灯

文章目录 效果过程灯泡闪亮实现&#xff08;animation和box-shadow&#xff09;控制灯泡闪亮时间和顺序&#xff08;animation-delay&#xff09;按钮开关 代码htmlcssjs 参考代码1代码2 前端demo目录 效果 效果预览&#xff1a;https://codepen.io/karshey/pen/zYyBRWZ 参考…

初出茅庐的小李博客之STM32F103C8T6音乐控制器实战教程【1】

STM32F103C8T6音乐控制器实战教程[1] USB简单介绍&#xff1a; "USB"代表通用串行总线&#xff08;Universal Serial Bus&#xff09;&#xff0c;是一种用于连接计算机及其外部设备的标准接口。USB接口允许各种设备&#xff08;如打印机、存储设备、键盘、鼠标、摄…

为何电商行业都在争相使用WhatsApp引流小挂件?

WhatsApp小挂件是嵌入在网站上的聊天小部件&#xff0c;允许访问者同WhatsApp与您联系。点击后&#xff0c;它会将客户带到移动或桌面 WhatsApp应用程序&#xff0c;或者直接打开一个对话框&#xff0c;客户可以在这些地方与您发起对话。让我们看看在您的网站上拥有WhatsApp聊天…