一个oracle事务的完整执行流程

本文详细介绍了Oracle服务器进程处理用户进程请求的过程,包括SQL语句解析、数据读取、日志记录、事务处理等多个步骤,以及commit和rollback的操作细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

服务进程如何处理用户进程的请求

服务器进程在完成用户进程的请求过程中,主要完成如下7个任务:


0.sql语句的解析

1.数据块的读入db buffer

2.记日志

3.为事务建立回滚段

4.本事务修改数据块

5.放入dirty list

6.用户commit或rollback

0.sql语句的解析

 

 

 

 

下面要讲oracle服务器进程如可处理用户进程的请求,当一用户进程提交一个sql时:update temp set a=a*2;首先oracle服务器进程从用户进程把信息接收到后,在PGA中就要此进程分配所需内存,存储相关的信息,如在会话内存存储相关的登录信息等;服务器进程把这个sql语句的字符转化为ASCII等效数字码,接着这个ASCII码被传递给一个HASH函数,并返回一个hash值,然后服务器进程将到shared pool中的library cache中去查找是否存在相同的hash值,如果存在,服务器进程将使用这条语句已高速缓存在SHARED POOLlibrary cache中的已分析过的版本来执行,如果不存在,服务器进程将在CGA中,配合UGA内容对sql,进行语法分析,首先检查语法的正确性,接着对语句中涉及的表,索引,视图等对象进行解析,并对照数据字典检查这些对象的名称以及相关结构,并根据ORACLE选用的优化模式以及数据字典中是否存在相应对象的统计数据和是否使用了存储大纲来生成一个执行计划或从存储大纲中选用一个执行计划,然后再用数据字典核对此用户对相应对象的执行权限,最后生成一个编译代码ORACLE将这条sql语句的本身实际文本、HASH值、编译代码、与此语名相关联的任何统计数据和该语句的执行计划缓存在SHARED POOLlibrary cache中。服务器进程通过SHARED POOL 锁存器(shared pool latch)来申请可以向哪些共享PL/SQL区中缓存这此内容,也就是说被SHARED POOL锁存器锁定的PL/SQL区中的块不可被覆盖,因为这些块可能被其它进程所使用。在SQL分析阶段将用到LIBRARY CACHE,从数据字典中核对表、视图等结构的时候,需要将数据字典从磁盘读入LIBRARY CACHE,因此,在读入之前也要使用LIBRARY CACHE锁存器(library cache pinlibrary cache lock)来申请用于缓存数据字典。

 

到现在为止,这个sql语句已经被编译成可执行的代码了,但还不知道要操作哪些数据,所以服务器进程还要为这个sql准备预处理数据。

 

 

 

 

1.数据块的读入db buffer

 

 

 

Oracle处理数据,都需要把数据读取到内存中(即db buffer中),首先服务器进程要判断所需数据是否在db buffer存在,如果存在且可用,则直接获取该数据,同时根据LRU算法增加其访问计数;如果buffer不存在所需数据,则要从数据文件上读取。首先服务器进程将在表头部请求TM锁(保证此事务执行过程其他用户不能修改表的结构),如果成功加TM锁,再请求一些行级锁(TX锁),如果TMTX锁都成功加锁,那么才开始从数据文件读数据,在读数据之前,要先为读取的文件准备好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

 

 

 

2.记日志

 

现在数据已经被读入到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 fileredo log buffer所持有的锁存器会被释放,并可被后来的写入信息覆盖,redo log buffer是循环使用的。Redo file也是循环使用的,当一个redo file 写满后,lgwr进程会自动切换到下一redo file(这个时候可能出现log file switchcheckpoint complete)等待事件)。如果是归档模式,归档进程还要将前一个写满的redo file文件的内容写到归档日志文件中(这个时候可能出现log file switcharchiving needed))。

 

 

 

 

3.为事务建立回滚段

 

 

 

