0
点赞
收藏
分享

微信扫一扫

SQL语句执行过程详解

文档课题:SQL语句执行过程详解.
1、SQL语句执行原理
1.1、客户端发出语句
当在客户端执行select语句时,客户端会把这条SQL语句发送给服务器端,让服务器端的进程来处理该语句.也就是说Oracle客户端不会做任何操作,它的主要任务是把客户端产生的SQL语句发送给服务器.虽然在客户端也有一个数据库进程,但该进程的作用与服务器上的进程作用是不相同的.服务器上的数据库进程才会对SQL语句进行处理.不过有个问题需要说明,客户端的进程跟服务器的进程是一一对应的.也就是说在客户端连接上服务器后,在客户端与服务器端都会形成一个进程,客户端上的叫做客户端进程,而服务器上的叫做服务器进程.
1.2、语句解析
当客户端把SQL语句传送到服务器后,服务器进程会对该语句进行解析.该解析工作也是在服务器端进行.如下为解析的详细步骤:
a、查库高速缓存(library cache)
服务器进程在接到客户端传送过来的SQL语句时,不会直接去数据库查询,而是先在数据库的库高速缓存去查找是否存在相同语句的执行计划.如果存在那么服务器进程就会直接执行该SQL语句,省去后续的工作.所以采用库高速缓存的话,可以提高SQL语句的查询效率.一方面是从内存中读取数据要比从硬盘数据文件中读取数据效率要高,另一方面也是因为这个语句解析的原因.不过要注意的是服务器端的数据缓存跟有些客户端软件的数据缓存是两码事.有些客户端软件为了提高查询效率,会在应用软件的客户端设置数据缓存。这些数据缓存的存在可以提高客户端应用软件的查询效率,但若其它人在服务器进行了相关修改,由于应用软件数据缓存的存在,导致修改的数据不能及时反映到客户端上。从这也可以看出应用软件的数据缓存跟数据库服务器的高速数据缓存不是一码事.
b、语句合法性检查(data dict cache)
当在库高速缓存中找不到对应的SQL语句时,服务器进程就会开始检查这条语句的合法性。主要是对SQL语句的语法进行检查,看看是否符合语法规则。如果服务器进程认为该条SQL语句不符合语法规则,就会把错误信息反馈给客户端。语法检查的过程中不会对SQL语句中所包含的表名、列名等进行检查.
c、语句含义检查(data dict cache)
若SQL语句符合语法上的定义,则服务器进程接下来会对语句中的字段、表等内容进行检查。检查字段、表是否在数据库中。如果表名与列名不准确的话,则数据库就会反馈错误信息给客户端。所以写select语句时若语法与表名或列名同时写错,系统是先提示语法错误,等到语法完全正确后,再提示列名或表名错误.
d、获得对象解析锁(control structer)
当语法、语义都正确后,系统就会对需要查询的对象加锁。主要是保护数据一致性,防止查询过程中其它用户对该对象的结构进行改变.
e、数据访问权限的核对(data dict cache)
当语法、语义通过检查之后,服务器进程接着检查连接的用户对访问的数据是否具有权限。若连接服务器的用户不具有数据访问权限,则客户端就不能够取得这些数据.
f、确定最佳执行计划
当语句与语法都没问题,权限也匹配,服务器进程依然不会直接对数据库文件进行查询。服务器进程会根据一定规则对该条SQL语句进行优化.不过要注意该优化是有限的,一般在应用软件开发的过程中对SQL语句进行优化,此阶段优化要明显强于服务器进程的自我优化,所以在应用软件开发时SQL的优化是非常有必要的。当服务器进程的优化器确定该查询语句的最佳执行计划后,就会将此SQL语句与执行计划保存到库高速缓存(library cache),以便以后相同查询时就会省略以上的语法、语义与权限检查的步骤而直接执行SQL语句,提高SQL语句处理效率.
2、语句执行
语句解析只是对SQL语句的语法进行解析,以确保服务器能够知道该语句具体含义.待语句解析完成后,数据库服务器进程才会真正执行该条SQL 语句.语句执行也分两种情况.
a、 若被选择行所在数据块已经被读取到数据缓冲区,则服务器进程会直接把该数据传递给客户端,而不是从数据库文件中去查询数据.
b、 若数据不在缓冲区中,则服务器进程将从数据库文件中查询相关数据,并把这些数据放到数据缓冲区中(buffer cache).
2.1、提取数据
当语句执行完成后,查询到的数据还在服务器进程中,还没有被传送到客户端的用户进程.所以在服务器端的进程中,有一个专门负责数据提取的一段代码.它的作用是把查询到的结果返回给客户端进程,从而完成整个查询动作.
2.2、注意事项
从整个查询处理过程中,在数据库开发或应用软件开发过程中,需要注意以下几点:
a、要了解数据库缓存跟应用软件缓存是两个概念.数据库缓存只有在数据库服务器端才存在,在客户端是不存在的.只有如此才能够保证数据库缓存中的内容跟数据库文件的内容一致,才能够根据相关规则防止数据脏读、错读的发生.而应用软件所涉及的数据缓存,由于跟数据库缓存不是一码事情,所以应用软件的数据缓存虽然可以提高数据的查询效率,但是却打破了数据一致性的要求,有时候会发生脏读、错读等情况。所以有时在应用软件上有专门一个功能用来在必要的时候清除数据缓存。不过这个数据缓存的清除,也只是清除本机上的数据缓存,或者说只是清除这个应用程序的数据缓存,而不会清除数据库的数据缓存.
b、绝大部分SQL语句都是按照此处理过程进行。DBA或者基于Oracle数据库的开发人员了解这些语句的处理过程对进行涉及到SQL语句的开发与调试是非常有帮助的。掌握这些处理原则可以减少排错时间。特别要注意数据库是把数据查询权限的审查放在语法语义的后面。
3、案例解析
3.1、SQL执行过程
A、客户端提交sql语句
update temp set a=a*2 给服务器进程.
B、服务器进程接收到客户端进程信息后,在PGA中就要为此进程分配所需内存,存储相关信息,如在会话内存存储相关的登录信息等.
C、服务器进程把sql语句的字符转化为ASCII等效数字码,接着该ASCII码被传递给HASH函数,并返回一个hash值,接着服务器进程将到shared pool中的library cache中去查找是否存在相同的hash值。
如果存在,服务器进程将使用这条已高速缓存在SHARED POOL的library cache中的执行计划来执行.
如果不存在,服务器进程将在CGA中,配合UGA内容对sql进行语法分析,首先检查语法的正确性,接着对语句中涉及的表、索引、视图等对象进行解析,并对照数据字典检查这些对象名称以及相关结构,并根据ORACLE选用的优化模式以及数据字典中是否存在相应对象的统计数据和是否使用了存储大纲来生成一个执行计划或从存储大纲中选用一个执行计划,然后再用数据字典核对此用户对相应对象的执行权限,最后生成一个编译代码.
D、ORACLE将这条sql语句的本身实际文本、HASH 值、编译代码、与此语名相关联的任何统计数据和执行计划缓存在SHARED POOL 的library cache中。服务器进程通过SHARED POOL锁存器(shared pool latch)来申请可以向哪些共享PL/SQL区中缓存这些内容,也就是说被SHARED POOL锁存器锁定的PL/SQL区中的块不可被覆盖,因为这些块可能被其它进程所使用.
E、在SQL分析阶段将用到LIBRARY CACHE,在数据字典中核对表、视图等结构时需要将数据字典从磁盘读入LIBRARY CACHE,因此在读入之前也要使用LIBRARY CACHE锁存器(library cache pin, library cache lock)来申请用于缓存数据字典. 到现在为止这个sql语句已经被编译成可执行的代码,但还不知道要操作哪些数据,所以服务器进程还要为这个sql准备预处理数据.
F、首先服务器进程要判断所需数据是否存在于db buffer,如果存在且可用则直接获取该数据,同时根据LRU算法增加其访问计数,如果buffer不存在所需数据,则要从数据文件上读取。首先服务器进程将在表头部请求TM锁(保证此事务执行过程其他用户不能修改表的结构),如果成功加TM 锁,再请求一些行级锁(TX锁),如果TM、TX锁都成功加锁,那么才开始从数据文件读数据,在读数据之前要先为读取的文件准备好buffer空间.服务器进程需要扫LRU list寻找free db buffer,扫描过程中服务器进程会把发现的所有已经被修改过的db buffer注册到dirty list中, 这些dirty buffer会通过dbwr的触发条件随后被写出到数据文件,当找到足够的空闲buffer,就可以把请求的数据行所在的数据块放入到db buffer的空闲区域或者覆盖已经被挤出LRU list的非脏数据块缓冲区,并排列在LRU list的头部,也就是在数据块放入DB BUFFER之前也是要先申请db buffer 中的锁存器,成功加锁后才能读数据到db buffer.
G、记日志,现在数据已经被读入db buffer,服务器进程将该语句所影响的并被读入db buffer中的这些行数据的rowid及要更新的原值和新值及scn等信息从PGA逐条写入redo log buffer中,在写入redo log buffer之前也要事先请求redo log buffer的锁存器,成功加锁后才开始写入,当写入达到redo log buffer大小的三分之一或写入量达到1M或超过三秒后或发生检查点时或dbwr发生之前,都会触发lgwr进程把redo log buffer的数据写入磁盘上的redo file文件中(此时可能会产生log file sync等待事件),已经被写入redo file的redo log buffer所持有的锁存器会被释放,并被后来的写入信息覆盖,redo log buffer是循环使用的.Redo file也是循环使用的,当一个redo file写满后,lgwr进程会自动切换到下一个redo file(此时可能出现log file switch (checkpoint not complete)等待事件).如果是归档模式,归档进程还要将前一个写满的redo file文件内容写到归档日志文件中(此时可能出现log file switch (archiving needed).
H、为事务建立回滚段,在完成事务所有相关的redo log buffer后,服务器进程开始改写相关db buffer的块头部事务列表并写入scn,然后copy包含这个块的头部事务列表及scn信息的数据副本放入回滚段中,将此时回滚段中的信息称为数据块的“前映像“,此”前映像“用于以后的回滚、恢复和一致性读。(回滚段可以存储在专门的回滚表空间中,该表空间由一个或多个物理文件组成,并专用于回滚表空间)
I、本事务修改数据块,准备工作已做好,现在可以改写db buffer块的数据内容,并在块的头部写入回滚段的地址.
J、放入dirty list,如果一个行数据多次update而未commit,则在回滚段中将会有多个“前映像“,除第一个”前映像“含有scn信息外,其他每个“前映像“的头部都有scn信息和“前前映像”回滚段地址。一个update只对应一个scn,然后服务器进程将在dirty list中建立一条指向此db buffer块的指针(方便dbwr进程可以找到dirty list的db buffer数据块并写入数据文件中),接着服务器进程会从数据文件中继续读入第二个数据块,重复前一数据块的动作,数据块的读入、记日志、建立回滚段、修改数据块、放入dirty list.当dirty queue的长度达到阀值(一般是25%),服务器进程将通知dbwr把脏数据写出,就是释放db buffer上的锁存器,腾出更多的free db buffer.前面一直都是在说oracle一次读一个数据块,其实oracle可以一次读入多个数据块(db_file_multiblock_read_count来设置一次读入块的个数)
3.2、特别说明
在预处理的数据已经缓存在db buffer或刚从数据文件读入到db buffer中,此时要根据sql语句的类型来决定接下来的操作.
3.2.1、如果是select语句
则要查看db buffer块的头部是否有事务。如果有事务,则从回滚段中读取数据;如果没有事务,则比较select的scn和db buffer块头部的scn,如果前者小于后者,仍然要从回滚段中读取数据;如果前者大于后者,说明其为非脏缓存,可以直接读取该db buffer块中的内容.
3.2.2、如果是dml操作
那么即使在db buffer中找到一个没有事务且SCN比自己小的非脏缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功才能进行后续动作;如果不成功,则要等待前面的进程解锁后才能进行后续动作(此时阻塞是tx锁)。
3.2.3、如果是commit或rollback
到现在为止数据已经在db buffer或数据文件中修改完成,但是否要永久写到数据文件,要由用户决定commit(保存更改到数据文件) rollback(撤销数据的更改).
3.2.3.1、执行commit
只有当sql语句所影响的所有行所在的最后一个块被读入db buffer并且重做信息被写入redo log buffer(仅指日志缓冲区,而不包括日志文件)之后,用户才可以发出commit命令,commit触发lgwr进程但不强制立即dbwr来释放所有相应db buffer块的锁(也就是no-force-at-commit,即提交不强制写),也就是说有可能虽然已经commit,但在随后的一段时间内dbwr还在写这条sql语句所涉及的数据块.表头部的行锁并不在commit之后立即释放,而是要等dbwr进程完成之后才释放,这就可能会出现一个用户请求另一用户已经commit的资源不成功的现象.
情形1:
虽然从Commit到dbwr进程结束之间的时间很短,但恰巧如果在commit之后,dbwr未结束之前断电,因为commit之后的数据已经属于数据文件的内容,但这部分文件没有完全写入到数据文件中,所以需要前滚。由于commit已经触发lgwr,这些所有未来得及写入数据文件的更改会在实例重启后,由smon进程根据重做日志文件来前滚,完成之前commit未完成的工作(即把更改写入数据文件).
情形2:
如果未commit就断电,因为数据已经在db buffer更改了但没有commit,说明这部分数据不属于数据文件,由于dbwr之前触发lgwr,也就是只要数据存在修改都会被先一步记入重做日志文件,实例重启后SMON进程会根据重做日志文件来回滚。其实smon的前滚回滚是根据检查点来完成的,当一个检查点发生时,首先让LGWR进程将redo log buffer中的所有缓冲(包含未提交的重做信息)写入重做日志文件,然后让dbwr进程将db buffer已提交的缓冲写入数据文件(不强制写未提交的).然后更新控制文件和数据文件头部的SCN,表明当前数据库是一致的,在相邻的两个检查点之间有很多事务,有提交和未提交的.

上诉前滚回滚比较完整的说法如下:
情形1:
发生检查点之前断电,并且当时有一个未提交的改变正在进行,实例重启之后SMON进程将从上一个检查点开始核对该检查点之后记录在重做日志文件中已提交的和未提交改变,因为dbwr之前会触发lgwr,所以dbwr对数据文件的修改一定会被先记录在重做日志文件中。那么断电前被DBWN 写进数据文件的改变将通过重做日志文件中的记录进行还原,称为回滚。
情形2:
如果断电时有一个事务已提交,但dbwr动作还没有完全完成,因为已经提交,提交会触发lgw进程,所以不管dbwr动作是否已完成,该语句将要影响的行及其产生的结果一定已经记录在重做日志文件中。实例重启后SMON进程根据重做日志文件进行前滚。实例失败后用于恢复的时间由两个检查点之间的间隔大小来决定。
可以通个四个参数设置检查点执行的频率:
Log_checkpoint_interval:决定两个检查点之间写入重做日志文件的系统物理块(redo blocks)的大小,默认值是0,无限制.
log_checkpoint_timeout:两个检查点之间的时间长度(秒)默认值1800s.
fast_start_io_target:决定用于恢复时需要处理的块的多少,默认值是0,无限制.
fast_start_mttr_target:直接决定用于恢复的时间长短,默认值是0,无限制
3.2.3.2、回滚概念区别
SMON进程执行的前滚和回滚与用户的回滚是不同的,SMON是根据重做日志文件进行前滚或回滚,而用户的回滚一定是根据回滚段的内容进行回滚。特别说明回滚段存储的数据:
假如是delete,回滚段将会记录整个行的数据;
假如是update,回滚段只记录被修改字段变化前的数据(前映像),也就是没有被修改的字段是不会被记录的;
假如是insert,则回滚段只记录插入记录的rowid;
假如是事务提交,回滚段将简单标记该事务已经提交。
回退情况:
如果delete,回退的时候把回滚段中数据重新写回数据块;
如果update,则把变化前数据修改回去;
如果是insert,则根据记录的rowid把该记录删除。
3.2.4、用户rollback
如果用户rollback,则服务器进程会根据数据文件块和DB BUFFER中块的头部的事务列表和SCN以及回滚段地址找到回滚段中相应的修改前的副本,并且用这些原值来还原当前数据文件中已修改但未提交的改变。如果有多个“前映像”,服务器进程会在一个“前映像”的头部找到“前前映像”的回滚段地址,一直找到同一事务下的最早的那个“前映像”为止。一旦发出了COMMIT,用户就不能rollback。
说明:
TM锁符合lock机制,用于保护对象的定义不被修改.
TX锁代表一个事务,是行级锁,用数据块头、数据记录头的一些字段表示,也是符合lock机制,有resource structure、lock structure、enqueue算法.

以上内容均来自网址:https://www.cnblogs.com/testero/p/15158167.html

举报

相关推荐

0 条评论