SQL语句执行完整过程:算法
1. 用户进程提交一个sql 语句: update temp set a=a*2,给服务器进程。sql
2.服务器进程从用户进程把信息接收到后, 在PGA 中就要此进程分配所需内存,存储相关的信息,如在会话内存存储相关的登陆信息等。数据库
3.服务 器进程把这个sql 语句的字符转化为ASCII 等效数字码, 接着这个ASCII 码被传递给一个HASH 函数, 并返回一个hash 值,而后服务器进程将到shared pool 中的library cache 中去查找是否存在相 同的hash 值,若是存在, 服务器进程将使用这条语句已高速缓存在SHARED POOL 的library cache 中的已分析过的版原本执行。缓存
4.若是不存在, 服务器进程将在CGA 中, 配合UGA 内容对sql,进行语法分析,首先检查语法的正确性,接着对语句中涉及的表,索引,视图等对象进行解析,并对照数据字典检查这些对象的名称以及相关结构,并根据ORACLE 选用的优化模式以及数据字典中是否存在相应对象的统计数据和是否使用了存储大纲来生成一个执行计划或从存储大纲中选用一个执行计划,而后再用数据字典核对此用户对相应对象的执行权限,最后生成一个编译代码。服务器
5.ORACLE 将这条sql 语句的自己实际文本、HASH 值、编译代码、与此语名相关联的任何统计数据 和该语句的执行计划缓存在SHARED POOL 的library cache 中。服务器进程经过SHARED POOL 锁存器(shared pool latch) 来申请能够向哪些共享PL/SQL 区中缓存这此内容,也就是说被SHARED POOL 锁存 器锁定的PL/SQL 区中的块不可被覆盖,由于这些块可能被其它进程所使用。网络
6.在SQL分析阶段将用到LIBRARY CACHE,从数据字典中核对表、视图等结构的时候,须要将数据 字典从磁盘读入LIBRARY CACHE,所以,在读入以前也要使用LIBRARY CACHE 锁存器(library cache pin,library cache lock) 来申请用于缓存数据字典。到如今为止, 这个sql 语句已经被编译成可执行的代码了,但还不知道要操做哪些数据, 因此服务器进程还要为这个sql 准备预处理数据。oracle
7.首先服务器进程要判断所需数据是否在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。函数
8.记日志如今数据已经被读入到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 等待事件) 已经被写入redofile 的redo log buffer 所持有的锁存器会被释放,并可被后来的写入信息覆盖, redo log buffer是循环使用的。Redo file 也是循环使用的, 当一个redo file 写满后,lgwr 进程会自动切换到 下一redo file( 这个时候可能出现log fileswitch(checkpoint complete)等待事件)。若是是归档模式,归档进 程还要将前一个写满的redo file 文件的内容写到归档日志文件中( 这个时候可能出现log fileswitch(archiving needed)。优化
9. 为事务创建回滚段在完成本事务全部相关的redo log buffer 以后,服务器进程开始改写 这个db buffer 的块头部事务列表并写入scn, 而后copy 包含这个块的头部事务列表及scn 信息的数据副本放入回滚段中,将这时回滚段““中的信息称为数据块的前映像,”这个前映“像用于之后的回滚、恢复和一致性读。(回滚段能够存储在专门的回滚表空间中,这个表空间由一个或多个物理文件组成,并专用于回滚表空间,回滚段也可在其它表空间中的数据文件中开辟。操作系统
10. 本事务修改数据块准备工做都已经作好了, 如今能够改写db buffer 块的数据内容了,并在块的头部写入回滚段的地址。
11. 放入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 来设置一次读入块的个数) 说明: 在预处理的数据已经缓存在db buffer 或刚刚被从数据文件 读入到db buffer 中, 就要根据sql 语句的类型来决定接下来如何操做。 1> 若是是select 语句, 则要查看db buffer 块的头部是否有事务,若是有事务,则从回滚段中读取数据;若是没有事务, 则比较select 的scn 和db buffer 块头部的scn,若是前者小于后者,仍然要从回滚段中读取数据;若是前者大于后者,说明这是一非脏缓存, 能够直接读取这个db buffer 块的中内容。 2> 若是是DML 操做, 则即便在db buffer 中找到一个没有事务, 并且SCN 比本身小的非脏 缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功才能进行后续动做,若是不成功,则要等待前面的进程解锁后才能进行动做( 这个时候阻塞是tx 锁阻塞)。 用户commit 或rollback 到如今为止, 数据已经在db buffer 或数据文件中修改完 成,可是否要永久写到数文件中,要由用 户来决定commit(保存更改到数据文件) rollback 撤销数据的更改)。 1. 用户执行commit 命令 只有当sql 语句所影响的全部行所在的最后一个块被读入db buffer 而且重作信息被写入redo log buffer(仅指日志缓冲区,而不包括日志文件)以后, 用户才能够发去commit 命令,commit 触发lgwr 进程,但不 强制当即dbwr 来释放全部相应db buffer 块的锁(也就是no-force-at-commit,即提交不强制写),也就是说有 可能虽然已经commit 了,但在随后的一 段时间内dbwr 还在写这条sql 语句所涉及的数据块。表头部的行锁 并不在commit 以后当即释放, 而是要等dbwr 进程完成以后才释放,这就可能会出现一个用户请求另外一用户 已经commit 的资源不成功的现象。 A . 从Commit 和dbwr 进程结束之间的时间很短,若是 恰巧在commit 以后,dbwr 未结束以前断电,由于commit 以后的数据已经属于数据文件的内容,但这部分文件没有彻底写入到数据文件中。因此须要前滚。由 于commit 已经触 发lgwr,这些全部将来得及写入数据文件的更改会在实例重启后, 由smon 进程根据重作日志文件来前滚, 完成以前commit 未完成的工做(即把更改写入数据文件)。 B. 若是未commit 就断电了, 由于数据已经在db buffer 更改了, 没有commit,说明这部分数据不属于数据文件, 因为dbwr 以前触发lgwr 也就是只要数据更改,( 确定要先有log) 全部DBWR,在数据文件上的修改都会被先一步记入重作日志文件,实例重启后,SMON 进程再根据重作日志文件来回滚。 其实smon 的前滚回滚是根据检查点来完成的,当一个所有检查点发生的时候, 首先让LGWR 进程将redo log buffer 中的全部缓冲(包含未提交的重作信息)写入重作日志文件, 而后让dbwr 进程将db buffer 已提交的缓冲写入数据文件(不强制写未提交的)。而后更新控制文件和 数据文件头部的SCN,代表当前数据库是一致的,在相邻的两个检查点之间有不少事务,有提交和未提交的。 像前面的前滚回滚比较完整的说法是以下的说明: A.发生检查点以前断电,而且当时有一个未提交的改变正在进行,实例重启以后,SMON 进程将从上一个检查点开始核对这个检查点以后记录在重作日志文件中已提交的和未提交改变,由于 dbwr 以前会触发lgwr, 因此dbwr 对数据文件的修改必定会被先记录在重作日志文件中。所以, 断电前被DBWN 写进数据文件的改变将经过重作日志文件中的记录进行还原,叫作回滚, B. 若是断电时有一个已提交, 但dbwr 动做尚未彻底完成的改变存在,由于已经提交, 提交会触发lgwr 进程,因此不 管dbwr 动做是否已完成,该语句将要影响的行及其产生的结果必定已经记录在重作日志文件中了,则实例重启后,SMON 进程根据重作日志文件进行前滚. 实例失败后用于恢复的时间由两个检查点之间的间隔大小来决定,能够通个四个参数设置检查点执行的频率: Log_checkpoint_interval: 决定两个检查点之间写入重作日志文件的系统物理块(redo blocks)的大小, 默认值是0,无限制。 log_checkpoint_timeout: 两个检查点之间的时间长度(秒)默认值1800s。fast_start_io_target: 决定了用于恢复时须要处理的块的多少, 默认值是0, 无限制。fast_start_mttr_target: 直接决定了用于恢复的时间的长短, 默认值是0,无限制(SMON 进程执行的前滚和回滚与用户的回滚是不一样的,SMON 是根据重作日志文件进行前滚或回滚,而用户的回滚必定是根据回滚段 的内容进行回滚的。 在这里要说一下回滚段存储的数据, 假如是delete 操做,则回滚段将会记录整个行的数据, 假如是update,则回滚段只记录被修改了的字段的变化前的数据(前映像),也就是没有被修改的字段是不会被记录的, 假如是insert,则回滚段只 记录插入记录的rowid 。这样假如事务提交,那回滚段中简单标记该事务已经提交;假如是回退, 则若是操做是delete,回退的时候把回滚段中数据从新写回数据块, 操做若是是update,则把变化前数据修改回去,操做若是 是insert, 则根据记录的rowid 把该记录删除。 2. 若是用户rollback。 则服务器进程会根据数据文件块和DB BUFFER 中块的头部的事务列表 和SCN 以及回滚段地址找到回滚段中相应的修改前的副本,而且用这些原值来还原当前数据文件中已修改但未提交的改变。若是有多个“”前映像,“服务器进程会在一个前映”“”像的头部找到前前映像的回滚段地址,一直找到同一事务下的最先的“”一个前映像 为止。一旦发出了COMMIT,用户就不能rollback, 这使得COMMIT 后DBWR 进程尚未所有完成的后续动做获得了保障。到如今为例一个事务已经结束了。 说明: TM 锁: 符合lock 机制的, 用于保护对象的定义不被修改。TX 锁: 这个锁表明一个事务,是行级锁,用数据块头、数据记录头的一些字段表示, 也是符合lock 机制, 有resource structure、lock structure、enqueue 算法。
仅仅是一个SQL语句就包含了这么复杂的处理过程,若是算上硬件交互(键盘鼠标操做),操做系统处理,网络传输等等,仅仅点击一个查询按钮后台就介入了无比复杂的各方面的处理过程