在完成本事务所有相关的redo log buffer之后,服务器进程开始改写这个db buffer的块头部事务列表并写入scn,然后copy包含这个块的头部事务列表及scn信息的数据副本放入回滚段中,将这时回滚段中的信息称为数据块的“前映像“,这个”前映像“用于以后的回滚、恢复和一致性读。(回滚段可以存储在专门的回滚表空间中,这个表空间由一个或多个物理文件组成,并专用于回滚表空间,回滚段也可在其它表空间中的数据文件中开辟。)

 

 

 

4.本事务修改数据块

 

 

 

 

准备工作都已经做好了,现在可以改写db buffer块的数据内容了,并在块的头部写入回滚段的地址。

 

 

5. 放入dirty list

 

 

 

如果一个行数据多次update而未commit,则在回滚段中将会有多个“前映像“,除了第一个”前映像“含有scn信息外,其他每个“前映像“的头部都有scn信息和“前前映像”回滚段地址。一个update只对应一个scn,然后服务器进程将在dirty list中建立一条指向此db buffer块的指针(方便dbwr进程可以找到dirty listdb 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块的头部是否有事务,如果有事务,则从回滚段中读取数据;如果没有事务,则比较selectscndb buffer块头部的scn,如果前者小于后者,仍然要从回滚段中读取数据;如果前者大于后者,说明这是一非脏缓存,可以直接读取这个db buffer块的中内容。

 

2>.  如果是DML操作,则即使在db buffer中找到一个没有事务,而且SCN比自己小的非脏缓存数据块,服务器进程仍然要到表的头部对这条记录申请加锁,加锁成功才能进行后续动作,如果不成功,则要等待前面的进程解锁后才能进行动作(这个时候阻塞是tx锁阻塞)。

 

 

 

 

 

 

 

6.用户commit或rollback

 

 

到现在为止,数据已经在db buffer或数据文件中修改完成,但是否要永久写到数文件中,要由用户来决定commit(保存更改到数据文件)和rollback(撤销数据的更改),下面来看看在commitrollback时,oracle都在做什么。

 

 

 

 

 

 

用户执行commit命令

 

只有当sql语句所影响的所有行所在的最后一个块被读入db buffer并且重做信息被写入redo log buffer(仅指日志缓冲区,而不包括日志文件)之后,用户才可以发去commit命令,commit触发lgwr进程,但不强制立即dbwr来释放所有相应db buffer块的锁(也就是no-force-at-commit,即提交不强制写),也就是说有可能虽然已经commit了,但在随后的一段时间内dbwr还在写这条sql语句所涉及的数据块。表头部的行锁并不在commit之后立即释放,而是要等dbwr进程完成之后才释放,这就可能会出现一个用户请求另一用户已经commit的资源不成功的现象。

 

 

 

A .

Commitdbwr进程结束之间的时间很短,如果恰巧在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 把该记录删除。)

 

 

用户执行rollback

 

 

 

如果用户rollback,则服务器进程会根据数据文件块和DB BUFFER中块的头部的事务列表和SCN以及回滚段地址找到回滚段中相应的修改前的副本,并且用这些原值来还原当前数据文件中已修改但未提交的改变。如果有多个前映像,服务器进程会在一个前映像的头部找到前前映像的回滚段地址,一直找到同一事务下的最早的一个前映像为止。一旦发出了COMMIT,用户就不能rollback,这使得COMMITDBWR进程还没有全部完成的后续动作得到了保障。

 

 

到现在为例一个事务已经结束了。

 

 

 

说明:

TM锁:符合lock机制的,用于保护对象的定义不被修改

TX锁:这个锁代表一个事务,是行级锁,用数据块头、数据记录头的一些字段表示,也是符合lock机制,有resource structurelock structureenqueue算法。



转自:http://blog.youkuaiyun.com/mango_song/article/details/8314412

