首页 > 数据库 >MySQL架构总览_查询执行流程_SQL解析顺序

MySQL架构总览_查询执行流程_SQL解析顺序

时间:2025-01-21 10:22:56浏览次数:1  
标签:mike aaa uid SQL bbb SELECT MySQL 总览 ccc

目录

MySQL 架构总览

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

查询执行流程

连接

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

处理

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

结果

  1. Query 请求完成后,将结果集返回给‘连接进/线程模块’
  2. 返回的也可以是相应的状态标识,如成功或失败等
  3. ‘连接进/线程模块’进行后续的清理工作,并继续等待请求或断开与客户端的连接
    一图小总结
    [701942-20151210224221011-1559007674.png]

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
  1. 创建测试表
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;
  1. 插入数据
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);
  1. 最后想要的结果
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;

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' 的解释图,如若侵犯了你的权益,请劳烦告知删除,谢谢。
[Pasted image 20241121154056.png]

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)

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)

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)

SELECT

这个子句对 SELECT 子句中的元素进行处理,生成 VT5 表。
(5-J1)SELECT
(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)

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)

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)

总结

[Pasted image 20241121154437.png]

参考书籍

《MySQL性能调优与架构实践》
《MySQL技术内幕:SQL编程》

标签:mike,aaa,uid,SQL,bbb,SELECT,MySQL,总览,ccc
From: https://www.cnblogs.com/TMesh/p/18683075

相关文章

  • SQL Server 2005部署备份任务.120308
    环境:SQLServer2005任务:1,每日凌晨1点给本地sqlserver做本地完整备份,且只保留7天的本地备份;2,每日凌晨5点将本地的备份打包上传到存储服务器。思路:1,sqlserver2005不支持SQLServer2000的sqlmaint命令,所以,需要通过数据库维护计划进行备份,而不能一步成型的写bat脚本;2,备份地址......
  • SQLServer2005恢复Master库.110509
    master库对于SQLServer来说,是很重要的系统数据库,保存着所有Sqlserver的用户信息、数据库信息等,当数据库崩溃时,master数据库的恢复成功与否起着重要的作用。这就跟Oracle的System表空间一样,非常的重要。备份数据前期准备:(1)在备用机准备好和生产机器一样的sql2005数据库环境(注意数......
  • MySQL数据库开启远程访问权限
    1、背景描述默认情况下,MySQL只允许本地登录,即只能在安装MySQL数据库所在的主机环境中访问。在实际开发和使用中,一般需要访问远程服务器的数据库,此时就需要开启服务器端MySQL的远程访问权限。2、查看MySQL的用户表如上图所示,Host列指定了允许用户登录所使用的IP,比如u......
  • PL/SQL 删除外键 ORA-02443: 无法删除约束条件-不存在的约束条件
    在PL/SQL中删除外键,无论是在【对象】窗口可视化操作删除还是用drop语句都会报错:ORA-02443:无法删除约束条件-不存在的约束条件看到有人有同样的问题。亲测之后:情况一:常规操作ALTERTABLEtable_nameDROPCONSTRAINTforeignkeyname;情况二:需要加引号(而且是双引号)ALTERTABL......
  • MySQL 中单独获取已知日期的年月日
    在MySQL中,处理日期和时间是一项常见任务。通常,我们需要从已知的日期中提取年、月、日等部分信息。MySQL提供了一些内置函数,可以方便地进行这些操作。本文将详细介绍如何在MySQL中单独获取已知日期的年、月、日部分。一、提取年份(Year)要从日期中提取年份,可以使用 YEAR() 函数......
  • mysql 获取当前时间戳13
    mysql获取当前时间戳13在MySQL中,您可以使用CURRENT_TIMESTAMP或NOW()函数来获取当前的时间戳。这将以'YYYY-MM-DDHH:MM:SS'格式返回当前的日期和时间。如果您需要的是一个UNIX时间戳(即自1970年1月1日以来的秒数),您可以使用UNIX_TIMESTAMP()函数。以下是获取当前UN......
  • sql server 获取当前时间戳
     sqlserver获取当前时间戳获取当前时间戳有以下几种方法实现:使用GETDATE()函数:  SELECTGETDATE()ASCurrentTimestamp;此方法返回当前日期和时间的完整时间戳。使用SYSDATETIME()函数:  SELECTSYSDATETIME()ASCurrentTimesta......
  • MySQL Switch Case
    MySQLSwitchCase123 在MySQL中,CASE语句用于根据条件返回值。当第一个条件满足时,它就会停止读取并返回结果。如果没有条件为真,则返回ELSE子句中的值。如果没有ELSE部分且没有条件为真,则返回NULL1。示例SELECT OrderID,Quantity,CASEWHEN Quantity>30 ......
  • 【数据库】详解MySQL数据库索引
    目录1.介绍2.索引概述2.1.优缺点3.索引结构3.1.B+Tree索引3.2.Hash索引4.索引分类5.索引语法5.1.创建索引5.2.查看索引5.3.删除索引6.SQL性能分析6.1.慢查询日志6.2.profile详情6.3.explain执行计划7.索引使用7.1索引使用原则7.1.1.最左前缀法则7.1.2.索引......
  • Mysql的学习
    Mysql建立索引优化:sql优化:为了解决下面的索引失效问题序列索引优化:解决orderby的关键在于提前在索引中就给好排序解决limit优化:利用id的子查询解决了回表查询然后提升了效能。关于count()的优化:`关于COUNT()的效率问题:COUNT()是用来统计记录数量的函数。不同写法的......