MySQL 进阶(三)【SQL 优化】

1、SQL 优化

1.1、插入数据优化

1.1.1、Insert 优化

1、批量插入

插入多条数据时,不建议使用单条的插入语句,而是下面的批量插入:

INSERT INTO tb_name VALUES (),(),(),...;

批量插入建议一次批量 500~100 条,如果数据量比较大,建议通过多条批量插入语句来插入;

2、手动提交事务

        MySQL 默认会开启事务,但是默认每执行一次插入语句就开启和关闭一次事务,所以会有大量的事务启动关闭开销;建议使用手动提交事务

START TRANSACTIONS;
INSERT INTO tb_name VALUES (),(),(),...;
INSERT INTO tb_name VALUES (),(),(),...;
INSERT INTO tb_name VALUES (),(),(),...;
...
COMMIT;
3、主键顺序插入

在插入数据时,建议尽量顺序插入主键,而不是乱序插入:

主键顺序插入(高效):1 2 3 4 5 ...主键乱序插入(低效):5 3 4 2 1 ...

1.1.2、load 大批量插入数据

使用 load 指令将本地磁盘文件中的数据插入到数据库表当中:

# 客户端连接MySQL服务器时,加上参数 --local-infile
mysql --local-infile -uroot -p
# 设置全局参数 local_infile = 1
set global local_infile = 1;
# 执行 load 指令将准备好的数据加载到表中
load data local infile '/path_to_data' into table table_name fields terminated by ',' lines terminated by '\n';

这里的 load 指令有点像 HQL 中的 load:

LOAD DATA [LOCAL] INPATH '/opt/module/data/xxx.txt' TO TABLE table_name;

HQL 的 load 命令并不需要指定分隔符,因为在建表的时候我们已经在 row format 中设置的文件的分隔符了;此外,这里想到今天使用 HQL load 命令的一些需要注意的问题:

  • 在向分桶表 load 数据的时候,不能从 local 直接 load,而是得先上传到 hdfs 上,再从 hdfs load 到分桶表才行

1.2、主键优化

1.2.1、数据组织方式

在 InnoDB 存储引擎当中,表数据都是根据主键顺序组织存放的,这种存储方式的表称为索引组织表(IOT)

之前我们在学习索引的时候知道,InnoDB 存储引擎中的索引可以分为聚集索引和二级索引,而聚集索引正是由主键构成的一颗B+Tree,它的叶子节点存储的是行数据;

接下来,我们看一下当我们往数据库表中插入数据的时候,它的流程是什么样的?

1.2.2、页分裂

page 可以填满,也可以为空,但在 InnoDB 存储引擎规定,在一个 page 中至少包含 2 行数据;

主键顺序插入

当主键顺序插入时,一切都非常平静:

主键乱序插入

下面,我们看一下主键乱序插入时的情况:

上面,我们的主键都是乱序插入的,可以看到,现在来了一个主键为 50 的 row,按道理它是应该放到主键为 47 的前面,主键为 23 的后面(也就是 page 1)的,但是显然 page 1 现在已经存满了,那么接下来就会发生页分裂:

首先,page 1 (因为主键为 23 的数据当前理应插入到 page 1)会把自己 50% 之后的数据移动到一个新的 page 当中,然后将要主键为 23 的新数据页添加进去,最后将原本页之间的连接断开,重新建立页的连接;

所以,不难想到如果是大量数据的场景下,主键乱序插入时会出现频繁的页分裂现象,性能很低;与新增数据相反的是删除数据,删除数据又会引起页合并:

1.2.3、页合并

在 MySQL 中,当删除一行记录时,实际上数据并不会被物理删除,只是记录被标记为删除并且它的空间变得允许被其它记录回收使用;

当页中被删除的记录达到了 50%,InnoDB 会开始寻找最靠近的页(前或后),看看能否将两个页合并以优化空间使用;

 1.2.4、主键设置原则

  • 满足业务需求的情况下,尽量降低主键的长度。(因为在一张表中,聚集索引只有一个,而二级索引可以有多个,如果主键很长,二级索引也有很多,那么就会在存储时会消耗大量的磁盘空间,查询时也会消耗大量的磁盘IO)
  • 插入数据时,尽量选择顺序插入,选择使用 AUTO_INCREMENT 自增主键;(不会出现页分裂现象)
  • 尽量不要使用 uuid 做主键或者是其它自然主键,如身份证号;(主键长度太长,而且无序)
  • 尽量避免主键的修改;

