MySQL之查询性能优化(十四)

查询性能优化

使用用户自定义变量

优化排名语句

使用用户自定义变量的一个特性是你可以在给一个变量赋值的同时使用这个变量,换句话说,用户自定义变量的赋值具有"左值"特性。下面的例子展示了如何使用变量来实现一个类似"行号(row number)"的功能:

mysql> SET @rownum := 0;
Query OK, 0 rows affected (0.00 sec)mysql> SELECT actor_id, @rownum := @rownum + 1 AS rownum FROM sakila.actor ORDER BY actor_id ASC  LIMIT 3;
+----------+--------+
| actor_id | rownum |
+----------+--------+
|        1 |      1 |
|        2 |      2 |
|        3 |      3 |
+----------+--------+
3 rows in set (0.01 sec)

这个例子的实际意义不打,它只是实现了一个和该主键一样的列。不过,我们也可以把这当作是一个排名。现在我们来看一个更复杂的用法。我们先编写一个查询获取演过最多电影的前10位演员,然后根据它们的出演电影次数做一个排名,如果出演的电影数量一样,则排名相同,我们先编写一个查询,返回每隔演员参演电影的数量:

mysql> SELECT actor_id,COUNT(*) AS cnt-> FROM sakila.film_actor-> GROUP BY actor_id-> ORDER BY cnt DESC-> LIMIT 10;
+----------+-----+
| actor_id | cnt |
+----------+-----+
|      107 |  42 |
|      102 |  41 |
|      198 |  40 |
|      181 |  39 |
|       23 |  37 |
|       81 |  36 |
|       60 |  35 |
|       13 |  35 |
|      158 |  35 |
|      144 |  35 |
+----------+-----+
10 rows in set (0.00 sec)

现在我们再把排名加上去,这里看到有四名演员都参演了35部电影,所以它们的排名应该是相同的。我们使用三个变量来实现:一个用来记录当前的排名,一个用来记录前一个演员的排名,还有一个用来记录当前演员参演的电影数量。只有当前演员参演的电影的数量和前一个演员不同时,排名才变化。我们先试试下面的写法:

mysql> SELECT actor_id,-> @curr_cnt :=COUNT(*) AS cnt,-> @rank :=IF(@prev_cnt <> @curr_cnt, @rank +1, @rank) AS rank,-> @prev_cnt := @curr_cnt AS dummy-> FROM sakila.film_actor-> GROUP BY actor_id-> ORDER BY cnt DESC-> LIMIT 10;
+----------+-----+------+-------+
| actor_id | cnt | rank | dummy |
+----------+-----+------+-------+
|      107 |  42 |    0 |     0 |
|      102 |  41 |    0 |     0 |
|      198 |  40 |    0 |     0 |
|      181 |  39 |    0 |     0 |
|       23 |  37 |    0 |     0 |
|       81 |  36 |    0 |     0 |
|      106 |  35 |    0 |     0 |
|       60 |  35 |    0 |     0 |
|       13 |  35 |    0 |     0 |
|       37 |  35 |    0 |     0 |
+----------+-----+------+-------+
10 rows in set (0.00 sec)

Oops——排名和统计列一直都无法更新,这是什么原因?对于这类问题,是没法给出一个放之四海皆准的答案的,例如,一个变量名的拼写错误就可鞥导致这样的问题(这个案例中并不是这个原因),具体问题要具体分析。这里,通过EXPLAIN我们看到将会使用临时表和文件排序,所以可能是由于变量赋值的时间和我们预料的不同。在使用用户自定义变量的时候,经常会遇到一些"诡异"的现象,要揪出这些问题的原因通常都不容易,但是相比其带来的好处,深究这些问题是值得的。使用SQL语句生成排名值通常需要做两次计算,例如,需要额外计算一次出演过相同数量电影的演员有哪些。使用变量则可一次完成——这对性能是一个很大的提升。针对这个案例,另一个简单的方案是在FROM子句中使用子查询生成一个中间的临时表:

