前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一条 SQL 查询语句是如何执行的?

一条 SQL 查询语句是如何执行的?

作者头像
王小明_HIT
发布于 2020-05-25 15:37:44
发布于 2020-05-25 15:37:44
83500
代码可运行
举报
文章被收录于专栏:程序员奇点程序员奇点
运行总次数:0
代码可运行

一条 SQL 查询语句是如何执行的?

比如,你有个最简单的表,表里只有一个ID字段,在执行下面这个查询语句时

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select from T where ID=10;

我们只看到一个输入语句,返回一个结果,却不知道这个 SQL 语句在 MySQL 内部的执行过程。

MySQL 基本架构示意图

MySQL 基本架构示意图

  • Server 层包括连接器,查询缓存,分析器,优化器,执行器等,涵盖的功能包括 MySQl 大多数核心服务,比如内置函数(日期,时间,数学和加密函数),跨存储引擎的Server 层实现。比如存储过程,触发器,视图等。
  • 存储引擎层,负责数据的存储和提取,架构是插件式的,支持 InnoDB,MyISAM 、Memory 等多个存储引擎。现在的最常用的引擎是 InnoDB ,他是 MySQL 5.5.5 版本开始后的默认引擎。

执行create table 建表的时候,如果不指定存储引擎类型,默认使用InnoDB ,不过,也可以通过指定存储引擎类型来选择其他的引擎 ,比如在 Create table 预警中使用 engine = memory 。

连接器

第一步,你会先连接到这个数据库上,这时候接待你的就是连接器。连接器负责跟客户端建立连接、获取权限、维持和管理连接。连接命令一般是这么写的:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql -h$ip -P$port -u$user -p

输入完命令之后,需要在交互对话页面输入密码, 也可以直接在-p 之后写密码。

连接命令中的 mysql 是客户端工具,用来跟服务器建立连接,完成经典的 TCP,握手之后,连接器要开始认证身份,这个时候需要输入用户名和密码。

  • 如果用户名或密码不对,你就会收到一个" Access denied for user"的错误,然后客户端程序结束执行。
  • 如果用户名密码认证通过,连接器会到权限表里面查出你拥有的权限。之后,这个连接里面的权限判断逻辑,都将依赖于此时读到的权限。

一个用户连接后,即使你试用管理员账号对这个用户的权限做修改,也不会影响已经存在的连接的权限,修改完成后,只有再新建的连接才会使用新的权限设置。

连接完成之后,如果没有后续动作,这个连接就处于空闲状态,可以通过 show processlit 命令看到:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MariaDB [mysql]> show  processlist
    -> ;
+-----------+------+-----------+-------+---------+------+-------+-------------------+----------+
| Id        | User | Host      | db    | Command | Time | State | Info              | Progress |
+-----------+------+-----------+-------+---------+------+-------+-------------------+----------+
| 198609891 | root | localhost | mysql | Query   |    0 | NULL  | show  processlist |    0.000 |
| 198613616 | root | n1:36946  | mysql | Sleep   |    3 |       | NULL              |    0.000 |
| 198613622 | root | n1:36956  | mysql | Sleep   |    2 |       | NULL              |    0.000 |
| 198613627 | root | n1:36966  | mysql | Sleep   |    2 |       | NULL              |    0.000 |
| 198613631 | root | n1:36974  | mysql | Sleep   |    1 |       | NULL              |    0.000 |
| 198613632 | root | n1:36976  | mysql | Sleep   |    1 |       | NULL              |    0.000 |
| 198613635 | root | n1:36982  | mysql | Sleep   |    0 |       | NULL              |    0.000 |
| 198613636 | root | n1:36984  | mysql | Sleep   |    0 |       | NULL              |    0.000 |
| 198613639 | root | n1:36990  | mysql | Sleep   |    0 |       | NULL              |    0.000 |
+-----------+------+-----------+-------+---------+------+-------+-------------------+----------+
9 rows in set (0.01 sec)

其中的 Commond 列显示为 Sleep 的这一行,就表现在系统文件里有一个空闲连接。客户端如果长时间没动静,这个时间 参数 wait_timeout 控制,默认是 8 小时。如果连接被断开,客户端再次发送请求的话,那么就会收到一个错误提箱:Lost connection to MySQL server during query ,这个时候如果你要继续,就需要重连,然后再执行请求。

数据库里面,长连接时值连接成功后,如果客户端持续有请求,则使用同一个连接,短连接时每次执行很少的几次查询就断开连接,下次查询再重新建立一个。建立连接的过程通常比较复杂,所以通常建议减少建立连接的动账,也就是尽量使用长连接。