1.3、order by 优化

  • Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作;所有不是通过索引直接返回排序结果的排序都是 FileSort 排序;
  • Using index:通过有序索引顺序扫描直接返回有序数据,这种情况就是 using index,不需要额外排序,操作效率高;

也就是说,我们在优化 order by 的时候,尽量优化成 using index;下面我们看一条普通的 order by 语句:

可以看到,在未建立索引时,order by 语句默认走的是 filesort;下面我们为 age 字段建立一个索引:

CREATE INDEX idx_student_no ON student(no);

注意:这里搜索时,我设置投影的字段是 id 和 no,其中 id 是主键,no 我们刚创建了索引,不能使用 select * ,因为只有使用覆盖索引(查询使用了索引,并且需要返回的列在该索引中全部能够找到对应的值)才能命中索引,不然索引不生效;

可以看到,即使是降序排序也是索引也是可以命中优化的;

 现在我们创建一个联合索引:idx_student_no_name,做一个测试:

可以看到,当 order by 的字段顺序和联合索引相反时,只有字段 no 能被索引命中,name 不可以,这是因为违背了最左前缀法则;继续:

可以看到,当 order by 的两个字段分别升序和降序排列时,降序的字段无法被索引命中,这是因为:

这是因为默认创建索引时,索引字段都是按照升序进行排列的,所以我们可以根据之后的需求在创建索引时,根据排序规则进行创建:

CREATE INDEX idx_student_no_name_ad ON student(no asc,name desc);

此时,再次查看查询计划:

可以看到,这样就解决了字段排序规则不同的问题;

 注意

  • 只有联合索引需要注意创建时的排序规则,单列索引不需要,因为单列索引默认升序,反向扫描只需要反向扫描即可。
  • 如果不可避免 filesort ,大数据排序时,可以适当增加排序缓冲区的大小 sort_buffer_size (默认 256 K)

1.4、group by 优化

这里我们同样讨论的是索引对于 group by 的影响,因为分组也是通过索引来提高查询效率的:

可以看到,在未建立索引前,使用 group by 语句时效率很低(临时表),但是创建索引后就可以走索引了;

可以看到,这次当我们用 age 字段 group by 时,又出现了 temporary ,效率依然不够好,这是因为违背了联合索引的最左前缀法则;可当我们同时使用 profession 和 age 进行 group by 时,索引再次命中;

但是有时候业务逻辑不需要我们去 group by 那么多的字段怎么办?

其实,就像上面这样,我们也可以通过前面给联合索引左边的字段加个 where 条件,让它满足最左前缀法则即可;

1.5、limit 优化

在大数据场景下,比如 limit 10000000,10 ,此时 MySQL 需要排序前 100000010 条记录(存储引擎会把这 100000010 条记录返回给服务层的缓存),并返回后 10 条记录,其它丢弃,查询排序的代价非常大;这一类问题也叫做深度分页

MySQL 深度分页是指在分页查询数据量比较大的表时,需要访问表中的某一段数据,而这段数据的位置非常靠后,需要通过较大的 offset 来获取目标数据。

阿里巴巴《Java 开发手册》:

1.5.1、覆盖索引 + 子查询

1.5.2、inner join 延迟关联

上面这两种方式没什么区别,下面是 inner join,上面是笛卡尔积,但是因为设置的过滤条件,所以等价于一个 inner join;对于 limit 的优化原理,简单来讲就是:控制返回的总页数。

        对于上面的 limit 10000000,10 来说,它会返回给服务端 10000010 条记录,然后再根据 offset 挨个抛弃前 10000000 条记录,返回给客户端剩余的 10 条记录。

        可以看出,当offset非0时,server层会从引擎层获取到很多无用的数据,而当select后面是*号时,就需要拷贝完整的行信息,拷贝完整数据只拷贝行数据里的其中一两个列字段耗时是不同的,这就让原本就耗时的操作变得更加离谱。

        因为前面的offset条数据最后都是不要的,就算将完整字段都拷贝来了又有什么用呢,所以我们可以将sql语句修改成下面这样:

