MySQL--SQL解析顺序

news2024/12/24 22:03:12

前言:

一直是想知道一条SQL语句是怎么被执行的,它执行的顺序是怎样的,然后查看总结各方资料,就有了下面这一篇博文了。
本文将从MySQL总体架构—>查询执行流程—>语句执行顺序来探讨一下其中的知识。

一、MySQL架构总览:

架构最好看图,再配上必要的说明文字。
下图根据参考书籍中一图为原本,再在其上添加上了自己的理解。
在这里插入图片描述
  从上图中我们可以看到,整个架构分为两层,上层是MySQLD的被称为的‘SQL Layer’,下层是各种各样对上提供接口的存储引擎,被称为‘Storage Engine Layer’。其它各个模块和组件,从名字上就可以简单了解到它们的作用,这里就不再累述了。

二、查询执行流程

下面再向前走一些,容我根据自己的认识说一下查询执行的流程是怎样的:

1.连接

1.1 客户端发起一条Query请求,监听客户端的‘连接管理模块’接收请求
1.2 将请求转发到‘连接进/线程模块’
1.3 调用‘用户模块’来进行授权检查
1.4 通过检查后,‘连接进/线程模块’从‘线程连接池’中取出空闲的被缓存的连接线程和客户端请求对接,如果失败则创建一个新的连接请求

2.处理

2.1 先查询缓存,检查Query语句是否完全匹配,接着再检查是否具有权限,都成功则直接取数据返回
2.2 上一步有失败则转交给‘命令解析器’,经过词法分析,语法分析后生成解析树
2.3 接下来是预处理阶段,处理解析器无法解决的语义,检查权限等,生成新的解析树
2.4 再转交给对应的模块处理
2.5 如果是SELECT查询还会经由‘查询优化器’做大量的优化,生成执行计划
2.6 模块收到请求后,通过‘访问控制模块’检查所连接的用户是否有访问目标表和目标字段的权限
2.7 有则调用‘表管理模块’,先是查看table cache中是否存在,有则直接对应的表和获取锁,否则重新打开表文件
2.8 根据表的meta数据,获取表的存储引擎类型等信息,通过接口调用对应的存储引擎处理
2.9 上述过程中产生数据变化的时候,若打开日志功能,则会记录到相应二进制日志文件中

3.结果

3.1Query请求完成后,将结果集返回给‘连接进/线程模块’
  3.2返回的也可以是相应的状态标识,如成功或失败等
  3.3‘连接进/线程模块’进行后续的清理工作,并继续等待请求或断开与客户端的连接

一图小总结
在这里插入图片描述

三、SQL解析顺序

接下来再走一步,让我们看看一条SQL语句的前世今生。
首先看一下示例语句

SELECT DISTINCT
    < select_list >
FROM
    < left_table > < join_type >
JOIN < right_table > ON < join_condition >
WHERE
    < where_condition >
GROUP BY
    < group_by_list >
HAVING
    < having_condition >
ORDER BY
    < order_by_condition >
LIMIT < limit_number >

然而它的执行顺序是这样的

FROM <left_table>
ON <join_condition>
<join_type> JOIN <right_table>
WHERE <where_condition>
GROUP BY <group_by_list>
HAVING <having_condition>
SELECT
DISTINCT <select_list>
ORDER BY <order_by_condition>
LIMIT <limit_number>

虽然自己没想到是这样的,不过一看还是很自然和谐的,从哪里获取,不断的过滤条件,要选择一样或不一样的,排好序,那才知道要取前几条呢。
既然如此了,那就让我们一步步来看看其中的细节吧。

准备工作
1.创建测试数据库

create database testQuery

2.创建测试表

CREATE TABLE table1
(
    uid VARCHAR(10) NOT NULL,
    name VARCHAR(10) NOT NULL,
    PRIMARY KEY(uid)
)ENGINE=INNODB DEFAULT CHARSET=UTF8;

CREATE TABLE table2
(
    oid INT NOT NULL auto_increment,
    uid VARCHAR(10),
    PRIMARY KEY(oid)
)ENGINE=INNODB DEFAULT CHARSET=UTF8;

3.插入数据

INSERT INTO table1(uid,name) VALUES('aaa','mike'),('bbb','jack'),('ccc','mike'),('ddd','mike');

INSERT INTO table2(uid) VALUES('aaa'),('aaa'),('bbb'),('bbb'),('bbb'),('ccc'),(NULL);

4.最后想要的结果