但是全部使用长连接后,可能会发现,有些时候 MySQL 占用内存涨的十分的快,因为 MySQL 在执行过程中使用的是内存管理连接对象,这些资源会在连接断开之后才释放,所以长连接累计下来,可能导致内存占用太大,被系统强行杀掉 (OOM),现象上可以看到 MySQL 异常重启了。

怎么解决 MySQL 长连接占用内存过大的问题?

  1. 定期断开长连接。使用一段时间,或者程序里面判断执行过一个占用内存的大查询后,断开连接,之后要查询再重连。
  2. 如果你用的是MSQL5.7或更新版本,可以在每次执行一个比较大的操作后,通过执行mysql _ reset_connection来重新初始化连接资源。这个过程不需要重连和重新做权限验证,但是会将连接恢复到刚刚创建完时的状态。

查询缓存

连接建立完成后,就可以执行 Select 语句了,执行逻辑就会进行到第二步:查询缓存。

MySQL 拿到一个请求后,先查询缓存看看,看是不是之前执行过这条语句,之前执行过的语句与结果,可能会以 key-value 形式,被直接缓存在内存中,key 是查询的语句, value 是查询的结果,如果你的查询语句能够直接在这个缓存中找到 key,那么这个 value 就会直接返回给客户端。

如果语句在查询缓存中不存在,就会继续后面的执行操作,执行完成后,执行结果会被写入缓存中,如果查询命中缓存,MSQL不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。

为什么大多数情况下不建议查询缓存?

因为查询缓存往往弊大于利。

查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。 因此很可能你费劲地把结果存起来,还没使用呢,就被一个更新全清空了。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。比如,一个系统配置表,那这张表上的查询才适合使用查询缓存。

好在MySQL 也提供了这种“按需使用”的方式。你可以将参数 query_cache_type 设置成DEMAND,这样对于默认的SQL语句都不使用查询缓存。而对于你确定要使用査询缓存的语句,可以用 SQL_CACHE显式指定,像下面这个语句一样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select SQL_CACHE * from T where ID=10

query_cache_type 参数设置如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
query_cache_type三个参数的含义:
query_cache_type=0OFF)关闭
query_cache_type=1ON)缓存所有结果,除非select语句使用SQL_NO_CACHE禁用查询缓存
query_cache_type=2(DEMAND),只缓存select语句中通过SQL_CACHE指定需要缓存的查询
修改为DEMAND方式:
vi /etc/my.cnf,加入如下行:
query_cache_type =2
保存并重启mysql

需要注意的是,MySQL8.0版本直接将査询缓存的整块功能删掉了,也就是说8.0开始彻底没有这个功能了。

分析器

如果没有命中査询缓存,就要开始真正执行语句了。首先,MySQL需要知道你要做什么,因此需要对SQL语句做解析。

词法分析

分析器先会做 “词法分析”。你输入的是由多个字符串和空格组成的一条SQL语句,MySQL需要识别出里面的字符串分别是什么,代表什么,MySQL从你输入的" select"这个关键字识别出来,这是一个查询语句。它也要把字符串“T识别成“表名T,把字符串“ID”识别成“列ID”。

语法分析

做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个SQL语句是否满足MySQL语法。

如果你的语句不对,就会收到“ You have an eror in your SQL syntax'的错误提醒,比如下面这个语句 select少打了开头的字母“s”.

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MariaDB [mysql]> elect * from  user ;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'elect * from  user' at line 1

一般语法错误会提示第一个出现错误的位置,所以你要关注的是紧接“ use near"的内容,

优化器

经过了分析器,MySQL 知道要做什么了,那么在开始执行之前,还要进行优化器的处理,优化器在表里面有多个索引的时候,决定使用哪个索引;或者在一个语句多表关联的时候,决定各个表的连接顺序,比如下面语句是执行两个表的的 join:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select * from t1 join t2 using(ID) where t1.c=10 and t2.d=20
  • 可以先从t1 里面取出 C=10 的记录的ID值,再根据 ID 值关联到表 t2,再判断 t2 里面的 d 值是否等于 20
  • 可以先从表 t2 里面提取 d= 20的记录的ID值,再根据 ID 值关联t1 ,再判断 t1 里面 c 的值是否等于10

这两种执行方法的逻辑结果是一样的,但是执行的效率会有不同,而优化器的作用就是决定选择使用哪一种方案。优化器阶段完成后,这个语句的执行方案就确定下来了,然后进入执行器阶段。

执行器

MySQL通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。开始执行的时候,要先判断一下你对这个表T有没有执行查询的权限,如果没有,就会返回没有权限的错误,如下所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
mysql> select from T where ID=10;
ERROR 114242000):SELECT command denied to user 'b@'localhost for table 'T'

