01 Oracle基础架构:一条SQL查询语句是如何执行的

Oracle数据库的体系架构非常复杂,很难用一篇文章介绍清楚。SQL语句是外界和数据库打交道的主要方式,今天我们就以一条SQL的视角,看看一条查询语句在数据库中是如何执行的。

应用架构

最早的应用架构主要是CS(Client-Server)架构,其主要构成是访问数据的客户端和主要由数据库担当的服务器端。后来随着互联网技术的发展,很多应用通过浏览器来访问数据,因此又出现了BS(Browser-Server)架构。由于浏览器更靠近用户端,且其设计上以轻便为主,不适宜承担过多的功能,因此在BS架构中通常还有一层 – 应用层,实际上是由浏览器发送指令到应用层,应用层再和数据库进行交互。
在这里插入图片描述

用户通过浏览器或者客户端提交给Oracle数据库的SQL语句,需要经过一系列的操作,才能完成用户的目标,把SQL转换成最终的结果返回给客户端。下图是一个SQL语句的完整执行过程,主要包括SQL解析、SQL优化、SQL行源生成和SQL执行等几个阶段。
在这里插入图片描述

解析和优化阶段

SQL解析阶段,有可以细分为语法和语义检查、权限检查、生成执行计划等几个步骤。

  • 语法检查,这个很好理解,不是随便写几个单词就能交给数据库执行,需要验证提交的SQL语句是否符合SQL规范,是否满足语法要求;
  • 语义检查,主要是确定SQL语句是否有意义,比如语句中指定的对象或者列名是否存在;
  • 权限检查,语法验证无误后,接下来需要检查用户是否具有访问相关对象的权限;
  • 在这之后,优化器需要生成数据的获取路径,也就是我们经常说的执行计划。执行计划的优劣直接决定一条SQL是否能够高效的执行,是数据库中最为核心的部分。Oracle数据库有很多针对执行计划的精妙设计,在后面的课程中逐步展开来讲。

假设我们提交的一个针对A表的简单查询,select * from A where id=1; ,又假设A表上有两个包含id列的索引ix1和ix2,那么问题就来了,如何更高效的获取到我们想要的数据呢?针对单表一共有三种可选路径:

  • 将A表所有数据检索一遍,找到满足id=1的数据;
  • 通过检索索引ix1,找到满足id=1的数据;
  • 通过检索索引ix2,找到满足id=1的数据。
    这是优化器需要给出的答案。

大家可以看到,一张有两个索引的表,我们有三种可选的路径来获取数据,如果有两个这样的表进行关联,还需要考虑表的关联顺序,可选的路径就会变成18种。对于一个8张表的关联查询,单单是表的关联顺序,排列组合起来就多达40320(8!)种,所以为一条复杂SQL选择高效的执行计划,是非常考虑优化器能力的事情!

以上操作包含了SQL解析阶段的所有步骤,这个过程被称之为硬解析。由于需要考虑的因素非常都,执行计划的决策成本非常高,过渡的硬解析会导致系统CPU使用率过高,在并发高的系统中可能还会导致并发度下降引发性能问题。因此对于Oracle数据库来说,理想的状态是一次解析多次执行,由此将硬解析带来的成本开销减少到最低。

鉴于选择执行计划的成本非常高,Oracle设计了Library Cache组件,这个组件位于共享池中,专门用来保存已经生成好的执行计划。每次应用程序提交SQL到服务器时,都会先检索这条SQL的信息是否包含在Library Cache中,如果在其中找到了这条SQL的历史执行计划,就不需要再为其生成执行计划,直接使用已有的就好了,这个过程我们称之为软解析。软解析过程仍然需要进行语法和权限的检查,但是不再需要为SQL语句选择执行计划,有效减少解析阶段最消耗资源的操作,从而大大降低了解析过程的时间成本。

Oracle数据库中使用sqlid来唯一标识一条SQL,sqlid是由SQL文本的通过hash计算得出来的,即便两条SQL只是大小写,甚至是空格的数量差异,计算出来的sqlid也不一样,就会被数据库认为是两条不同的SQL,需要重新进行硬解析。但是在现实场景中,很难有两条SQL完全相同的,我们经常需要根据不同的条件去做查询,比如上面的这条语句 select * from A where id=1;,我们查询的是 id = 1的记录,但我们也经常需要查询 id =2 , id =3 等其他的记录,按照sqlid的计算方法,都会生成不同的sqlid,从而产生硬解析。

为了解决这个问题,Oracle引入了绑定变量。上面的语句我们做一个简单的变化,where条件中不再代入具体的值,而是使用一个变量:1来代替,具体的变量值则在SQL语句执行的第二阶段 – 绑定变量赋值阶段代入,提交给数据库执行。于是前述的过程变成了以下的样子。

exec :1 := 1;
select * from A where id=:1;
exec :1 := 2;
select * from A where id=:1;
exec :1 := 3;
select * from A where id=:1;

相信大家也看出来了,使用了绑定变量之后,每次执行的SQL语句都是相同的,不同的是变量的赋值。这种方式即保证数据查询的正确性,又大大减少了硬解析的次数,可谓是一个“完美”的方案。
现实世界总是在各种场景中不断的平衡,数据库系统的设计也一样,所谓有所得必有所失。所以这里我给完美打上了双引号,具体会有什么问题,暂时先不展开留给大家思考,后面的课程里再介绍。Oracle甚至这一二十年都在和这个问题做着不懈的斗争。

执行阶段

生成执行计划之后,交给执行引擎进行下一步的处理。Oracle的执行计划是一个树状的结构,也成为解析树,包括单表的访问路径、多表之间的关联方式和关联顺序等。执行引擎按照设定好的次序完成执行计划中的操作,上一步骤产生的结果集交给下一步骤继续处理,直到完成所有的操作。

为了提升数据库读写性能,Oracle设计了数据库缓存组件。默认情况下,数据库使用确定好的执行计划从数据库缓存中筛选满足条件的记录行,如果缓存中没有相关的数据,则需要从数据文件读取到缓冲区,这个过程我们称之为物理读。相对而言,磁盘物理读比内存缓冲区读(通常成为逻辑读)要慢很多,因此需要配置好数据库缓冲区的大小,以及结合一定的表访问策略,使得应用程序访问数据库的缓存命中率尽可能高,由此来得到更好的性能。

提取阶段

执行阶段数据库已经为我们准备好了查询结果集,提取阶段则负责将结果集发送到客户端。这个阶段相对比较简单,偶尔可能会受到操作系统传输效率、网络传输性能以及应用程序本身的配置等因素影响数据发送和接收效率。

总结

在这篇文章中,给大家介绍了从客户端发送一条SQL到数据库,经历了解析、绑定、执行和提取等几个阶段最终获取到相应的数据。其中,解析阶段因为涉及到执行计划的确定,成本非常高,为了实现一次解析多次执行的目的,Oracle又引入了绑定变量,将执行过程中的变量进行参数化处理,“欺骗”数据库认为都是相同的语句,从而避免了更多的硬解析。

但是这种方式是“完美”的吗?又会给数据库的运行带来哪些问题?大家在使用数据库的过程中,又遇到哪些解析的问题?欢迎留言讨论!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值