mysql> SELECT actor_id, @curr_cnt :=cnt AS cnt, @rank:= IF(@prev_cnt <> @curr_cnt, @rank +1, @rank + 1) AS rank, @prev_cnt := @curr_cnt AS dummy FROM ( SELECT actor_id, COUNT(*)
AS cnt FROM sakila.film_actor GROUP BY actor_id ORDER BY cnt DESC LIMIT 10 ) as der;
+----------+-----+------+-------+
| actor_id | cnt | rank | dummy |
+----------+-----+------+-------+
|      107 |  42 |    1 |    42 |
|      102 |  41 |    2 |    41 |
|      198 |  40 |    3 |    40 |
|      181 |  39 |    4 |    39 |
|       23 |  37 |    5 |    37 |
|       81 |  36 |    6 |    36 |
|       60 |  35 |    7 |    35 |
|       13 |  35 |    8 |    35 |
|      158 |  35 |    9 |    35 |
|      144 |  35 |   10 |    35 |
+----------+-----+------+-------+
10 rows in set (0.01 sec)

避免重复查询刚刚更新的数据

如果在更新行的同时又希望获得该行的信息,要怎么做才能避免重复的查询呢?不幸的是,MySQL并不支持像PostgreSQL那样的UPDATE RETURNING语法,这个语法可以帮你在更新行的时候同时返回该行的信息。还好在MySQL中你可以使用变量来解决这个问题。例如,一个用户希望能够更高效地更新一条记录地时间戳,同时希望查询当前记录中存放地时间戳是什么。简单地,可以用下面地代码来实现:

UPDATE t1 SET lastUpdated = NO() WHERE id =1;
SELECT lastUpdated FROM t1 WHERE id = 1;

使用变量,我们可以按如下方式重写查询:

UDPATE t1 SET lastUpdated = NOW() WHERE id = 1 AND @now:=NOW();

上面查询看起来仍然需要两个查询,需要两次网络来回,但是这里的第二个查询无须访问任何数据表,所以会快非常多。(如果网络延迟非常大,那么这个优化的意义可能不大,不过这对这个用户,这样做的效果很好)

统计更新和插入的数量

当使用了INSERT ON DUPLICATE KEY UPDATE的时候,如果想知道到底插入了多少行数据,到底有多少数据是因为冲突而改写成更新操作的?Kerstian Kohntopp在他的博客上给出了一个解决这个问题的办法,实现办法的本质如下:

INSERT INTO t1 (c1, c2) VALUES(4,4),(2,1), (3,1)
ON DUPLICATE KEY UPDATE
c1 = VALUES(c1) + () * (@x:=@x+1));

当每次由于冲突导致更新时对变量@x自增一次。然后通过对这个表达式乘以0来让其不影响要更新的内容。另外,MySQL的协议会返回被更改的总行数,所以不需要单独统计这个值

确定取值的顺序

使用用户自定义变量的一个最常见的问题就是没有注意到在赋值和读取变量的时候可能是在查询的不同的阶段。例如,在SELECT子句中进行赋值然后在WHERE子句中读取变量,则可能变量取值并不如你所想。下面的查询看起来只返回一个结果,但事实并非如此:

mysql> SET @rownum := 0;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT actor_id, @rownum := @rownum + 1 AS cnt-> FROM sakila.actor-> WHERE @rownum <= 1;
+----------+------+
| actor_id | cnt  |
+----------+------+
|       58 |    1 |
|       92 |    2 |
+----------+------+
2 rows in set (0.00 sec)

因为WHERE和SELECT是在查询执行的不同阶段被执行的。如果在查询中再加入ORDER BY的化,结果可能会更不同:

mysql> SELECT actor_id, @rownum := @rownum + 1 AS cnt FROM sakila.actor WHERE @rownum <= 1 ORDER BY first_name;

这是因为ORDER BY 引入了文件排序,而WHERE条件是文件排序操作之前取值的,所以这条查询会返回表中的全部记录。解决这个问题的办法是让变量的赋值和取值发生在执行查询的同一阶段:

mysql> SET @rownum :=0;
Query OK, 0 rows affected (0.00 sec)mysql> SELECT actor_id, @rownum AS rownum-> FROM sakila.actor-> WHERE (@rownum := @rownum + 1) <= 1;
+----------+--------+
| actor_id | rownum |
+----------+--------+
|       58 |      1 |
+----------+--------+
1 row in set (0.00 sec)

