如何编写快速高效的SQL查询(四)——优化特定类型的查询与样例

news2024/11/25 20:18:51

这一节,我们将介绍如何优化特定类型的查询。
本节介绍的多数优化技巧都和特定的版本有关,所以对于未来MySQL的版本未必适用。毫无疑问,某一天优化器自己也会实现这里列出的部分或者全部优化技巧。

优化COUNT()查询

COUNT()聚合函数,以及如何优化使用了该函数的查询,很可能是MySQL中最容易被误解的前10个话题之一。你在网上随便搜索一下就能看到很多错误的理解,可能比我们想象中的要多得多。在做优化之前,先来看看COUNT()函数真正的作用是什么。

COUNT()的作用

COUNT()是一个特殊的函数,有两种非常不同的作用:它可以统计某列的值的数量,也可以统计行数。在统计列值时要求列值是非空的(不统计NULL)。如果在COUNT()的括号中指定了列或者列的表达式,则统计的就是这个表达式有值的结果数。因为很多人对NULL理解有问题,所以这里很容易产生误解。如果你想了解更多关于SQL语句中NULL的含义,建议阅读一些关于SQL语句基础的书籍。

COUNT()的另一个作用是统计结果集的行数。当MySQL确认括号内的表达式值不可能为空时,实际上就是在统计行数。最简单的就是当我们使用COUNT(*)的时候,这种情况下通配符*并不会像我们猜想的那样扩展成所有的列,实际上,它会忽略所有的列而直接统计所有的行数。

我们发现最常见的错误之一是,当需要统计行数时,在COUNT()函数的括号内指定了列名。如果想要知道结果中的行数,应该始终使用COUNT(*),这样可以更清晰地传达意图,避免糟糕的性能表现。

简单优化

通常会看到这样的问题:如何在一个查询中统计同一列的不同值的数量,以减少查询的语句量。例如,假设可能需要通过一个查询返回各种不同颜色的商品数量,此时不能使用OR语句(比如,SELECT COUNT(color='blue'OR color='red')FROM items;),因为这样做无法区分不同颜色的商品数量;也不能在WHERE条件中指定颜色(比如,SELECT COUNT(*)FROM items WHERE color='blue'AND color='RED';),因为颜色的条件是互斥的。下面的查询可以在一定程
度上解决这个问题:1
在这里插入图片描述

也可以使用COUNT()而不是SUM()实现同样的目的,只需要将满足条件设置为真,不满足条件设置为NULL即可:
在这里插入图片描述

使用近似值

有时候,某些业务场景并不要求完全精确的统计值,此时可以用近似值来代替。EXPLAIN出来的优化器估算的行数就是一个不错的近似值,执行EXPLAIN并不需要真正地去执行查询,所以成本很低。

很多时候,计算精确值非常复杂,而计算近似值则非常简单。曾经有一个客户希望我们统计他的网站的当前活跃用户数是多少,这个活跃用户数保存在缓存中,过期时间为30分钟,所以每隔30分钟需要重新计算并放入缓存。这个活跃用户数本身就不是精确值,所以使用近似值代替是可以接受的。另外,如果要精确统计在线人数,使用WHERE条件会很复杂,一方面需要剔除当前非活跃用户,另一方面还要剔除系统中某些特定ID的“默认”用户,去掉这些约束条件对总数的影响很小,但却可能提升该查询的性能。更进一步的优化则可以尝试删除DISTINCT这样的约束来避免文件排序。这样重写过的查询比原来精确统计的查询快很多,而返回的结果则几乎相同。

更复杂的优化

通常来说,COUNT()查询需要扫描大量的行(意味着要访问大量数据)才能获得精确的结果,因此是很难优化的。除了前面提到的方法,在MySQL层面还能做的就只有索引覆盖扫描了。如果这还不够,那就需要考虑修改应用的架构,可以增加类似Memcached这样的外部缓存系统。不过,可能很快你就会陷入一个熟悉的困境:“快速、精确和实现简单”。三者永远只能满足其二,必须舍掉一个。

优化联接查询

这个话题基本上整本书都在讨论,这里需要特别提到以下几点。

  • 确保ON或者USING子句中的列上有索引。在创建索引的时候就要考虑到联接的顺序。当表A和表B用列c联接的时候,如果优化器的联接顺序是B、A,那么就不需要在B表的对应列上建索引。没有用到的索引只会带来额外的负担。一般来说,除非有其他理由,否则只需在联接顺序中的第二个表的相应列上创建索引。
  • 确保任何GROUP BY和ORDER BY中的表达式只涉及一个表中的列,这样MySQL才有可能使用索引来优化这个过程。
  • 当升级MySQL的时候需要注意:联接语法、运算符优先级等其他可能会发生变化的地方。因为以前是普通联接的地方可能会变成笛卡儿积,不同类型的联接可能会生成不同的结果,甚至会产生语法错误。