SELECT
    a.uid,
    count(b.oid) AS total
FROM
    table1 AS a
LEFT JOIN table2 AS b ON a.uid = b.uid
WHERE
    a. NAME = 'mike'
GROUP BY
    a.uid
HAVING
    count(b.oid) < 2
ORDER BY
    total DESC
LIMIT 1;

开始SQL解析之旅吧!

1. FROM

当涉及多个表的时候,左边表的输出会作为右边表的输入,之后会生成一个虚拟表VT1。

(1-J1)笛卡尔积

计算两个相关联表的笛卡尔积(CROSS JOIN) ,生成虚拟表VT1-J1。

mysql> select * from table1,table2;
+-----+------+-----+------+
| uid | name | oid | uid  |
+-----+------+-----+------+
| aaa | mike |   1 | aaa  |
| bbb | jack |   1 | aaa  |
| ccc | mike |   1 | aaa  |
| ddd | mike |   1 | aaa  |
| aaa | mike |   2 | aaa  |
| bbb | jack |   2 | aaa  |
| ccc | mike |   2 | aaa  |
| ddd | mike |   2 | aaa  |
| aaa | mike |   3 | bbb  |
| bbb | jack |   3 | bbb  |
| ccc | mike |   3 | bbb  |
| ddd | mike |   3 | bbb  |
| aaa | mike |   4 | bbb  |
| bbb | jack |   4 | bbb  |
| ccc | mike |   4 | bbb  |
| ddd | mike |   4 | bbb  |
| aaa | mike |   5 | bbb  |
| bbb | jack |   5 | bbb  |
| ccc | mike |   5 | bbb  |
| ddd | mike |   5 | bbb  |
| aaa | mike |   6 | ccc  |
| bbb | jack |   6 | ccc  |
| ccc | mike |   6 | ccc  |
| ddd | mike |   6 | ccc  |
| aaa | mike |   7 | NULL |
| bbb | jack |   7 | NULL |
| ccc | mike |   7 | NULL |
| ddd | mike |   7 | NULL |
+-----+------+-----+------+
28 rows in set (0.00 sec)
(1-J2)ON过滤

基于虚拟表VT1-J1这一个虚拟表进行过滤,过滤出所有满足ON 谓词条件的列,生成虚拟表VT1-J2。
注意:这里因为语法限制,使用了’WHERE’代替,从中读者也可以感受到两者之间微妙的关系;

mysql> SELECT
    -> *
    -> FROM
    -> table1,
    -> table2
    -> WHERE
    -> table1.uid = table2.uid
    -> ;
+-----+------+-----+------+
| uid | name | oid | uid  |
+-----+------+-----+------+
| aaa | mike |   1 | aaa  |
| aaa | mike |   2 | aaa  |
| bbb | jack |   3 | bbb  |
| bbb | jack |   4 | bbb  |
| bbb | jack |   5 | bbb  |
| ccc | mike |   6 | ccc  |
+-----+------+-----+------+
6 rows in set (0.00 sec)
(1-J3)添加外部列

如果使用了外连接(LEFT,RIGHT,FULL),主表(保留表)中的不符合ON条件的列也会被加入到VT1-J2中,作为外部行,生成虚拟表VT1-J3。

mysql> SELECT
    -> *
    -> FROM
    -> table1 AS a
    -> LEFT OUTER JOIN table2 AS b ON a.uid = b.uid;
+-----+------+------+------+
| uid | name | oid  | uid  |
+-----+------+------+------+
| aaa | mike |    1 | aaa  |
| aaa | mike |    2 | aaa  |
| bbb | jack |    3 | bbb  |
| bbb | jack |    4 | bbb  |
| bbb | jack |    5 | bbb  |
| ccc | mike |    6 | ccc  |
| ddd | mike | NULL | NULL |
+-----+------+------+------+
7 rows in set (0.00 sec)

下面从网上找到一张很形象的关于‘SQL JOINS’的解释图:
在这里插入图片描述

2. WHERE

对VT1过程中生成的临时表进行过滤,满足WHERE子句的列被插入到VT2表中。
注意:
此时因为分组,不能使用聚合运算;也不能使用SELECT中创建的别名;
与ON的区别:
如果有外部列,ON针对过滤的是关联表,主表(保留表)会返回所有的列;
如果没有添加外部列,两者的效果是一样的;
应用:
对主表的过滤应该放在WHERE;
对于关联表,先条件查询后连接则用ON,先连接后条件查询则用WHERE;