select * from tb_user where id >=(select id from tb_user order by id limit 10000000, 1) order by id limit 10;

上面这条sql语句,里面先执行子查询 select id from page order by id limit 6000000, 1, 这个操作,其实也是将在innodb中的主键索引中获取到6000000+1条数据然后server层会抛弃前6000000条,只保留最后一条数据的id。

但不同的地方在于,在返回server层的过程中,只会拷贝数据行内的id这一列,而不会拷贝数据行的所有列,当数据量较大时,这部分的耗时还是比较明显的。

在拿到了上面的id之后,假设这个id正好等于10000000,那sql就变成了

select * from tb_user where id >=(10000000) order by id limit 10;

这样 innodb 再走一次主键索引,通过B+树快速定位到id=6000000的行数据,时间复杂度是lg(n),然后向后取10条数据。

关于深度分页,知乎这篇文章讲的很不错,我也是受启发于这篇文章;

1.6、count 优化

  • count 是一个聚合函数,对于返回的结果集,一行一行进行判断,只有不是 NULL 才会计数(count(字段)的时候,count(*)或者count(id)依然计算null)
SELECT COUNT(*) FROM table_name;

上面的 SQL  是查询当前表的总行数,不同的存储引擎的效率是不一样的:

  • 对于 MyISAM 而言,它会把表的总行数存到磁盘上,所以不加条件直接查询 count(*) 会直接返回结果,O(1)的时间复杂度;
  • 而对 InnoDB 而言,它就只能遍历整张表了,性能很低;

可以看到,如果 count(*) 的查询语句中包含 where 过滤条件,不管是 MYISAM 还是 InnoDB ,性能都很差,所以我们需要对它进行优化:

1.6.1、count 的几种用法

对于 count,我们使用的无非就是那几种:

  • count(主键)
    •  InnoDB 会遍历整张表,把每一行主键取出来返回给服务层,服务层拿到主键后直接进行累加(主键不可能为空);
  • count(字段)
    •  InnoDB 会遍历整张表,把每一行字段值取出来返回给服务层,如果字段有 not null 约束,那么就直接按行累加;如果没有,那么就对非 null 的值进行计数
  • count(1)
    •  InnoDB 遍历整张表,但是不取值。服务层会对返回的每一行放一个数字 1 进去,直接按行进行累加
  • count(*)
    •  InnoDB 同样遍历整张表,但是不会把字段取出来,而是专门做了优化。服务层直接按行进行累加

按照排序效率:count(字段) < count(主键) < count(1) < count(*) ,所以尽量使用 count(*),因为数据库专门对它做了优化;

1.7、update 优化

关于 update 语句需要注意的就是,update 的条件一定要是索引字段(比如主键),因为只有更新条件的字段是索引列才会是行锁,否则将是表锁

可以看到,当我们的更新条件是 no 字段时(不是索引列),当另一个客户端去更新数据时直接被阻塞,最后甚至超时更新失败;

所以,当我们在使用 update 语句的时候,一定要注意尽量使用索引字段做为更新条件去更新,否则就会出现行锁升级为表锁,并发性能就会降低;因为 InnoDB 的行锁是针对索引加的锁,而不是针对记录加的锁! 

总结

  • 插入数据
    • inset 语句,大数据量(分成500~1000的记录批量插入)建议使用批量插入,而且建议手动事务(避免频繁创建销毁事务开销)、主键顺序插入(避免页分裂,顺序插入性能高)
    • 大批量数据:load data local infile
  • 主键优化
    •  主键设计应尽量短、顺序插入(建议 auto_increment 而不是 uuid,比如身份证号,不仅长度长,而且无序)
  • order by 优化
    •  using index:直接返回数据(不需要再去服务层缓冲区排序),性能高
    • using filesort:需要将查询返回的结果去服务层缓冲区去排序
    • 所以在对 order by 进行优化时,其实是使用索引来进行优化的;涉及导排序的字段尽量建立索引,同时注意创建索引时的升序降序问题
    • 尽量使用覆盖索引而不是 select *
  • group by 优化
    •  索引,多字段分组时遵循最左前缀法则
  • limit 优化
    •  深度分页性能低,使用覆盖索引 + 子查询
  • count 优化
    •  count(字段) < count(主键) < count(1) < count(*)
  • update 优化
    •  update 的条件字段尽量使用索引字段(尽量主键),InnoDB 的行锁是针对索引加的锁,而不是针对记录加的锁!