如果有权限,就打开表继续执行。打开表的时候,执行器就会根据表的引擎定义,去使用这个引擎提供的接口。比如我们这个例子中的表 T 中,ID 字段没有索引,那么执行器的流程如下:

  1. 调用InnoDB引擎接口取这个表的第一行,判断ID值是不是10,如果不是则跳过,如果是则将这行存在结果集中
  2. 调用引擎接口取“下一行”,重复相同的判断逻辑,直到取到这个表的最后一行
  3. 执行器将上述遍历过程中所有满足条件的行组成的记录集作为结果集返回给客户端。

对于有索引的表,执行的逻辑也差不多。第一次调用的是“取满足条件的第一行”这个接口,之后循环取“满足条件的下一行”这个接口,这些接口都是引擎中已经定义好的。你会在数据库的慢查询日志中看到一个 rows_examined的字段,表示这个语句执行过程中扫描了多少行。这个值就是在执行器每次调用引擎获取数据行的时候累加的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MariaDB [(none)]> show variables  like '%slow_query_log%';
+---------------------+-------------+
| Variable_name       | Value       |
+---------------------+-------------+
| slow_query_log      | OFF         |
| slow_query_log_file | n1-slow.log |
+---------------------+-------------+
2 rows in set (0.00 sec)

MariaDB [(none)]> set global slow_query_log=1;
Query OK, 0 rows affected (0.04 sec)

MariaDB [(none)]>  show variables like '%slow_query_log%';
+---------------------+-------------+
| Variable_name       | Value       |
+---------------------+-------------+
| slow_query_log      | ON          |
| slow_query_log_file | n1-slow.log |
+---------------------+-------------+
2 rows in set (0.00 sec)

那么开启了慢查询日志后,什么样的SQL才会记录到慢查询日志里面呢?这个是由参数long_query_time控制,默认情况下long_query_time的值为10秒,可以使用命令修改,也可以在my.cnf参数里面修改。关于运行时间正好等于long_query_time的情况,并不会被记录下来。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MariaDB [(none)]> select sleep(11);
+-----------+
| sleep(11) |
+-----------+
|         0 |
+-----------+
1 row in set (11.00 sec)

查看慢查询日志

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[root@n1 /]# more /var/lib/mysql/n1-slow.log
/usr/libexec/mysqld, Version: 5.5.60-MariaDB (MariaDB Server). started with:
Tcp port: 3306  Unix socket: /var/lib/mysql/mysql.sock
Time                 Id Command    Argument
# Time: 200518 10:46:23
# User@Host: root[root] @ localhost []
# Thread_id: 199009283  Schema:   QC_hit: No
# Query_time: 11.000291  Lock_time: 0.000000  Rows_sent: 1  Rows_examined: 0
SET timestamp=1589769983;
select sleep(11);

参数说明

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Query_time: duration
语句执行时间(以秒为单位)
Lock_time: duration
在几秒钟内获得锁定的时间
Rows_sent: N
发送到客户端的行数。
Rows_examined:
优化程序检查的行数
mysqldumpslow 慢查询日志分析工具
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#访问时间最长的10个sql语句
mysqldumpslow -t 10   /var/lib/mysql/n1-slow.log
#访问次数最多的10个sql语句
mysqldumpslow -s c -t 10  /var/lib/mysql/n1-slow.log
#访问记录集最多的10个sql
mysqldumpslow -s r -t 10  /var/lib/mysql/n1-slow.log

[root@n1 /]# mysqldumpslow

Reading mysql slow query log from /var/lib/mysql/n1-slow.log
Count: 1  Time=11.00s (11s)  Lock=0.00s (0s)  Rows_sent=1.0 (1), Rows_examined=0.0 (0), root[root]@localhost
  select sleep(N)

[root@n1 /]# more /var/lib/mysql/n1-slow.log
/usr/libexec/mysqld, Version: 5.5.60-MariaDB (MariaDB Server). started with:
Tcp port: 3306  Unix socket: /var/lib/mysql/mysql.sock
Time                 Id Command    Argument
# Time: 200518 10:46:23
# User@Host: root[root] @ localhost []
# Thread_id: 199009283  Schema:   QC_hit: No
# Query_time: 11.000291  Lock_time: 0.000000  Rows_sent: 1  Rows_examined: 0
SET timestamp=1589769983;
select sleep(11);
存储引擎对比

存储引擎对比