使用WITH ROLLUP优化GROUP BY

分组查询的一个变种就是要求MySQL对返回的分组结果再做一次超级聚合。可以使用WITH ROLLUP子句来实现这种逻辑,但可能优化得不够。可以通过EXPLAIN来观察其执行计划,特别要注意分组是否是通过文件排序或者临时表实现的。然后再去掉WITH ROLLUP子句来看执行计划是否相同。也可以通过本节前面介绍的优化器提示来强制执行计划。

很多时候,如果可以,在应用程序中做超级聚合是更好的,虽然这需要给客户端返回更多的结果。也可以在FROM子句中嵌套使用子查询,或者是通过一个临时表存放中间数据,然后和临时表执行UNION来得到最终结果。最好的办法是尽可能地将WITH ROLLUP功能转移到应用程序中处理。

优化LIMIT和OFFSET子句

在系统中需要进行分页操作的时候,我们通常会使用LIMIT加上偏移量的办法实现,同时加上合适的ORDER BY子句。如果有对应的索引,通常效率会不错,否则,MySQL需要做大量的文件排序操作。

一个非常常见又令人头疼的问题是,在偏移量非常大的时候,例如,可能是LIMIT 1000,20这样的查询,这时MySQL需要查询10020条记录然后只返回最后20条,前面10 000条记录都将被抛弃,这样的代价非常高。如果所有的页面被访问的频率都相同,那么这样的查询平均需要访问半个表的数据。要优化这种查询,要么是在页面中限制分页的数量,要么是优化大偏移量的性能。

优化此类分页查询的一个最简单的办法就是尽可能地使用索引覆盖扫描,而不是查询所有的行。然后根据需要做一次联接操作再返回所需的列。在偏移量很大的时候,这样做的效率会有非常大的提升。考虑下面的查询:
在这里插入图片描述

如果这个表非常大,那么这个查询最好改写成下面的样子:
在这里插入图片描述

这种“延迟联接”之所以有效,是因为它允许服务器在不访问行的情况下检查索引中尽可能少的数据,然后,一旦找到所需的行,就将它们与整个表联接,以从该行中检索其他列。类似的技术也适用于带有LIMIT子句的联接。

有时候也可以将LIMIT查询转换为已知位置的查询,让MySQL通过范围扫描获得对应的结果。例如,如果在一个位置列上有索引,并且预先计算出了边界值,上面的查询就可以改写为:
在这里插入图片描述

对数据进行排名的问题也与此类似,但往往还会同时和GROUP BY混合使用。在这种情况下通常需要预先计算并存储排名信息。

LIMIT和OFFSET的问题,其实是OFFSET的问题,它会导致MySQL扫描大量不需要的行然后再抛弃掉。如果可以使用书签记录上次取数据的位置,那么下次就可以直接从该书签记录的位置开始扫描,这样就可以避免使用OFFSET。例如,若需要按照租借记录做翻页,那么可以根据最新一条租借记录向回追溯,这种做法可行是因为租借记录的主键是单调增长的。首先使用下面的查询获得第一组结果:
在这里插入图片描述

假设上面的查询返回的是主键为16,049到16,030的租借记录,那么下一页查询就可以从16,030这个点开始:
在这里插入图片描述

该技术的好处是无论翻页到多么靠后,其性能都会很好。

其他优化办法还包括使用预先计算的汇总表,或者联接到一个冗余表,冗余表只包含主键列和需要做排序的数据列。

优化SQL CALC FOUND ROWS

分页的时候,另一个常用的技巧是在LIMIT语句中加上SQL_CALC_FOUND_ROWS提示(hint),这样就可以获得去掉LIMIT以后满足条件的行数,因此可以作为分页的总数。看起来,MySQL做了一些非常“高深”的优化,像是通过某种方法预测了总行数。但实际上,MySQL只有在扫描了所有满足条件的行以后,才会知道行数,所以加上这个提示以后,不管是否需要,MySQL都会扫描所有满足条件的行,然后再抛弃掉不需要的行,而不是在满足LIMIT的行数后就终止扫描。所以该提示的代价可能非常高。

一个更好的设计是将具体的页数换成“下一页”按钮,假设每页显示20条记录,那么我们每次查询时都是用LIMIT返回21条记录并只显示20条,如果第21条存在,那么就显示“下一页”按钮,否则就说明没有更多的数据,也就无须显示“下一页”按钮了。