mysql> SELECT
    -> *
    -> FROM
    -> table1 AS a
    -> LEFT OUTER JOIN table2 AS b ON a.uid = b.uid
    -> WHERE
    -> a. NAME = 'mike';
+-----+------+------+------+
| uid | name | oid  | uid  |
+-----+------+------+------+
| aaa | mike |    1 | aaa  |
| aaa | mike |    2 | aaa  |
| ccc | mike |    6 | ccc  |
| ddd | mike | NULL | NULL |
+-----+------+------+------+
4 rows in set (0.00 sec)
3. GROUP BY

这个子句会把VT2中生成的表按照GROUP BY中的列进行分组。生成VT3表。
注意:
其后处理过程的语句,如SELECT,HAVING,所用到的列必须包含在GROUP BY中,对于没有出现的,得用聚合函数;
原因:
GROUP BY改变了对表的引用,将其转换为新的引用方式,能够对其进行下一级逻辑操作的列会减少;
我的理解是:
根据分组字段,将具有相同分组字段的记录归并成一条记录,因为每一个分组只能返回一条记录,除非是被过滤掉了,而不在分组字段里面的字段可能会有多个值,多个值是无法放进一条记录的,所以必须通过聚合函数将这些具有多值的列转换成单值;

mysql> SELECT
    -> *
    -> FROM
    -> table1 AS a
    -> LEFT OUTER JOIN table2 AS b ON a.uid = b.uid
    -> WHERE
    -> a. NAME = 'mike'
    -> GROUP BY
    -> a.uid;
+-----+------+------+------+
| uid | name | oid  | uid  |
+-----+------+------+------+
| aaa | mike |    1 | aaa  |
| ccc | mike |    6 | ccc  |
| ddd | mike | NULL | NULL |
+-----+------+------+------+
3 rows in set (0.00 sec)
4. HAVING

这个子句对VT3表中的不同的组进行过滤,只作用于分组后的数据,满足HAVING条件的子句被加入到VT4表中。

mysql> SELECT
    -> *
    -> FROM
    -> table1 AS a
    -> LEFT OUTER JOIN table2 AS b ON a.uid = b.uid
    -> WHERE
    -> a. NAME = 'mike'
    -> GROUP BY
    -> a.uid
    -> HAVING
    -> count(b.oid) < 2;
+-----+------+------+------+
| uid | name | oid  | uid  |
+-----+------+------+------+
| ccc | mike |    6 | ccc  |
| ddd | mike | NULL | NULL |
+-----+------+------+------+
2 rows in set (0.00 sec)
5. SELECT

这个子句对SELECT子句中的元素进行处理,生成VT5表。
(5-J1)计算表达式 计算SELECT 子句中的表达式,生成VT5-J1
(5-J2)DISTINCT
寻找VT5-1中的重复列,并删掉,生成VT5-J2
如果在查询中指定了DISTINCT子句,则会创建一张内存临时表(如果内存放不下,就需要存放在硬盘了)。这张临时表的表结构和上一步产生的虚拟表VT5是一样的,不同的是对进行DISTINCT操作的列增加了一个唯一索引,以此来除重复数据。

mysql> SELECT
    -> a.uid,
    -> count(b.oid) AS total
    -> FROM
    -> table1 AS a
    -> LEFT OUTER JOIN table2 AS b ON a.uid = b.uid
    -> WHERE
    -> a. NAME = 'mike'
    -> GROUP BY
    -> a.uid
    -> HAVING
    -> count(b.oid) < 2;
+-----+-------+
| uid | total |
+-----+-------+
| ccc |     1 |
| ddd |     0 |
+-----+-------+
2 rows in set (0.00 sec)
6.ORDER BY

从VT5-J2中的表中,根据ORDER BY 子句的条件对结果进行排序,生成VT6表。
注意:
唯一可使用SELECT中别名的地方;

mysql> SELECT
    -> a.uid,
    -> count(b.oid) AS total
    -> FROM
    -> table1 AS a
    -> LEFT OUTER JOIN table2 AS b ON a.uid = b.uid
    -> WHERE
    -> a. NAME = 'mike'
    -> GROUP BY
    -> a.uid
    -> HAVING
    -> count(b.oid) < 2
    -> ORDER BY
    -> total DESC;
+-----+-------+
| uid | total |
+-----+-------+
| ccc |     1 |
| ddd |     0 |
+-----+-------+
2 rows in set (0.00 sec)
7.LIMIT

