ORACLESQL语句执行流程与顺序原理解析
ORACLESQL语句执⾏流程与顺序原理解析
Oracle语句执⾏流程
第⼀步:客户端把语句发给服务器端执⾏
当我们在客户端执⾏SQL语句时,客户端会把这条SQL语句发送给服务器端,让服务器端的进程来处理这语句。也就是说,Oracle 客户端是不会做任何的操作,他的主要任务就是把客户端产⽣的⼀些SQL语句发送给服务器端。服务器进程从⽤户进程把信息接收到后,在PGA 中就要此进程分配所需内存,存储相关的信息,如:在会话内存存储相关的登录信息等。
虽然在客户端也有⼀个数据库进程,但是,这个进程的作⽤跟服务器上的进程作⽤是不相同的,服务器上的数据库进程才会对SQL 语句进⾏相关的处理。不过,有个问题需要说明,就是客户端的进程跟服务器的进程是⼀⼀对应的。也就是说,在客户端连接上服务器后,在客户端与服务器端都会形成⼀个进程,客户端上的我们叫做客户端进程,⽽服务器上的我们叫做服务器进程。
第⼆步:语句解析
当客户端把SQL语句传送到服务器后,服务器进程会对该语句进⾏解析。这个解析的⼯作是在服务器端所进⾏的,解析动作⼜可分为很多⼩动作。
1)查询⾼速缓存(library cache)
服务器进程在接到客户端传送过来的SQL语句时,不会直接去数据库查询。服务器进程把这个SQL语句的字符转化为ASCII等效数字码,接着这个ASCII码被传递给⼀个HASH函数,并返回⼀个hash值,然后服务器进程将到shared pool中的library cache(⾼速缓存)中去查是否存在相同的hash值。如果存在,服务器进程将使⽤这条语句已⾼速缓存在SHARED POOL的library cache中的已分析过的版本来执⾏,省去后续的解析⼯作,这便是软解析。若调整缓存中不存在,则需要进⾏后⾯的步骤,这便是硬解析。硬解析通常是昂贵的操作,⼤约占整个SQL执⾏的70%左右的时间,硬解析会⽣成执⾏树,执⾏计划,等等。
所以,采⽤⾼速数据缓存的话,可以提⾼SQL 语句的查询效率。其原因有两⽅⾯:⼀⽅⾯是从内存中读取数据要⽐从硬盘中的数据⽂件中读取数据效率要⾼,另⼀⽅⾯也是因为避免语句解析⽽节省了时间。
不过这⾥要注意⼀点,这个数据缓存跟有些客户端软件的数据缓存是两码事。有些客户端软件为了提⾼查询效率,会在应⽤软件的客户端设置数据缓存。由于这些数据缓存的存在,可以提⾼客户端应⽤软件的查询效率。但是,若其他⼈在服务器进⾏了相关的修改,由于应⽤软件数据缓存的存在,导致修改的数据不能及时反映到客户端上。从这也可以看出,应⽤软件的数据缓存跟数据库服务器的⾼速数据缓存不是⼀码事。
中秋节祝福短信2)语句合法性检查(data dict cache)
2022国庆节放假调休当在⾼速缓存中不到对应的SQL语句时,则服务器进程就会开始检查这条语句的合法性。这⾥主要是对SQL语句的语法进⾏检查,看看其是否合乎语法规则。如果服务器进程认为这条SQL语句不符合语法规则的时候,就会把这个错误信息反馈给客户端。在这个语法检查的过程中,不会对SQL语句中所包含的表名、列名等等进⾏检查,只是检查语法。
3)语⾔含义检查(data dict cache)
若SQL 语句符合语法上的定义的话,则服务器进程接下去会对语句中涉及的表、索引、视图等对象进⾏解析,并对照数据字典检查这些对象的名称以及相关结构,看看这些字段、表、视图等是否在数据库中。如果表名与列名不准确的话,则数据库会就会反馈错误信息给客户端。
所以,有时候我们写select语句的时候,若语法与表名或者列名同时写错的话,则系统是先提⽰说语法错误,等到语法完全正确后再提⽰说列名或表名错误。
4)获得对象解析锁(control structer)
当语法、语义都正确后,系统就会对我们需要查询的对象加锁。这主要是为了保障数据的⼀致性,防⽌我们在查询的过程中,其他⽤户对这个对象的结构发⽣改变。
5)数据访问权限的核对(data dict cache)
当语法、语义通过检查之后,客户端还不⼀定能够取得数据,服务器进程还会检查连接⽤户是否有这个数据访问的权限。若⽤户不具有数据访问权限的话,则客户端就不能够取得这些数据。要注意的是数据库服务器进程先检查语法与语义,然后才会检查访问权限。
6)确定最佳执⾏计划
当语法与语义都没有问题权限也匹配,服务器进程还是不会直接对数据库⽂件进⾏查询。服务器进程会根据⼀定的规则,对这条语句进⾏优化。在执⾏计划开发之前会有⼀步查询转换,如:视图合并、⼦查询解嵌套、谓语前推及物化视图重写查询等。为了确定采⽤哪个执⾏计划,Oracle还需要收集统计信息确定表的访问联结⽅法等,最终确定可能的最低成本的执⾏计划。
不过要注意,这个优化是有限的。⼀般在应⽤软件开发的过程中,需要对数据库的sql语句进⾏优化,这个优化的作⽤要⼤⼤地⼤于服务器进程的⾃我优化。
当服务器进程的优化器确定这条查询语句的最佳执⾏计划后,就会将这条SQL语句与执⾏计划保存到数据⾼速缓存(library cache)。如此,等以后还有这个查询时,就会省略以上的语法、语义与权限检查的步骤,⽽直接执⾏SQL语句,提⾼SQL语句处理效率。
第三步:绑定变量赋值
如果SQL语句中使⽤了绑定变量,扫描绑定变量的声明,给绑定变量赋值,将变量值带⼊执⾏计划。若在解析的第⼀个步骤,SQL在⾼速缓冲中存在,则直接跳到该步骤。
第四步:语句执⾏
语句解析只是对SQL语句的语法进⾏解析,以确保服务器能够知道这条语句到底表达的是什么意思。等到语句解析完成之后,数据库服务器进程才会真正的执⾏这条SQL语句。
对于SELECT语句:
1)⾸先服务器进程要判断所需数据是否在db buffer存在,如果存在且可⽤,则直接获取该数据⽽不是从数据库⽂件中去查询数据,同时根据LRU 算法增加其访问计数;
2)若数据不在缓冲区中,则服务器进程将从数据库⽂件中查询相关数据,并把这些数据放⼊到数据缓冲区中(buffer cache)。
七一思想汇报其中,若数据存在于db buffer,其可⽤性检查⽅式为:查看db buffer块的头部是否有事务,如果有事务,则从回滚段中读取数据;如果没有事务,则⽐较select的scn和db buffer块头部的scn,如果前者
⼩于后者,仍然要从回滚段中读取数据;如果前者⼤于后者,说明这是⼀⾮脏缓存,可以直接读取这个db buffer块的中内容。
对于DML语句(insert、delete、update):
1)检查所需的数据库是否已经被读取到缓冲区缓存中。如果已经存在缓冲区缓存,则直接执⾏步骤3;
2)若所需的数据库并不在缓冲区缓存中,则服务器将数据块从数据⽂件读取到缓冲区缓存中;
3)对想要修改的表取得的数据⾏锁定(Row Exclusive Lock),之后对所需要修改的数据⾏取得独占锁;
4)将数据的Redo记录复制到redo log buffer;
5)产⽣数据修改的undo数据;
6)修改db buffer;
7)dbwr将修改写⼊数据⽂件;
其中,第2步,服务器将数据从数据⽂件读取到db buffer经经历以下步骤:
1)⾸先服务器进程将在表头部请求TM锁(保证此事务执⾏过程其他⽤户不能修改表的结构),如果成功加TM锁,再请求⼀些⾏级锁(TX锁),如果TM、TX锁都成功加锁,那么才开始从数据⽂件读数据。理财产品风险等级
2)在读数据之前,要先为读取的⽂件准备好buffer空间。服务器进程需要扫描LRU list寻free db buffer,扫描的过程中,服务器进程会把发现的所有已经被修改过的db buffer注册到dirty list中。如果free db buffer及⾮脏数据块缓冲区不⾜时,会触发dbwr将dirty buffer中指向的缓冲块写⼊数据⽂件,并且清洗掉这些缓冲区来腾出空间缓冲新读⼊的数据。
3)到了⾜够的空闲buffer,服务器进程将从数据⽂件中读⼊这些⾏所在的每⼀个数据块(db block)(DB BLOCK是ORACLE的最⼩操作单元,即使你想要的数据只是DB BLOCK中很多⾏中的⼀⾏或⼏⾏,ORACLE也会把这个DB BLOCK中的所有⾏都读⼊Oracle DB BUFFER中)放⼊db buffer的空闲的区域或者覆盖已被挤出LRU list的⾮脏数据块缓冲区,并且排列在LRU列表的头部,也就是在数据块放⼊db buffer之前也是要先申请db buffer中的锁存器,成功加锁后,才能读数据到db buffer。
若数据块已经存在于db buffer cache(有时也称db buffer或db cache),即使在db buffer中到⼀个
没有事务,⽽且SCN⽐⾃⼰⼩的⾮脏缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功才能进⾏后续动作,如果不成功,则要等待前⾯的进程解锁后才能进⾏动作(这个时候阻塞是tx锁阻塞)。
在记redo⽇志时,其具体步骤如下:
1)数据被读⼊到db buffer后,服务器进程将该语句所影响的并被读⼊db buffer中的这些⾏数据的rowid及要更新的原值和新值及scn等信息从PGA逐条的写⼊redo log buffer中。在写⼊redo log buffer之前也要事先请求redo log buffer的锁存器,成功加锁后才开始写⼊。
2)当写⼊达到redo log buffer⼤⼩的三分之⼀或写⼊量达到1M或超过三秒后或发⽣检查点时或者dbwr之前发⽣,都会触发lgwr进程把redo log buffer的数据写⼊磁盘上的redo file⽂件中(这个时候会产⽣log file sync等待事件)。
3)已经被写⼊redo file的redo log buffer所持有的锁存器会被释放,并可被后来的写⼊信息覆盖,redo log buffer是循环使⽤的。Redo file也是循环使⽤的,当⼀个redo file写满后,lgwr进程会⾃动切换到下⼀redo file(这个时候可能出现log file switch(check point complete)等待事件)。如果是归档模式,归档进程还要将前⼀个写满的redo file⽂件的内容写到归档⽇志⽂件中(这个时候可能出现log file switch(archiving needed)。
在为事务建⽴undo信息时,其具体步骤如下:
熟视无睹造句1)在完成本事务所有相关的redo log buffer之后,服务器进程开始改写这个db buffer的块头部事务列表并写⼊scn(⼀开始scn是写在redo log buffer中的,并未写在db buffer)。
2)然后copy包含这个块的头部事务列表及scn信息的数据副本放⼊回滚段中,将这时回滚段中的信息称为数据块的“前映像”,这个“前映像”⽤于以后的回滚、恢复和⼀致性读。(回滚段可以存储在专门的回滚表空间中,这个表空间由⼀个或多个物理⽂件组成,并专⽤于回滚表空间,回滚段也可在其它表空间中的数据⽂件中开辟)。
在修改信息写⼊数据⽂件时,其具体步骤如下:
1)改写db buffer块的数据内容,并在块的头部写⼊回滚段的地址。
2)将db buffer指针放⼊dirty list。如果⼀个⾏数据多次update⽽未commit,则在回滚段中将会有多个“前映像”,除了第⼀个“前映像”含有scn信息外,其他每个"前映像"的头部都有scn信息和"前前映像"回滚段地址。⼀个update只对应⼀个scn,然后服务器进程将在dirty list中建⽴⼀条指向此db buffer块的指针(⽅便dbwr进程可以到dirty list的db buffer数据块并写⼊数据⽂件中)。接着服务器进程会从数据⽂件中继续读⼊第⼆个数据块,重复前⼀数据块的动作,数据块的读⼊、记⽇志、建⽴回滚段、修改数据块、放⼊dirty list。
3)当dirty queue的长度达到阀值(⼀般是25%),服务器进程将通知dbwr把脏数据写出,就是释放db buffer上的锁存器,腾出更多的free db buffer。前⾯⼀直都是在说明oracle⼀次读⼀个数据块,其实oracle可以⼀次读⼊多个数据块(db_file_multiblock_read_count来设置⼀次读⼊块的个数)
当执⾏commit时,具体步骤如下:
1)commit触发lgwr进程,但不强制dbwr⽴即释放所有相应db buffer块的锁。也就是说有可能虽然已经commit了,但在随后的⼀段时间内dbwr还在写这条sql语句所涉及的数据块。表头部的⾏锁并不在commit之后⽴即释放,⽽是要等dbwr进程完成之后才释放,这就可能会出现⼀个⽤户请求另⼀⽤户已经commit的资源不成功的现象。
2)从Commit和dbwr进程结束之间的时间很短,如果恰巧在commit之后,dbwr未结束之前断电,因为commit之后的数据已经属于数据⽂件的内容,但这部分⽂件没有完全写⼊到数据⽂件中。所以需要前滚。由于commit已经触发lgwr,这些所有未来得及写⼊数据⽂件的更改会在实例重启后,由smon进程根据重做⽇志⽂件来前滚,完成之前commit未完成的⼯作(即把更改写⼊数据⽂件)。
3)如果未commit就断电了,因为数据已经在db buffer更改了,没有commit,说明这部分数据不属于数据⽂件。由于dbwr之前触发lgwr 也就是只要数据更改,(肯定要先有log)所有dbwr在数据⽂件上的修改都会被先⼀步记⼊重做⽇志⽂件,实例重启后,SMON进程再根据重做⽇志⽂件来回滚。
其实smon的前滚回滚是根据检查点来完成的,当⼀个全部检查点发⽣的时候,⾸先让LGWR进程将redologbuffer中的所有缓冲(包含未提交的重做信息)写⼊重做⽇志⽂件,然后让dbwr进程将dbbuffer已提交的缓冲写⼊数据⽂件(不强制写未提交的)。然后更新控制⽂件和数据⽂件头部的SCN,表明当前数据库是⼀致的,在相邻的两个检查点之间有很多事务,有提交和未提交的。
当执⾏rollback时,具体步骤如下:
服务器进程会根据数据⽂件块和db buffer中块的头部的事务列表和SCN以及回滚段地址到回滚段中相应的修改前的副本,并且⽤这些原值来还原当前数据⽂件中已修改但未提交的改变。如果有多个”前映像“,服务器进程会在⼀个“前映像”的头部到“前前映像”的回滚段地址,⼀直到同⼀事务下的最早的⼀个“前映像”为⽌。⼀旦发出了commit,⽤户就不能rollback,这使得commit后dbwr进程还没有全部完成的后续动作得到了保障。
第五步:提取数据
当语句执⾏完成之后,查询到的数据还是在服务器进程中,还没有被传送到客户端的⽤户进程。所以,在服务器端的进程中,有⼀个专门负责数据提取的⼀段代码。他的作⽤就是把查询到的数据结果返回给⽤户端进程,从⽽完成整个查询动作。
从这整个查询处理过程中,我们在数据库开发或者应⽤软件开发过程中,需要注意以下⼏点:
  ⼀是要了解数据库缓存跟应⽤软件缓存是两码事情。数据库缓存只有在数据库服务器端才存在,在客户端是不存在的。只有如此,才能够保证数据库缓存中的内容跟数据库⽂件的内容⼀致。才能够根据相关的规则,防⽌数据脏读、错读的发⽣。⽽应⽤软件所涉及的数据缓存,由于跟数据库缓存不是⼀码事情,所以,应⽤软件的数据缓存虽然可以提⾼数据的查询效率,但是,却打破了数据⼀致性的要求,有时候会发⽣脏读、错读等情况的发⽣。所以,有时候,在应⽤软件上有专门⼀个功能,⽤来在必要的时候清除数据缓存。不过,这个数据缓存的清除,也只是清除本机上的数据缓存,或者说,只是清除这个应⽤程序的数据缓存,⽽不会清除数据库的数据缓存。
  ⼆是绝⼤部分SQL语句都是按照这个处理过程处理的。我们DBA或者基于Oracle数据库的开发⼈员了解这些语句的处理过程,对于我们进⾏涉及到SQL语句的开发与调试,是⾮常有帮助的。有时候,掌握这些处理原则,可以减少我们排错的时间。特别要注意,数据库是把数据查询权限的审查放在语法语义的后⾯进⾏检查的。所以,有时会若光⽤数据库的权限控制原则,可能还不能满⾜应⽤软件权限控制的需要。此时,就需要应⽤软件的前台设置,实现权限管理的要求。⽽且,有时应⽤数据库的权限管理,也有点显得繁琐,会增加服务器处理的⼯作量。因此,对于记录、字段等的查询权限控制,⼤部分程序涉及⼈员喜欢在应⽤程序中实现,⽽不是在数据库上实现。