所以,总之我们在做 SQL 优化的时候,其实基本都是在针对索引进行优化;

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

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

相关文章

GNN学习笔记

1.拉普拉斯矩阵 D是度矩阵&#xff0c;A是邻接矩阵 L的第二个公式常用 L的特征值>0 2.图的整体&#xff0c;节点&#xff0c;边都能代表一个分类/回归问题。 3.GNN的感受野 N-Hop Neighbors&#xff0c;某一点的n阶邻居。n步以内能到达的点。 4.残差连接 最后对图结果的处…

RK3568笔记三十六:LED驱动开发(设备树)

若该文为原创文章&#xff0c;转载请注明原文出处。 记录使用设备树编写一个简单的 LED 灯驱动程序 一、编程思路 程序编写的主要内容为添加 LED 灯的设备树节点、在驱动程序中使用 of 函数获取设备节点中的 属性&#xff0c;编写测试应用程序。 • 首先向设备树添加 LED 设备…

SCSA第八天

防火墙的带宽管理 核心思想 1&#xff0c;带宽限制 --- 限制非关键业务流量占用带宽的比例 2&#xff0c;带宽保证 --- 这里需要保证的是我们关键业务流量&#xff1b;再业务繁忙时&#xff0c;确保关键业务不受影 响&#xff1b; 3&#xff0c;连接数的限制 --- 这也…

如何生成好看的zabbix告警报表并发送邮件

作者 乐维社区&#xff08;forum.lwops.cn&#xff09; 许远 一、场景模拟 小东是一名资深的IT运维人员&#xff0c;其直属领导想要了解公司业务系统的健康状态以及小东日常的工作情况等&#xff0c;要求小东每周统计系统告警情况并发邮件给到他。小东所在公司搭建了一套zabbix…

为什么品牌需要做 IP 形象?

品牌做IP形象的原因有多方面&#xff0c;这些原因共同构成了IP形象在品牌建设中的重要性和价值&#xff0c;主要原因有以下几个方面&#xff1a; 增强品牌识别度与记忆点&#xff1a; IP形象作为品牌的视觉符号&#xff0c;具有独特性和辨识性&#xff0c;能够在消费者心中留…

Linux--网络基础

计算机网络背景 计算机网络背景是一个复杂而丰富的领域&#xff0c;涵盖了从计算机单机模式到网络互联的演变过程&#xff0c;以及网络技术的不断发展和创新。 计算机单机模式和独立发展 在早期&#xff0c;计算机主要以单机模式存在&#xff0c;即每台计算机都是独立的&…

SAC-IA粗配准算法记录

1. 算法思路 SAC-IA(Sample Consensus Initial Aligment,SAC-IA)粗配准算法是一种基于局部特征描述子的点云粗配准算法,其需要计算点云的快速点特征直方图(FPFH)来保持对应点对之间的相似关系,根据相似关系来搜索点云中的对应点。其基本原理是采用采样一致性的思想,通过查…

P2712 摄像头

好久没发帖了&#xff0c;放假了来水一波。 代码&#xff1a; #include<iostream> #include<cstring> #include<algorithm> using namespace std;const int N 200010, mod 80112002;int n, m; int nn; int e[N], h[N], ne[N], idx; int q[N], in[N], chu[N…

宝塔安装RabbitMq教程

需要放开15672端口&#xff0c;默认账号密码为guest/guest

前端开发日记——在MacBook上配置Vue环境

前言 大家好&#xff0c;我是来自CSDN的寄术区博主PleaSure乐事。今天是开始学习vue的第一天&#xff0c;我使用的编译器是vscode&#xff0c;浏览器使用的是谷歌浏览器&#xff0c;后续会下载webstorm进行使用&#xff0c;当前学习阶段使用vscode也是可以的&#xff0c;不用担…

Jupyter Notebook安装及基本使用