另一种做法是先获取并缓存较多的数据——例如,缓存1000条——然后每次分页都从这个缓存中获取。这样做可以让应用程序根据结果集的大小采取不同的策略,如果结果集小于1000,就可以在页面上显示所有的分页链接,因为数据都在缓存中,所以这样做不会对性能造成影响。如果结果集大于1000,则可以在页面上设计一个额外的“找到的结果多于1000条”之类的按钮。这两种策略都比每次生成全部结果集再抛弃不需要的数据的效率高很多。

有时候也可以考虑使用EXPLAIN的结果中的rows列的值来作为结果集总数的近似值(实际上,Google的搜索结果总数也是一个近似值)。当需要精确结果的时候,再单独使用COUNT(*)来满足需求,这时如果能够使用索引覆盖扫描则通常也会比SQL_CALC_FOUND_ROWS快得多。

优化UNION查询

MySQL总是通过创建并填充临时表的方式来执行UNION查询,因此很多优化策略在UNION查询中都没法很好地被使用。经常需要手工地将WHERE、LIMIT、ORDER BY等子句“下推”到UNION的各个子查询中,以便优化器可以充分利用这些条件进行优化(例如,直接将这些子句冗余地写一份到各个子查询)。

除非你确实需要服务器消除重复的行,否则一定要使用UNION ALL,这一点很重要。如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这会导致对整个临时表的数据做唯一性检查。这样做
的代价非常高。即使有ALL关键字,MySQL仍然会使用临时表存储结果。事实上,MySQL总是将结果放入临时表,然后再读出,再返回给客户端,虽然很多时候这样做是没有必要的(例如,MySQL可以直接把这些结果返回给客户端)。


  1. 也可以写成这样的SUM()表达式:SUM(color=‘blue’),SUM(color=‘red’)。 ↩︎

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

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

相关文章

Hive安装部署

1、Hive安装地址 ①Hive官网地址 Apache Hive ②文档查看地址 GettingStarted - Apache Hive - Apache Software Foundation ③下载地址 Index of /dist/hive ④github地址 GitHub - apache/hive: Apache Hive 2、 安装Hive 1)把apache-hive-3.1.3-bin.ta…

“AI换脸”诈骗背后,如何应对黑灰产使用手段?

目录 诈骗是如何发生的? AI换脸诈骗的操作防范 AI换脸的风险分析与技术防范 近日,警方通报了一起使用智能AI技术进行电信诈骗的案件。被骗者是福州市某科技公司法人代表郭先生,他通过微信视频接到自己好友的电话,对方佯装需要借…

帕累托改进和帕累托最优、卡尔多-希克斯改进

根据目标个数,分为单目标规划,以及多目标规划。多目标的规划是去找折中的解集合,既pareto最优解集合。对优化目标超过3个以上的,称之为超多目标优化问题。 帕累托改进描述的就是在没有人变得不好的前提下让有些人更好的过程。帕累…

GPT虚拟直播Demo系列(二)|无人直播间实现虚拟人回复粉丝

摘要 虚拟人和数字人是人工智能技术在现实生活中的具体应用,它们可以为人们的生活和工作带来便利和创新。在直播间场景里,虚拟人和数字人可用于直播主播、智能客服、营销推广等。接入GPT的虚拟人像是加了超强buff,具备更强大的自然语言处理能…

Postman 接口测试神器

Postman 接口测试神器 Postman 是一个接口测试和 http 请求的神器,非常好用。 官方 github 地址: Postman Inc. GitHub Postman 的优点: 支持各种的请求类型: get、post、put、patch、delete 等支持在线存储数据,通过账号就可以进行迁移…

HY-M5 三维机器视觉系统在工业自动化生产的应用

行业背景: 如今科学技术有了日新月异的变化,工业自动化也在不断地发展。然而,在高强度、高精准的工作环境下,人工操作已经不能适应企业的发展需求,于是机器人的出现便提供了高效快捷的解决方案。为了实现自动化生产并确…

AUTOSAR通信篇 - CAN网络通信(二:CanIf)

在上一篇,我们介绍了CAN模块,接下来我们介绍在CAN模块之上的模块Can Interface(CanIf)模块。在AUTOSAR软件架构中,CanIf也在BSW层,它处于CAN模块之上紧挨着CAN模块。CanIf是一个硬件独立层,具有…

【MySQL】如何实现单表查询?

在我们对数据进行操作时,查询无疑是至关重要的,查询操作灵活多变,我们可以根据开发的需求,设计高效的查询操作,把数据库中存储的数据展示给用户。 文章目录 前言1. 基础查询1.1 基础查询语法1.2 基础查询练习 2. 条件查…