慢查询日志开启方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
slow_query_log = ON
log_queries_not_using_indexes = ON;
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 1
执行器的状态
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员奇点 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
我的第一个Electron应用
hello,好久不见,最近笔者花了几天时间入门Electron,然后做了一个非常简单的应用,本文就来给各位分享一下过程,Electron大佬请随意~
街角小林
2023/05/31
1.4K0
我的第一个Electron应用
Electron使用electron-updater自动更新
在项目的根目录新建dev-app-update.yml文件 这个文件是为了方便进行本地调试
明知山
2021/12/28
4.3K2
Electron使用electron-updater自动更新
初识 Electron
使用 JavaScript, HTML 和 CSS 构建的跨平台桌面应用,能够兼容 Mac, Windows 和 Linux 环境,我们所熟悉的 Skype, Gihub Desktop, Visual Studio Code, Atom 等等软件,都是通过 Electron 进行构建的。
Nian糕
2020/06/16
1.6K0
初识 Electron
使用electron实现百度网盘悬浮窗口功能!
没有使用electron内置的-webkit-app-region: drag 因为使用他那个有很多问题 比如事件无法使用 右键无法使用 以及不能使用手型等!
李昊天
2019/05/26
2.6K0
基于ElectronEgg&Python,从零开始打造一款免费的PDF桌面工具
昨天给大家介绍了 ElectronEgg 这款开源桌面应用开发框架。那么我们能不能搭配使用Python语言开发一套自己的工具箱呢!
Python兴趣圈
2023/12/12
8240
基于ElectronEgg&Python,从零开始打造一款免费的PDF桌面工具
electron+vue全家桶开发包含(心得,遇见的坑,解决办法等)
目前网上有好多关于electron相关的文章,但是本人在开发的时候发现,网上大部分文章可以说是千篇一律,没有真正的痛点解析啥的很无语 ,好多的问题都需要自己去找、去试,这无异于加大了开发成本与学习成本,所以本篇博客会从electron 的api 到 electron +vue 组合式开发到 打包 及开发过程中遇见的问题分门别类的进行说明, 当然在最后的文末我会将我写的 electron + vue全家桶的git开源项目附上,需要的话就去git 吧
全栈若城
2024/02/29
2.3K0
electron+vue全家桶开发包含(心得,遇见的坑,解决办法等)
Electron入门教程3 ——进程通信
在Electron中,进程通过开发人员定义的“通道”与ipcMain模块和ipcRenderer模块进行通信。这些通道是任意的(您可以任意命名它们)和双向的(您可以为两个模块使用相同的通道名称)。要从渲染进程向主进程发送单向IPC消息,可以再预渲染脚本preload.js里使用ipcRenderer发送API发送消息,然后在main.js里用ipcMain.on接收。你通常使用这个模式从你的web内容中调用一个主进程API。我们将通过创建一个简单的应用程序来演示这种模式,该应用程序可以通过编程方式更改窗口的标题。
害恶细君
2022/11/22
1.3K0
Electron入门教程3 ——进程通信
(2/3)Electron知识学习 · 基础篇
Electron中,与GUI相关的模块(如 dialog, menu 等)只存在于主进程,而不在渲染进程中
老张的哲学
2023/01/09
8520
(2/3)Electron知识学习 · 基础篇
如何用Vue开发Electron桌面程序? 这篇就够了!
Electron 是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目. 使用 JavaScript,HTML 和 CSS 构建跨平台的桌面应用程序
治电小白菜
2021/01/05
6.1K2
如何用Vue开发Electron桌面程序? 这篇就够了!
Electron 进程通信(IPC)装饰器应用
在 Electron 在实现渲染进程到主进程通信时,无论是单向通信还是双向通信都必须经过:编写处理函数(主进程)、注册事件监听(主进程)、暴露 API(预加载脚本)和执行 API(渲染进程)4 个步骤。其中除了编写处理函数和执行 API 两个与业务代码紧相关的步骤外,注册事件监听和暴露 API 均具有共性且需要重复编码的特点,这一特点又恰巧符合 AOP 切面编程中反射技术的应用场景,所以我选择使用 TypeScript 装饰器来实现这部分代码的优化。
前端小鑫同学
2024/04/29
2590
记录一次electron踩坑
vue引入electron的ipcRenderer等其它模块会出现这种情况 解决方案 自定义事件 使用渲染进程捕捉
代码哈士奇
2021/01/26
1.8K0
记录一次electron踩坑
Electron + Vue 从零开始打造一个本地播放器
女朋友工作是音频后期,平常会收集一些音频音乐,需要看音频的频谱波形,每次用au这种大型软件播放音乐看波形,很不方便,看到她这么辛苦,身为程序猿的我痛心疾首,于是,就有了这么一个小软件,软件涉及到的技术主要为electron,vue,node,波形的展示主要通过wavesurfer生成。
五月君
2020/12/30
1.5K0
electron窗口间通信
窗口A的渲染进程发消息给主进程 const { clipboard, ipcRenderer, remote } = require('electron'); ipcRenderer.send('imgUploadMain', { id: dom.id, siteId: this.siteId, url: dom.src }); 主进程收到
liulun
2019/07/02
1.9K0
Electron利用web技术开发桌面应用
简单来说,Electron就是可以让你用Javascript、HTML、CSS来编写运行于Windows、macOS、Linux系统之上的桌面应用的库。本文的目的是通过使用Electron开发一个完整但简单的小应用:记事本,来体验一下这个神器的开发过程。本文犹如Hello World一样的存在,是个入门级笔记,但如果你之前从未接触过Electron,而又对它有兴趣,某想信这会是一篇值得一看的入门教程。   PS:这篇文章是基于Windows的开发过程,未对macOS、Linux作测试。
javascript.shop
2019/09/04
2.3K0
Electron利用web技术开发桌面应用
Electron 快速入门,顺便聊聊 IPC 通信
前阵子将排课系统的一些功能,提供给 solar 编辑器使用,solar 是基于互动课件编辑器 Cocos ICE 进行二次定制和个性化开发的课件制作系统,其底层是 Cocos Creator。而 Cocos Creator 是基于 Electron 进行开发的,所以学习了一些关于 Electron IPC 通信的相关知识,在这里做一个总结。 文章的开始,先让我们来了解下 Electron 是什么。 1. 什么是 Electron? Electron 官网只有一句简单的话: 使用 JavaScript,HTM
用户1097444
2022/06/29
1.8K0
Electron 快速入门,顺便聊聊 IPC 通信
electron 仿制QQ登录界面
注意 不要使用内置的拖动 我们要自己实现! 在页面中加入以下代码就可以实现拖动了!
李昊天
2019/05/26
7.7K0
浅谈electron
Electron相当于一个浏览器的外壳,可以把网页程序嵌入到壳里面,可以运行在桌面上的一个程序,可以把网页打包成一个在桌面运行的程序,通俗来说就是软件,比如像QQ、优酷、网易音乐等等。功能的强大超出你的想象,可以构建跨平台桌面程序, 本身支持node.js,可以使用node.js的一些模块
小吕
2022/06/15
2.2K0
electron 构建跨平台桌面应用
昨日(2016.09.13)本文发表后,获得了一定的阅读和转发量,但经部分网友反馈和仔细审核后发现,在与 NW.js 对比的环节,言辞欠妥,且数据的真实性有待考究,特此将争议部分删除,同时借此诚挚地向 NW.js 的作者以及各位读者反馈表示感谢,期待更多深入的交流和分享,修订后的版本如下: Stack Overflow 联合创始人 Jeff Wood 曾说过,任何一个能用 JavaScript 编写的应用系统,最终都必将使用 JavaScript 实现。 简介 Electron 是一款可以通过 Web前端技
QQ音乐技术团队
2018/01/31
3.7K0
electron 构建跨平台桌面应用
使用React、Electron、Dva、Webpack、Node.js、Websocket快速构建跨平台应用
目前Electron在github上面的star量已经快要跟React-native一样多了 这里吐槽下,webpack感觉每周都在偷偷更新,很糟心啊,还有Angular更新到了8,Vue马上又要出正
Peter谭金杰
2019/08/02
3.2K0
使用React、Electron、Dva、Webpack、Node.js、Websocket快速构建跨平台应用
electron实现qq快捷登录!
之前本来想不写这个功能的,结果客户死活要qq登录! 实在没办法就写了,顺便写个文章! 在写之前有两个问题: 1: 打开qq授权页面点击页面中的链接会又打开一个页面! ..... 2: 授权之后是否成功很难去判断
李昊天
2019/05/26
4.5K1
相关推荐
我的第一个Electron应用
更多 >
LV.0
创梦网络科技有限公司后端工程师
目录
  • 一条 SQL 查询语句是如何执行的?
  • MySQL 基本架构示意图
  • 连接器
    • 怎么解决 MySQL 长连接占用内存过大的问题?
  • 查询缓存
    • 为什么大多数情况下不建议查询缓存?
    • query_cache_type 参数设置如下
  • 分析器
    • 词法分析
    • 语法分析
  • 优化器
  • 执行器
    • mysqldumpslow 慢查询日志分析工具
    • 存储引擎对比
    • 慢查询日志开启方法
    • 执行器的状态
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档