Jupyter Notebook安装及基本使用 目录 Jupyter Notebook安装及基本使用方式一&#xff1a;Anaconda直接安装方式二&#xff1a;pip命令安装Jupyter使用虚拟环境 方式一&#xff1a;Anaconda直接安装 安装Anaconda 下载地址&#xff0c;输入邮箱&#xff0c;Windows下载 开始安…

【sgWatermark.js】自定义组件:基于Vue2+html2canvas实现全局水印效果

sgWatermark.js源码 import html2canvas from html2canvas; // npm install --save html2canvas (官网&#xff1a;https://html2canvas.hertzen.com) export default {addWatermark({container document.body, // 水印添加到的容器innerHTML "水印文字内容", //…

最大文件句柄数

优质博文&#xff1a;IT-BLOG-CN 灵感来源 一、什么是文件句柄 文件句柄File Handle是操作系统中用于访问文件的一种数据结构&#xff0c;通常是一个整数或指针。文件句柄用于标识打开的文件&#xff0c;每个打开的文件都有一个唯一的文件句柄。 它们是对文件、网络套接字或…

实战:功能强大齐全BBS论坛项目Echo简介

项目简介 Echo 是一套前后端不分离的开源社区系统&#xff0c;基于目前主流 Java Web 技术栈&#xff08;SpringBoot MyBatis MySQL Redis Kafka Elasticsearch Spring Security ...&#xff09;&#xff0c;并提供详细的开发文档和配套教程。包含帖子、评论、私信、系…

InstaPrism能否平替BayesPrism(贝叶斯棱镜)?

上一期内容提到了BayesPrism算法用于单细胞数据的反卷积&#xff0c;BayesPrism算法在实际应用中非常占用计算资源以及消耗使用者的时间。那么是否有较好的替代包呢&#xff1f; 曾老师告诉了我一个R包—InstaPrism&#xff0c;他希望我将其和BayesPrism算法做个对比。 开发者…

使用Python Turtle绘制圣诞树和装饰

简介(❤ ω ❤) 在这篇文章中&#xff0c;我们将探索如何使用Python的Turtle模块来绘制一个充满节日气氛的圣诞树&#xff0c;以及一些可爱的装饰品。Turtle是一个受Logo语言启发的图形库&#xff0c;非常适合初学者学习编程和创建图形。 码农不是吗喽&#xff08;大学生版&…

【常见开源库的二次开发】基于openssl的加密与解密——单向散列函数(四)

目录&#xff1a; 目录&#xff1a; 一、什么是单项散列函数&#xff1f; 1.1 如何验证文件是否被修改过 1.2 单项散列函数&#xff1a; 二、单向hash抗碰撞 2.1 弱抗碰撞&#xff08;Weak Collision Resistance&#xff09; 2.2 强抗碰撞&#xff08;Strong Collision Resista…

图像边缘检测中Sobel算子的原理,并附OpenCV和Matlab的示例代码

Sobel算子是一种用于图像边缘检测的离散微分算子。它结合了图像的平滑处理和微分计算&#xff0c;旨在强调图像中强度变化显著的区域&#xff0c;即边缘。Sobel算子在图像处理中被广泛使用&#xff0c;特别是在计算机视觉和图像分析领域。 Sobel算子的原理 Sobel算子主要用于计…

大模型入门(一)—— LLaMa/Alpaca/Vicuna

LLaMa模型是Meta开源的大模型&#xff0c;模型参数从7B到65B不等&#xff0c;LLaMa-7B在大多数基准测试上超过了GPT3-173B&#xff0c;而LLaMa-65B和Chinchilla-70B、PaLM-540B相比也极具竞争力。相比于ChatGPT或者GPT4来说&#xff0c;LLaMa可能效果上还有差距&#xff0c;但相…

HyperBDR云容灾,让低碳未来有“迹”可循

全球气候变化时刻牵动着我们的心。生活在同一个地球下&#xff0c;万博智云始终坚持环境友好&#xff0c;携手企业和合作伙伴在保持市场竞争力、促进企业可持续发展的同时&#xff0c;共同肩负起守护地球环境的责任。 HyperBDR云容灾以“碳足迹”践行低碳容灾 云产品及数据中心…