小测试:如果在上面再加入ORDER BY ,那会返回什么结果?试试看吧,如果得出的结果出乎你的意料,想想为什么?再看下面这个查询会返回什么,下面的查询中ORDER BY子句会改变变量值,那WHERE语句执行时变量是多少。

mysql> SELECT actor_id, first_name, @rownum AS rownum FROM sakila.actor WHERE @rownum <= 1 ORDER BY first_name, LEAST(0, @rownum := @rownum + 1);
+----------+------------+--------+
| actor_id | first_name | rownum |
+----------+------------+--------+
|        2 | NICK       |      2 |
|        1 | PENELOPE   |      1 |
+----------+------------+--------+
2 rows in set (0.01 sec)

这个最出人意料的变量行为的答案可以在EXPLAIN语句中找到,注意看在Extra列中的"Using where"、“Using temporary"或者"Using filesort”.
在上面的最后一个例子中,我们引入了一个新的技巧:我们将赋值语句放到LEAST()函数中,这样就可以在完全不改变排序顺序的时候完成赋值操作(在上面例子中,LEAST()函数总是返回0).这个技巧在不希望对子句的执行结果有影响却又要完成变量赋值的时候很有用。这个例子中,无须在返回值中新增额外列。这样的函数还有GREATEST()、LENGTH()、ISNULL()、NULLIFL()、IF()和COALESCE(),可以单独使用也可以组合使用。例如,COALESCE()可以在一组参数中取第一个已经被定义的变量/

编写偷懒的UNIO

假设需要编写一个UNION查询,其第一个子查询作为分支条件先执行,如果找到了匹配的行,则跳过第二个分支。在某些业务场景中确实会有这样的需求,比如先在一个频繁访问的表中查找"热"数据,找不到再去另外一个较少访问的表中查找"冷"数据(区分热数据和冷叔是一个很好的提高缓存命中率的方法)。
下面的查询会在两个地方查找一个用户——一个主用户表,一个长事件不活跃的用户表,不活跃用户表的目的是为了实现更高效的归档(Baron认为在一些社交网站上归档一些常见不活跃用户后,用户重新回到网站时有这样的需求,当用户再次登录时,一方面我们需要将其从归档中重新拿出来,另外,还可以给他发送一份欢迎邮件。这对一些不活跃的用户是非常好的一个优化)

SELECT id FROM WHERE id = 123
UNION ALL
SELECT id FROM users_archived WHERE id = 123;

上面这个查询是可以正常工作的,但是即使在users表中找到了记录,上面的查询还是会去归档表user_archived中再查找一次。我们可以用一个偷懒的UNION查询来抑制这样的数据返回,而且只有当第一个表中没有数据时,我们才在第二个表中查询。一旦在第一个表中找到记录,我们就定义一个变量@found.我们通过在结果列中做一次赋值来实现,然后将赋值放在CREATEST中来避免返回额外的数据。为了明确我们的结果到底来自哪张表,我们新增了一个包含表名的列。最后我们需要在查询的末尾将变量重置为NULL。这样保证遍历时不会干扰后面的结果。完成的查询如下:

SELECT GREATEST(@found := -1, id) AS id, 'users' AS which_tbl
FROM users WHERE id = 1
UNION ALL
SELECT id, 'users_archived'
FROM users_archived WHERE id = 1 AND @found IS NULL
UNION ALL
SELECT 1, 'reset' FROM DUAL WHERE (@found := NULL) IS NOT NULL;

用户自定义变量的其他用处

不仅是在SELECT语句中,在其他任何类型的SQL语句中都可以对变量进行赋值。事实上,这也是用户自定义变量最大的用途。例如,可以像前面使用子查询的方式改进排名语句一样改进UPDATE语句。不过我们需要使用一些技巧来获得我们希望的效果。有时,优化器会把变量当作一个编译时常量来对待,而不是对其进行赋值。将函数放在类似于LEAST()这样的函数中通常可以避免这样的问题。另一个办法是在查询被执行前检查变量是否被赋值。不同的场景下使用不同的办法。通过一些实际,可以了解所有用户自定义变量能够做的有趣的事情,例如下面这些用法:

  • 1.查询运行时计算总数和平均值
  • 2.模拟GROUP 语句中的函数FIRST()和LAST()
  • 3.对大量数据做一些数据计算
  • 4.计算一个大表的MD5散列值
  • 5.编写一个样本处理函数,当样本中的数值超过某个边界值的时候将其变成0
  • 6.模拟读/写游标
  • 7.在SHOW语句的WHERE子句中加入变量值

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

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