LIMIT子句从上一步得到的VT6虚拟表中选出从指定位置开始的指定行数据。
注意:
offset和rows的正负带来的影响;
当偏移量很大时效率是很低的,可以这么做:
采用子查询的方式优化,在子查询里先从索引获取到最大id,然后倒序排,再取N行结果集
采用INNER JOIN优化,JOIN子句里也优先从索引获取ID列表,然后直接关联查询获得最终结果

mysql> SELECT
    -> a.uid,
    -> count(b.oid) AS total
    -> FROM
    -> table1 AS a
    -> LEFT JOIN table2 AS b ON a.uid = b.uid
    -> WHERE
    -> a. NAME = 'mike'
    -> GROUP BY
    -> a.uid
    -> HAVING
    -> count(b.oid) < 2
    -> ORDER BY
    -> total DESC
    -> LIMIT 1;
+-----+-------+
| uid | total |
+-----+-------+
| ccc |     1 |
+-----+-------+
1 row in set (0.00 sec)

至此SQL的解析之旅就结束了,上图总结一下:
在这里插入图片描述

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

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

相关文章

算法模板 6.并查集

并查集 用于解决连通块问题。 判断集合个数也就是判断p[x] x 的个数 836. 合并集合 - AcWing题库 #include <bits/stdc.h> using namespace std; const int N 1e5 10; int p[N]; // 记录每个元素的father int n, m;int find(int x){ // 查询元素x的fatherif(p[x] …

GPIO控制和命名规则

Linux提供了GPIO子系统驱动框架&#xff0c;使用该驱动框架即可灵活地控制板子上的GPIO。 GPIO命名 泰山派开发板板载了一个40PIN 2.54间距的贴片排针&#xff0c;排针的引脚定义兼容经典40PIN接口。 在后续对GPIO进行操作前&#xff0c;我们需要先了解k3566的GPIO命名规则&a…

Windows 重启 explorer 的正确做法

目录 一、关于 Restart Manager 二、重启管理器实例 三、完整实现代码和测试 本文属于原创文章&#xff0c;转载请注明出处&#xff1a; https://blog.csdn.net/qq_59075481/article/details/136179191。 我们往往使用 TerminateProcess 并传入 PID 和特殊结束代码 1 或者…

rocketMQ-Dashboard安装与部署

1、下载最新版本rocketMQ-Dashboard 下载地址&#xff1a;https://github.com/apache/rocketmq-dashboard 2、下载后解压&#xff0c;并用idea打开 3、修改配置 ①、修改端口及rocketmq服务的ip&#xff1a;port ②、修改访问账号、密码 3、然后启动访问&#xff1a; 4、mav…

单片机01天_stm32f407zg_创建新工程

创建“寄存器版工程” 1、创建工程文件夹 创建工程文件夹“Project”&#xff0c;内部包含文件夹“CMSIS”&&“USER”。 CMSIS&#xff1a;Cortex系列内核接口驱动文件。 USER&#xff1a;存放用户编写的程序文件。 “USER”文件夹内包含“Inc”&&“Src”…

yolov5的Mosaic原理解析

众所周知&#xff0c;yolov5中使用了mosaic增强进行数据增强&#xff0c;效果就是将4张图片拼凑为1张图片。为了更好优化自定义任务&#xff0c;特对mosaic原理进行解析。 1、mosaic原理解析 mosaic增强的原理一张图就可以解释&#xff1a; 1.1 图的注释 首先高亮区域&am…

YOLOv8-Openvino和ONNXRuntime推理【CPU】

1 环境&#xff1a; CPU&#xff1a;i5-12500 2 安装Openvino和ONNXRuntime 2.1 Openvino简介 Openvino是由Intel开发的专门用于优化和部署人工智能推理的半开源的工具包&#xff0c;主要用于对深度推理做优化。 Openvino内部集成了Opencv、TensorFlow模块&#xff0c;除此…

ClickHouse 基础(一)

官网 以毫秒为单位查询数十亿行 ClickHouse是用于实时应用和分析的最快、资源效率最高的开源数据库。 安装ClickHouse 使用ClickHouse&#xff0c;你有三个选择: ClickHouse云:官方ClickHouse作为一项服务&#xff0c;-由ClickHouse的创建者构建&#xff0c;维护和支持快速安…

C语言中关于#include的一些小知识