Oracle SQL语句执⾏顺序
(8)SELECT (9) DISTINCT (11) <select_list>
(1)  FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
2022年冬奥会中国获得几枚金牌
(6) WITH {CUBE | ROLLUP}
(7) HAVING <having_condition>
(10) ORDER BY <order_by_list>
1)FROM:对FROM⼦句中的表执⾏笛卡尔积(交叉联接),⽣成虚拟表VT1。
2)ON:对VT1应⽤ON筛选器,只有那些使为真才被插⼊到TV2。
3)OUTER (JOIN):如果指定了OUTER JOIN(相对于CROSS JOIN或INNER JOIN),保留表中未到匹配的⾏将作为外部⾏添加到
VT2,⽣成TV3。如果FROM⼦句包含两个以上的表,则对上⼀个联接⽣成的结果表和下⼀个表重复执⾏步骤1到步骤3,直到处理完所有的表位置。
4)WHERE:对TV3应⽤WHERE筛选器,只有使为true的⾏才插⼊TV4。
5)GROUP BY:按GROUP BY⼦句中的列列表对TV4中的⾏进⾏分组,⽣成TV5。
6)CUTE|ROLLUP:把超组插⼊VT5,⽣成VT6。
7)HAVING:对VT6应⽤HAVING筛选器,只有使为true的组插⼊到VT7。
8)SELECT:处理SELECT列表,产⽣VT8。
9)DISTINCT:将重复的⾏从VT8中删除,产品VT9。
10)ORDER BY:将VT9中的⾏按ORDER BY⼦句中的列列表顺序,⽣成⼀个游标(VC10),⽣成表TV11,并返回给调⽤者。
以上每个步骤都会产⽣⼀个虚拟表,该虚拟表被⽤作下⼀个步骤的输⼊。这些虚拟表对调⽤者(客户端应⽤程序或者外部查询)不可⽤。只有最后⼀步⽣成的表才会会给调⽤者。如果没有在查询中指定某⼀个⼦句,将跳过相应的步骤。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。