<think>我们被要求解释Oracle触发器的执行流程和工作原理。根据提供的引用,特别是引用[2]和引用[3],我们可以总结如下: Oracle触发器是存储在数据库中的一种特殊对象,它在特定事件发生时自动执行(即“触发”)。这些事件通常是对表的DML操作(INSERT、UPDATE、DELETE)或DDL操作,甚至数据库系统事件(如启动或关闭)。 ### 触发器的执行流程 1. **事件发生**:当对数据库执行DML操作(INSERT、UPDATE、DELETE)时,或者发生特定的系统事件(如数据库启动或关闭)时,会触发相应的触发器。 2. **触发器类型决定执行时机**: - **BEFORE触发器**:在触发事件(如INSERT、UPDATE、DELETE)执行之前运行。 - **AFTER触发器**:在触发事件执行之后运行。 - **INSTEAD OF触发器**:主要用于视图,代替直接对视图的操作,转而执行触发器定义的操作。 3. **行级与语句级**: - **行级触发器(FOR EACH ROW)**:对受DML操作影响的每一行都会触发一次。在行级触发器中,可以使用`:new`和`:old`伪记录来访问该行在操作之前和之后的值。 - **语句级触发器**:无论DML操作影响多少行,整个DML语句只触发一次。语句级触发器不能使用`:new`和`:old`伪记录。 4. **条件检查**:如果触发器带有`WHEN`条件,则在触发时会检查该条件。只有当条件为真时,才会执行触发器的主体部分;否则,跳过执行。 5. **执行触发器主体**:当满足条件时,执行触发器主体中定义的PL/SQL代码块或存储过程调用。 6. **事务处理**:触发器中的操作是作为触发事件所在事务的一部分。如果触发器执行失败,则整个事务会回滚,导致触发事件(即DML语句)也被回滚。 ### 工作原理 触发器的工作原理可以概括为以下步骤: 1. **事件检测**:数据库监控到有定义的事件发生(例如,在某个表上执行了INSERT语句)。 2. **触发器选择**:根据事件类型(INSERT、UPDATE、DELETE)和时机(BEFORE或AFTER),数据库选择符合条件的触发器。 3. **条件评估**:如果触发器定义了`WHEN`条件,则评估该条件。如果条件不满足,则跳过该触发器的执行。 4. **执行触发器**: - 对于行级触发器,数据库会为受影响的每一行执行一次触发器代码。在行级触发器中,可以通过`:new`和`:old`来引用当前行的新旧值。 - 对于语句级触发器,整个语句只执行一次触发器代码。 5. **原子性**:触发器的执行是原子性的,即触发器中的操作和触发事件(DML语句)是一个事务。如果触发器中的操作失败(例如抛出异常),则整个事务会回滚,包括触发事件所做的更改。 6. **多个触发器的执行顺序**:如果同一个事件有多个触发器,则它们的执行顺序可以通过`FOLLOWS`和`PRECEDES`子句(Oracle 11g及以后)来指定,否则执行顺序是不确定的(但通常按创建时间顺序)。 ### 示例 假设有一个表`employees`,其上定义了一个BEFORE INSERT行级触发器: ```sql CREATE OR REPLACE TRIGGER before_emp_insert BEFORE INSERT ON employees FOR EACH ROW WHEN (NEW.salary < 0) BEGIN :NEW.salary := 0; -- 如果插入的工资为负数,则自动改为0 END; ``` 当执行一条INSERT语句向`employees`插入一行数据时,如果该行的salary值为负数,触发器会在插入操作发生之前将该值改为0。 ### 总结 Oracle触发器提供了一种在数据库层面自动执行业务规则和复杂约束的机制。它们由事件驱动,根据定义的时机(BEFORE/AFTER)和粒度(行级/语句级)执行,并且可以设置条件。触发器的主体通常是一个PL/SQL块,可以包含复杂的逻辑。需要注意的是,过度使用触发器可能导致性能问题,因为它们会在每次事件发生时执行,可能增加事务的开销。 [^1]: Oracle触发器概述 [^2]: Oracle触发器详解 [^3]: oracle触发器语句 §§相关问题§§ 1. Oracle触发器中BEFORE和AFTER触发器的使用场景有什么区别? 2. 在行级触发器中,如何访问修改前后的数据? 3. 如何控制同一个表上多个触发器的执行顺序? 4. 触发器执行过程中如果发生异常,会怎样影响事务?</think>### Oracle触发器执行流程及工作原理 #### 1. **触发器的基本概念** Oracle触发器是一种特殊的存储程序,由特定数据库事件(如INSERT、UPDATE、DELETE)自动触发执行,无需显式调用[^2]。其核心特点是: - **事件驱动**:由DML操作或系统事件(如数据库启动)激活。 - **隐式执行**:满足触发条件时自动运行,无法接收参数。 - **作用范围**:可定义在表或视图上,用于实现复杂业务规则约束或审计功能[^2]。 #### 2. **执行流程(分步解析)** ##### **步骤1:事件检测** 当用户执行DML操作(如`INSERT INTO employees(...)`)时,数据库检测是否存在关联触发器。 ##### **步骤2:时机判定** 根据触发器定义的时机选择执行阶段: - **BEFORE触发器**:在DML操作**前**执行(如数据校验)[^3]。 ```sql CREATE TRIGGER validate_salary BEFORE INSERT ON employees -- 插入前触发 FOR EACH ROW BEGIN IF :NEW.salary < 0 THEN RAISE_APPLICATION_ERROR(-20001, '薪资不能为负数'); END IF; END; ``` - **AFTER触发器**:在DML操作**后**执行(如审计日志记录)[^3]。 ##### **步骤3:行级/语句级处理** - **行级触发器(`FOR EACH ROW`)**: 影响N行数据则触发N次,可通过`:NEW`和`:OLD`伪记录访问数据变化: ```sql CREATE TRIGGER log_salary_change AFTER UPDATE ON employees FOR EACH ROW -- 每修改一行触发一次 BEGIN INSERT INTO audit_log VALUES(:OLD.salary, :NEW.salary, SYSDATE); END; ``` - `:OLD`:操作前的数据(DELETE/UPDATE时有效) - `:NEW`:操作后的数据(INSERT/UPDATE时有效) - **语句级触发器**: 无论影响多少行,整个DML语句**仅触发一次**(无`:NEW/:OLD`访问权限)[^3]。 ##### **步骤4:条件过滤(`WHEN`子句)** 若触发器包含`WHEN`条件,仅当条件满足时执行主体代码: ```sql CREATE TRIGGER high_salary_alert AFTER INSERT ON employees FOR EACH ROW WHEN (NEW.salary > 100000) -- 仅当薪资>10万时触发 BEGIN send_alert('高薪员工新增: ' || :NEW.name); END; ``` ##### **步骤5:事务绑定** 触发器执行与触发它的DML操作属于**同一事务**: - 成功:事务提交后所有修改生效。 - 失败:触发器抛出异常时,整个事务回滚(包括原始DML操作)[^2]。 #### 3. **关键机制详解** 1. **临时表机制** `:NEW`和`:OLD`是内存中的伪记录,提供行级数据变更的快照。例如: - UPDATE操作中,`:OLD.salary`代表旧值,`:NEW.salary`代表新值。 - 在BEFORE触发器中修改`:NEW`值可覆盖实际写入数据[^3]。 2. **执行顺序控制** 同一事件(如INSERT)的多个触发器,默认按创建顺序执行Oracle 11g+可通过`FOLLOWS`指定顺序: ```sql CREATE TRIGGER trig2 AFTER INSERT ON emp FOLLOWS trig1 -- 明确在trig1之后执行 ... ``` 3. **递归与级联** 触发器内部执行DML可能触发其他触发器(递归)。需避免无限循环,可通过`ALTER TRIGGER trigger_name DISABLE`临时禁用[^3]。 #### 4. **典型应用场景** - **数据完整性**:实现跨表约束(如部门预算校验)。 - **审计跟踪**:自动记录关键表的数据变更历史。 - **派生数据**:实时更新汇总表(如订单总额计算)。 - **安全控制**:禁止非工作时间修改数据。 > ⚠️ **注意事项**: > 过度使用触发器可能导致性能下降(尤其是行级触发器),建议将复杂逻辑移至存储过程,仅用触发器处理必要事件[^2]。 [^1]: Oracle触发器概述 [^2]: Oracle触发器详解 [^3]: oracle触发器语句
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值