相关文章

【第14章】SpringBoot实战篇之多环境配置

文章目录 前言一、通用配置文件1. 定义2. 使用2.1 application.yml2.2 启动类 3. 测试 二、多环境配置文件1.定义1.1 application-local.yml1.2 application-dev.yml1.3 application-test.yml1.4 application-prod.yml 2.使用2.1 application.yml2.2 启动类 3.测试 三、多环境配…

OpenGL-ES 学习(6)---- Ubuntu OES 环境搭建

OpenGL-ES Ubuntu 环境搭建 此的方法在 ubuntu 和 deepin 上验证都可以成功搭建 目录 OpenGL-ES Ubuntu 环境搭建软件包安装第一个三角形基于 glfw 实现基于 X11 实现 软件包安装 sudo apt install libx11-dev sudo apt install libglfw3 libglfw3-dev sudo apt-get install…

​2020-2024 idea最新安装激活

前言&#xff1a;怎么才能既免费&#xff0c;又能使用上正式版呢&#xff01;&#xff08;不是正版用不起&#xff0c;而是‘激活’更有性价比&#xff09; 1-2 下载安装&#xff0c;此处省略 记得安装好不要打开&#xff0c;看下一步。 3.开始 3.1打开idea 首先打开idea&am…

CodeWF.EventBus:轻量级事件总线,让通信更流畅

1. CodeWF.EventBus EventBus(事件总线)&#xff0c;用于解耦模块之间的通讯。本库&#xff08;CodeWF.EventBus&#xff09;适用于进程内消息传递&#xff08;无其他外部依赖&#xff09;&#xff0c;与大家普遍使用的MediatR部分类似&#xff0c;但MediatR库侧重于ASP.NET C…

Dish-TS: 缓解分布转移的一般范例 时间序列预测

摘要 时间序列预测(TSF)中的分布移位(即序列分布随时间的变化)在很大程度上阻碍了TSF模型的性能。现有的关于时间序列中分布变化的研究大多局限于分布的量化&#xff0c;更重要的是&#xff0c;忽视了回望窗和地平线窗之间的潜在变化。为了应对上述挑战&#xff0c;我们系统地…

网络编程之XDP技术应用

一、AF_XDP介绍 在上文中介绍了XDP技术&#xff0c;XDP技术的基本原理已经明白&#xff0c;但有一个问题&#xff0c;一个技术如何落地&#xff0c;如何在实际中应用&#xff1f;这就需要有一个承载其的具体的形式。举一个例子&#xff0c;网络编程一般使用Socket方式&#xf…

VM-Import 导入 Debian 12 系统

介绍 之前介绍过使用 VM-Import 导入 Windows 系统到 AWS 环境启动 EC2 实例, 本文将介绍如何导入 Debian 12 系统. 本地虚拟化使用 VMWare Workstation 创建虚拟机安装和准备 Debian 12 系统, 导出 OVA 文件后上传到 S3 存储桶中再使用 AWSCLI 执行 VM-Import 命令实现导入过…

【Vue】获取模块内的state数据

目标&#xff1a; 掌握模块中 state 的访问语法 尽管已经分模块了&#xff0c;但其实子模块的状态&#xff0c;还是会挂到根级别的 state 中&#xff0c;属性名就是模块名 使用模块中的数据 直接通过模块名访问 $store.state.模块名.xxx 通过 mapState 映射&#xff1a; 默认…

mac免费的ntfs软件哪个好 MAC读取NTFS硬盘格式