算法与数据结构-复杂度分析(上)

文章目录 什么是大 O 复杂度表示法为什么要用大 O 复杂度表示法如何分析一段代码的时间复杂度1、只关注循环执行次数最多的一段代码2、加法法则:总复杂度等于量级最大的那段代码的复杂度3、乘法法则:嵌套代码的复杂度等于嵌套内外代码复杂度的乘积 几种常…

Unity老动画系统Animation

1、创建老动画系统 给要制作动画的GameObeject添加Animation组件 2、Animation参数 Animation:默认播放的动画 Animations:该动画组件可以控制的所有动画 Play AutoMatically:是否一开始就自动播放默认动画 Animate Physics:动画…

【JavaSE】Java基础语法(三十二):Stream流

文章目录 1. 体验Stream流2. Stream流的常见生成方式3. Stream流中间操作方法【应用】4. Stream流终结操作方法【应用】5. Stream流的收集操作 1. 体验Stream流 案例需求 按照下面的要求完成集合的创建和遍历 创建一个集合,存储多个字符串元素把集合中所有以"…

Python文件打包成exe文件

文章目录 背景安装pyinstaller开始打包总结 背景 今天因为在线将pdf转为word被收费了,有点不爽,所以自己动手撸一个pdf转word的小工具,想着打包成exe给朋友使用,万一哪天会用到呢? 安装pyinstaller 打开cmd命令窗口…

【AGC】云监控日志服务查询不到Logger日志相关问题

【关键字】 AGC、云监控、日志服务 【问题描述】 开发者反馈在使用AGC云监控,填写了Logger日志,但是在云监控的日志服务查不到的问题。具体如下所述: 云函数按要求写了Logger日志,但是在云监控的日志服务页面查询不到&#xff…

R语言混合效应(多水平/层次/嵌套)模型及 贝叶斯实现技术

回归分析是科学研究中十分重要的数据分析工具。随着现代统计技术发展,回归分析方法得到了极大改进。混合效应模型(Mixed effect model),即多水平模型(Multilevel model)/分层模型(Hierarchical Model)/嵌套…

【计算思维题】少儿编程 蓝桥杯青少组计算思维 数学逻辑思维真题详细解析第7套

少儿编程 蓝桥杯青少组计算思维 数学逻辑思维真题详细解析第7套 1、下图中,乐乐家的位置用数对(4,3)表示,学校在乐乐家西南方向。下列选项中,学校的位置不可能是 A、(5,4) B、(2,2) C、(2,1) D、(1,2) 答案:A 考点分析:主要考查小朋友们的观察能力和方…

springboot聚合项目程打包,提示包不存在

报错提示如下&#xff0c;这是子模块large_screen调用login_security模块时&#xff0c;找不到login_security的包&#xff0c;但是login_security能单独打包成功 项目结构&#xff0c;两个子模块可以启动 解决办法&#xff1a; 父pom,要用 <packaging>pom</packag…

基础sql代码讲解含运行截图(详细版)

用student表为例&#xff0c;表的结构如下&#xff1a; 查询student表中的全部数据 SELECT * FROM student 插入数据&#xff1a; INSERT INTO student (id,name,phone,age) VALUES (2,张,1123,19) 还可以不写字段名字进行插入&#xff0c;但是此种方式必须和数据库字段一一…

3:String类

文章目录 String类1&#xff1a;介绍&#xff1a;2&#xff1a;String类实现了很多的接口&#xff1a;3&#xff1b;String类常用构造器4&#xff1a;不同方式创建String类对象的区别&#xff08;1&#xff09;直接赋值的方式&#xff08;2&#xff09;常规new的方式&#xff0…

租赁行业提供固定资产管理的解决方案

在租赁行业&#xff0c;固定资产管理和盘点是非常重要的环节。然而&#xff0c;由于资产数量庞大、资产分散、资产更新频繁等因素&#xff0c;使得固定资产管理和盘点变得十分复杂和繁琐。为了解决这些问题&#xff0c;易点易动固定资产管理系统应运而生。 易点易动固定资产管…

vulnhub 靶机渗透:Stapler

Stapler nmap扫描21 端口22 53端口80端口目录爆破 139端口666 端口3306端口12380端口获取数据库root权限获取系统立足点提权 其他思路系统立足点1系统立足点2提权1提权2 https://www.vulnhub.com/entry/stapler-1,150/ 靶机ip:192.168.54.27 kali ip:192.168.54.128 nmap扫描 …