写代码的过程中&#xff0c;因为手误&#xff0c;重复包含了头文件 可以看到没有报错 如果是你自己编写的头文件&#xff0c;那么如果没加唯一包含标识的话&#xff0c;那么编译器会编译报错的。如果是系统自带的头文件&#xff0c;由于其每个头文件都加了特殊标识&#xff0c…

【软考】系统集成项目管理工程师(十六)变更管理【1分】

一、 变更的概念 1、定义、原因、分类 2、变更流程 二、 变更的原则 1、变更管理原则、配置管理工具 2、变更管理流程 三、 变更的流程及角色职责 1、提出变更申请、变更影响分析 2、变更测试 1、有些变更很小&#xff0c;客户着急要&#xff0c;可以不用走变更程序直接修改…

Android widget基础指南

widget的概念最早是由一名叫Rose的苹果工程师提出&#xff0c;后来经过多方面机缘巧合的发展下&#xff0c;便有了今天Android平台上的小组件widget&#xff0c;一般APP开发可能应用场景较少&#xff0c;最常见的莫过于天气APP的widget。但对于从事IOT或车载方向的同学&#xf…

FL Studio2024年最新21.2破解中文版本下载地址

FL Studio 21的中文版本是一款非常受欢迎的音乐制作软件&#xff0c;它为用户提供了丰富的功能和工具&#xff0c;使他们能够轻松创作和编辑音乐。以下是一些关于FL Studio 21中文版本的主要特点和功能&#xff1a; FL Studio 21 Win-安装包下载如下: https://wm.makeding.co…

- 工程实践 - 《QPS百万级的有状态服务实践》03 - 消息队列

本文属于专栏《构建工业级QPS百万级服务》 继续上篇《QPS百万级的有状态服务实践》02 - 冷启动和热更新。我们的架构如图1。上一章在热更新部分&#xff0c;我们引入了消息队列。本章我们介绍下各个消息队列的优缺点&#xff0c;并选择其中一个说下核心概念和原理。 图1 目前市…

【Go语言】Go语言的数据类型

GO 语言的数据类型 Go 语言内置对以下这些基本数据类型的支持&#xff1a; 布尔类型&#xff1a;bool 整型&#xff1a;int8、byte、int16、int、uint、uintptr 等 浮点类型&#xff1a;float32、float64 复数类型&#xff1a;complex64、complex128 字符串&#xff1a;st…

嵌入式学习 Day21

一. 文件IO: 1. lseek off_t lseek(int fd, off_t offset, int whence); 功能: 重新设定文件描述符的偏移量 参数: fd:文件描述符 offset:偏移量 whence: SEEK_SET 文件开头 …

基于STM32F407的coreJSON使用教程

目录 概述 工程建立 代码集成 函数介绍 使用示例 概述 coreJSON是FreeRTOS中的一个组件库&#xff0c;支持key查找的解析器&#xff0c;他只是一个解析器&#xff0c;不能生成json数据。同时严格执行 ECMA-404 JSON 标准。该库用 C 语言编写&#xff0c;设计符合 ISO C90…

杨氏矩阵和杨辉三角

杨氏矩阵 有一个数字矩阵&#xff0c;矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c;请编写程序在这样的矩阵中查找某个数字是否存在。 要求&#xff1a;时间复杂度小于O(N); 分析 若要满足要求时间复杂度小于O(N)&#xff0c;就不能每一行一个个…

机器学习基础(一)理解机器学习的本质

导读&#xff1a;在本文中&#xff0c;将深入探索机器学习的根本原理&#xff0c;包括基本概念、分类及如何通过构建预测模型来应用这些理论。 目录 机器学习 机器学习概念 相关概念 机器学习根本&#xff1a;模型 数据的语言&#xff1a;特征与标签 训练与测试&#xf…

elementui 中 el-date-picker 控制选择当前年之前或者之后的年份

文章目录 需求分析 需求 对 el-date-picker控件做出判断控制 分析 给 el-date-picker 组件添加 picker-options 属性&#xff0c;并绑定对应数据 pickerOptions html <el-form-item label"雨量年份&#xff1a;" prop"date"><el-date-picker …

正整数A+B(PTA团体天题练习题)细节题刨析

哎呀&#xff0c;又是看似简单的AB模型&#xff0c;这题确实也是AB&#xff0c;不过这个题让我debug1个多小时才找出来问题所在&#xff0c;服了&#xff0c;真是所谓细节决定成败&#xff0c;这题也挺值得记录下来的&#xff0c;话不多嗦&#xff0c;看题 题的目标很简单&…