对于苹果用户来说&#xff0c;Mac电脑和移动硬盘已经成为日常工作中不可缺少的一部分&#xff0c;但有时我发现Mac打开移动硬盘只能读取无法写入&#xff0c;这是由于所连接的移动硬盘为NTFS格式。我们可以通过对硬盘格式化为Mac正常读写格式&#xff0c;或使用数据读写软件对N…

DNS协议 | NAT技术 | 代理服务器

目录 一、DNS协议 1、DNS背景 2、DNS协议 域名 域名解析 二、NAT技术 1、NAT技术 2、NAPT技术 3、NAT技术的缺陷 三、代理服务器 1、正向代理服务器 2、反向代理服务器 一、DNS协议 域名系统&#xff08;Domain Name System&#xff0c;缩写&#xff1a;DNS&#…

Zabbix配置中文显示及乱码问题

页面配置为中文显示 在zabbix 5.0版本开始用户菜单更改为左侧栏显示&#xff0c;找到并点击 User Settings&#xff0c;Language 修改语言为 Chinese (zh_CN) 即可。 PS&#xff1a;一般在部署后初始配置时&#xff0c;未找到 Chinese (zh_CN) 这一项&#xff0c;修改如下&…

深度学习中embedding层的理解

Embedding层作用 在深度学习领域中&#xff0c;Embedding层扮演着至关重要的角色&#xff0c;尤其在处理文本数据或类别数据。Embedding层的功能有两个&#xff1a; 1. 将高维稀疏的输入数据&#xff08;如单词、类别标签等&#xff09;转换为低维稠密的向量表示&#xff0c;…

数 据 类 型

概述 Java 是强类型语言。 每一种数据都定义了明确的数据类型&#xff0c;在内存中分配了不同大小的内存空间&#xff08;字节&#xff09;。 Java 中一共有 8 种基本类型&#xff08;primitive type&#xff09;&#xff0c;包括 4 种整型、2 种浮点型、1 种字符类型&#…

Vulnhub靶机之reven 1

一、信息收集 nmap扫描网段&#xff0c;靶机地址为192.168.145.129。 nmap -sP 192.168.145.* 扫一下端口&#xff0c;开放了22、80、111、50967。 nmap -sT -T4 -p1-65535 192.168.145.129 再看一下目录情况&#xff0c;发现一个疑似后台登录的地址。 dirsearch -u http://…

【C++修行之道】类和对象(六)再谈构造函数(初始化列表)| explicit关键字 | static成员 | 友元|匿名对象|拷贝时一些编译器优化

目录 一、再谈构造函数 1.1 构造函数体赋值 1.2 初始化列表 1. 所有的成员,既可以在初始化列表初始化,也可以在函数体内初始化 2. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次) 3. 类中包含以下成员&#xff0c;必须放在初始化列表位置进行初始化&…

N32G45XVL-STB之移植LVGL(lvgl-8.2.0)

目录 概述 1 软硬件介绍 1.1 软件版本信息 1.2 ST7796-LCD 1.3 MCU IO与LCD PIN对应关系 2 认识LVGL 2.1 LVGL官网 2.2 LVGL库文件下载 3 移植LVGL 3.1 准备移植文件 3.2 添加lvgl库文件到项目 3.2.1 src下的文件 3.2.2 examples下的文件 3.2.3 配置文件路径 3.2…

VS2019专业版 C#和MFC安装

1. VS2019专业版下载地址 https://learn.microsoft.com/en-us/visualstudio/releases/2019/history 2.安装 C# 部分 MFC部分

【Linux】进程6——环境变量

1.什么是环境变量 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 比如&#xff1a;我们在编写C/C代码的时候&#xff0c;在链接的时候&#xff0c;从来不知道我们的所链接的动态静态库在哪里&#xff0c;但是照样可以链接成功&…

[知识点] 内存顺序属性的用途和行为

C标准库中定义了以下几种内存顺序属性&#xff1a; std::memory_order_relaxedstd::memory_order_consumestd::memory_order_acquirestd::memory_order_releasestd::memory_order_acq_relstd::memory_order_seq_cst 1. std::memory_order_relaxed 定义&#xff1a;不提供同步…

通过 Python+Nacos实现微服务,细解微服务架构

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 背景 一直以来的想法比较多&#xff0c;然后就用Python编写各种代码脚本。很多…