CategoryResourceRepost/极客时间专栏/SQL必知必会/第一章:SQL语法基础篇/03丨学会用数据库的方式思考SQL是如何执行的.md
louzefeng d3828a7aee mod
2024-07-11 05:50:32 +00:00

176 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<audio id="audio" title="03丨学会用数据库的方式思考SQL是如何执行的" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ad/07/ad590188d5b924bf34f75dd6f0324a07.mp3"></audio>
通过上一篇文章对不同的DBMS的介绍你应该对它们有了一些基础的了解。虽然SQL是声明式语言我们可以像使用英语一样使用它不过在RDBMS关系型数据库管理系统SQL的实现方式还是有差别的。今天我们就从数据库的角度来思考一下SQL是如何被执行的。
关于今天的内容,你会从以下几个方面进行学习:
1. Oracle中的SQL是如何执行的什么是硬解析和软解析
1. MySQL中的SQL是如何执行的MySQL的体系结构又是怎样的
1. 什么是存储引擎MySQL的存储引擎都有哪些
## Oracle中的SQL是如何执行的
我们先来看下SQL在Oracle中的执行过程
<img src="https://static001.geekbang.org/resource/image/4b/70/4b43aeaf9bb0fe2d576757d3fef50070.png" alt=""><br>
从上面这张图中可以看出SQL语句在Oracle中经历了以下的几个步骤。
<li>
语法检查检查SQL拼写是否正确如果不正确Oracle会报语法错误。
</li>
<li>
语义检查检查SQL中的访问对象是否存在。比如我们在写SELECT语句的时候列名写错了系统就会提示错误。语法检查和语义检查的作用是保证SQL语句没有错误。
</li>
<li>
权限检查:看用户是否具备访问该数据的权限。
</li>
<li>
共享池检查共享池Shared Pool是一块内存池最主要的作用是缓存SQL语句和该语句的执行计划。Oracle通过检查共享池是否存在SQL语句的执行计划来判断进行软解析还是硬解析。那软解析和硬解析又该怎么理解呢
在共享池中Oracle首先对SQL语句进行Hash运算然后根据Hash值在库缓存Library Cache中查找如果存在SQL语句的执行计划就直接拿来执行直接进入“执行器”的环节这就是软解析。
如果没有找到SQL语句和执行计划Oracle就需要创建解析树进行解析生成执行计划进入“优化器”这个步骤这就是硬解析。
</li>
<li>
优化器:优化器中就是要进行硬解析,也就是决定怎么做,比如创建解析树,生成执行计划。
</li>
<li>
执行器当有了解析树和执行计划之后就知道了SQL该怎么被执行这样就可以在执行器中执行语句了。
</li>
共享池是Oracle中的术语包括了库缓存数据字典缓冲区等。我们上面已经讲到了库缓存区它主要缓存SQL语句和执行计划。而数据字典缓冲区存储的是Oracle中的对象定义比如表、视图、索引等对象。当对SQL语句进行解析的时候如果需要相关的数据会从数据字典缓冲区中提取。
库缓存这一个步骤决定了SQL语句是否需要进行硬解析。为了提升SQL的执行效率我们应该尽量避免硬解析因为在SQL的执行过程中创建解析树生成执行计划是很消耗资源的。
你可能会问如何避免硬解析尽量使用软解析呢在Oracle中绑定变量是它的一大特色。绑定变量就是在SQL语句中使用变量通过不同的变量取值来改变SQL的执行结果。这样做的好处是能提升软解析的可能性不足之处在于可能会导致生成的执行计划不够优化因此是否需要绑定变量还需要视情况而定。
举个例子,我们可以使用下面的查询语句:
```
SQL&gt; select * from player where player_id = 10001;
```
你也可以使用绑定变量,如:
```
SQL&gt; select * from player where player_id = :player_id;
```
这两个查询语句的效率在Oracle中是完全不同的。如果你在查询player_id = 10001之后还会查询10002、10003之类的数据那么每一次查询都会创建一个新的查询解析。而第二种方式使用了绑定变量那么在第一次查询之后在共享池中就会存在这类查询的执行计划也就是软解析。
因此我们可以通过使用绑定变量来减少硬解析减少Oracle的解析工作量。但是这种方式也有缺点使用动态SQL的方式因为参数不同会导致SQL的执行效率不同同时SQL优化也会比较困难。
## MySQL中的SQL是如何执行的
Oracle中采用了共享池来判断SQL语句是否存在缓存和执行计划通过这一步骤我们可以知道应该采用硬解析还是软解析。那么在MySQL中SQL是如何被执行的呢
首先MySQL是典型的C/S架构即Client/Server架构服务器端程序使用的mysqld。整体的MySQL流程如下图所示
<img src="https://static001.geekbang.org/resource/image/c4/9e/c4b24ef2377e0d233af69925b0d7139e.png" alt=""><br>
你能看到MySQL由三层组成
1. 连接层客户端和服务器端建立连接客户端发送SQL至服务器端
1. SQL层对SQL语句进行查询处理
1. 存储引擎层:与数据库文件打交道,负责数据的存储和读取。
其中SQL层与数据库文件的存储方式无关我们来看下SQL层的结构
<img src="https://static001.geekbang.org/resource/image/30/79/30819813cc9d53714c08527e282ede79.jpg" alt="">
1. 查询缓存Server如果在查询缓存中发现了这条SQL语句就会直接将结果返回给客户端如果没有就进入到解析器阶段。需要说明的是因为查询缓存往往效率不高所以在MySQL8.0之后就抛弃了这个功能。
1. 解析器在解析器中对SQL语句进行语法分析、语义分析。
1. 优化器在优化器中会确定SQL语句的执行路径比如是根据全表检索还是根据索引来检索等。
1. 执行器在执行之前需要判断该用户是否具备权限如果具备权限就执行SQL查询并返回结果。在MySQL8.0以下的版本,如果设置了查询缓存,这时会将查询结果进行缓存。
你能看到SQL语句在MySQL中的流程是SQL语句→缓存查询→解析器→优化器→执行器。在一部分中MySQL和Oracle执行SQL的原理是一样的。
与Oracle不同的是MySQL的存储引擎采用了插件的形式每个存储引擎都面向一种特定的数据库应用环境。同时开源的MySQL还允许开发人员设置自己的存储引擎下面是一些常见的存储引擎
1. InnoDB存储引擎它是MySQL 5.5版本之后默认的存储引擎,最大的特点是支持事务、行级锁定、外键约束等。
1. MyISAM存储引擎在MySQL 5.5版本之前是默认的存储引擎,不支持事务,也不支持外键,最大的特点是速度快,占用资源少。
1. Memory存储引擎使用系统内存作为存储介质以便得到更快的响应速度。不过如果mysqld进程崩溃则会导致所有的数据丢失因此我们只有当数据是临时的情况下才使用Memory存储引擎。
1. NDB存储引擎也叫做NDB Cluster存储引擎主要用于MySQL Cluster分布式集群环境类似于Oracle的RAC集群。
1. Archive存储引擎它有很好的压缩机制用于文件归档在请求写入时会进行压缩所以也经常用来做仓库。
需要注意的是数据库的设计在于表的设计而在MySQL中每个表的设计都可以采用不同的存储引擎我们可以根据实际的数据处理需要来选择存储引擎这也是MySQL的强大之处。
## 数据库管理系统也是一种软件
我们刚才了解了SQL语句在Oracle和MySQL中的执行流程实际上完整的Oracle和MySQL结构图要复杂得多
<img src="https://static001.geekbang.org/resource/image/d9/74/d99e951b69a692c7f075dd21116d3574.png" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/9b/7f/9b515e012856099b05d9dc3a5eaabe7f.png" alt=""><br>
如果你只是简单地把MySQL和Oracle看成数据库管理系统软件从外部看难免会觉得“晦涩难懂”毕竟组织结构太多了。我们在学习的时候还需要具备抽象的能力抓取最核心的部分SQL的执行原理。因为不同的DBMS的SQL的执行原理是相通的只是在不同的软件中各有各的实现路径。
既然一条SQL语句会经历不同的模块那我们就来看下在不同的模块中SQL执行所使用的资源时间是怎样的。下面我来教你如何在MySQL中对一条SQL语句的执行时间进行分析。
首先我们需要看下profiling是否开启开启它可以让MySQL收集在SQL执行时所使用的资源情况命令如下
```
mysql&gt; select @@profiling;
```
<img src="https://static001.geekbang.org/resource/image/bc/c1/bcbfdd58b908dc8820fb57d00ff4dcc1.png" alt=""><br>
profiling=0代表关闭我们需要把profiling打开即设置为1
```
mysql&gt; set profiling=1;
```
然后我们执行一个SQL查询你可以执行任何一个SQL查询
```
mysql&gt; select * from wucai.heros;
```
查看当前会话所产生的所有profiles
<img src="https://static001.geekbang.org/resource/image/d9/bf/d9445abcde0f3b38488afe21aca8e9bf.png" alt=""><br>
你会发现我们刚才执行了两次查询Query ID分别为1和2。如果我们想要获取上一次查询的执行时间可以使用
```
mysql&gt; show profile;
```
<img src="https://static001.geekbang.org/resource/image/09/7d/09ef901a55ffcd32ed263d82e3cf1f7d.png" alt=""><br>
当然你也可以查询指定的Query ID比如
```
mysql&gt; show profile for query 2;
```
查询SQL的执行时间结果和上面是一样的。
在8.0版本之后MySQL不再支持缓存的查询原因我在上文已经说过。一旦数据表有更新缓存都将清空因此只有数据表是静态的时候或者数据表很少发生变化时使用缓存查询才有价值否则如果数据表经常更新反而增加了SQL的查询时间。
你可以使用select version()来查看MySQL的版本情况。
<img src="https://static001.geekbang.org/resource/image/08/1a/0815cf2a78889b947cb498622377c21a.png" alt="">
## 总结
我们在使用SQL的时候往往只见树木不见森林不会注意到它在各种数据库软件中是如何执行的今天我们从全貌的角度来理解这个问题。你能看到不同的RDBMS之间有相同的地方也有不同的地方。
相同的地方在于Oracle和MySQL都是通过解析器→优化器→执行器这样的流程来执行SQL的。
但Oracle和MySQL在进行SQL的查询上面有软件实现层面的差异。Oracle提出了共享池的概念通过共享池来判断是进行软解析还是硬解析。而在MySQL中8.0以后的版本不再支持查询缓存而是直接执行解析器→优化器→执行器的流程这一点从MySQL中的show profile里也能看到。同时MySQL的一大特色就是提供了各种存储引擎以供选择不同的存储引擎有各自的使用场景我们可以针对每张表选择适合的存储引擎。
<img src="https://static001.geekbang.org/resource/image/02/f1/02719a80d54a174dec8672d1f87295f1.jpg" alt=""><br>
今天的内容到这里就结束了你能说一下Oracle中的绑定变量是什么使用它有什么优缺点吗MySQL的存储引擎是一大特色其中MyISAM和InnoDB都是常用的存储引擎这两个存储引擎的特性和使用场景分别是什么
最后留一道选择题吧解析后的SQL语句在Oracle的哪个区域中进行缓存
A. 数据缓冲区<br>
B. 日志缓冲区<br>
C. 共享池<br>
D. 大池
欢迎你在评论区写下你的思考我会在评论区与你一起交流如果这篇文章帮你理顺了Oracle和MySQL执行SQL的过程欢迎你把它分享给你的朋友或者同事。
※注:本篇文章出现的图片请点击[这里](http://github.com/cystanford/SQL-XMind)下载高清大图。