Oracle9i&10g的编程艺术-概念

修改跟踪文件(change tracking file)此文件只用于数据库的备份和恢复。
闪回日志文件(flashback log file)此文件用于存储数据库块的“前映像”,以便完成flashback database功能。

SID是站点标识符(site identifie)。在UNIX中,SID和ORACLE_HOME(Oracle软件的安装目录)一同进行散列运算,创建一个惟一的键名从而附加到SGA。如果ORACLE_SID或ORACLE_HOME设置不当,就会得到ORACLE NOT AVAILABLE (ORACLE不可用)错误,因为无法附加到这个惟一键所标识的共享内存段。

如果使用Oracle的共享服务器连接,就会使用一个后台进程;因此,跟踪文件的位置由BACKGROUND_DUMP_DEST确定。如果使用的是专用服务器连接,则会使用一个用户或前台进程与Oracle交互;所以跟踪文件会放在USER_DUMP_DEST参数指定的目录中。

deferred指定系统修改是否只对以后的会话生效(对当前建立的会话无效,包括执行此修改的会话)。默认情况下,ALTER SYSTEM命令会立即生效,但是有些参数不能“立即”修改,只能为新建立的会话修改这些参数。可以使用以下查询来看看哪些参数要求必须使用deferred:
select name from v$parameter where ISSYS_MODIFIABLE = 'DEFERRED';
alter system set sort_area_size = 65536 deferred;
将参数更改为默认值,sid是必须的
alter system reset sort_area_size scope=spfile sid='*';

假设你能读取USER_DUMP_DEST目录,就可以使用会话参数TRACEFILE_IDENTIFIER。采用这种方法,可以为跟踪文件名增加一个可以惟一标识的串。
alter session set tracefile_identifier = 'Look_For_Me';
alter session set sql_trace=true;

段(segment)就是占用存储空间的数据库对象。
CREATE TABLE T ( x int, y date ) cluster MY_CLUSTER则不会创建任何段。

区段(extent)是文件中一个逻辑上连续分配的空间。
每个段都至少有一个区段,有些对象可能还需要至少两个区段(回 滚段就至少需要两个区段)。

块(block)是Oracle中最小的空间分配单位。
一个段由一个或多个区段组成,区段则由连续分配的一些块组成。

块首部(block header) 包含块类型的有关信息(表块、索引块等)、块上发生的活动事务和过去事务的相关信息(仅事务管理的块有此信息,例如临时排序块就没有事务信息),以及块在 磁盘上的地址(位置)。块中接下来两部分是表目录和行目录,最常见的数据库块中(即堆组织表的数据块)都有这两部分。第10章将更详细地介绍数据库表类型,不过,现在知道大多数表都是这种类型就足够了。如果有表目录(table directory),则其中会包含把行存储在这个块上的表的有关信息(可能一个块上存储了多个表的数据)。行目录(row directory)包含块中行的描述信息。这是一个指针数组,指向块中数据部分中的行。块中的这3部分统称为块开销(block overhead),这部分空间并不用于存放数据,而是由Oracle用来管理块本身。块中余下的两部分就很清楚了:块上可能有一个空闲空间(free space),通常还会有一个目前已经存放数据的已用空间(used space)。

临时表空间则不同,你不能在其中创建自己的永久对象。实际上根本的区别只有这一条;空间还是在数据字典表中管理。不过,一旦在临时表空间中分配了一个区段,系统就会一直持有(也就是说,不会把空间交回)。下一次有人出于某种目的在临时表空间中请求空间时,Oracle会 在其内部的已分配区段列表中查找已经分配的区段。如果找到,就会直接重用,否则还是用老办法来分配一个区段。采用这种方式,一旦数据库启动,并运行一段时 间,临时段看上去就好像满了,但是实际上只是“已分配”。里面都是空闲区段,它们的管理完全不同。当有人需要临时空间时,Oracle会在内存中的数据结构里查找空间,而不是执行代价昂贵的递归SQL。

对于本地管理表空间,会使用每个数据文件中存储的一个位图来管理区段。
如果SYSTEM是本地管理的,那么该数据库中所有其他表空间也会是本地管理的。

Oracle使用临时文件来存储大规模排序操作和散列操作的中间结果,如果RAM中没有足够的空间,还会用临时文件存储全局临时表数据,或结果集数据。

对临时文件并不生成redo日志,不过可以生成undo日志。由于UNDO总是受redo的“保护”,因此,这就会生成使用临时表的redo日志。

在重用重做日志之前,失败时应该不需要重做日志文件的内容。如果Oracle不能肯定这一点,也就是说,它不清楚是否真的不需要日志文件的内容,就会暂时挂起数据库中的操作,确保将缓存中的数据(即redo“保护”的数据)安全地写入磁盘本身(建立检查点)。一旦Oracle能肯定这一点,再恢复处理,并重用重做文件。

建立检查点就是把脏块(已修改的块)从缓冲区缓存写至磁盘。
在填满日志文件1并切换到日志文件2时,Oracle就会启动一个检查点。此时,DBWn开始将日志文件组1所保护的所有脏块写至磁盘。在DBWn把该日志文件保护的所有块刷新输出之前,Oracle不能重用这个日志文件。

如果你想启动一个实例进行装载,并打开一个数据库,根据定义,在连接的另一端实际上“还没有数据库”,也无 法从中查找认证的详细信息。这就是一个鸡生蛋还是蛋生鸡的问题。因此密码文件“应运而生”。密码文件保存了一个用户名和密码列表,这些用户名和密码分别对 应于可以通过网络远程认证为SYSDBA的用户。Oracle必须使用这个文件来认证用户,而不是数据库中存储的正常密码列表。

修改跟踪文件(change tracking file)是一个可选的文件,这是Oracle 10g 企业版中新增的。这个文件惟一的目的是跟踪自上一个增量备份以来哪些块已经修改。采用这种方式,恢复管理器(Recovery Manager,RMAN)工具就能只备份确实有变化的数据库块,而不必读取整个数据库。
alter database enable block change tracking
 using file
 '/home/ora10gr1/product/10.1.0/oradata/ora10gr1/ORA10GR1/changed_blocks.bct';
alter database disable block change tracking;

闪回日志文件(flashback log file)简称为闪回日志(flashback log),这是Oracle 10g 中为支持FLASHBACK DATABASE命令而引入的,也是Oracle 10g企业版的一个新特性。闪回日志包含已修改数据库块的“前映像”,可用于将数据库返回(恢复)到该时间点之前的状态。

用户全局区(User Global Area,UGA):这个内存区与特定的会话相关联。它可能在SGA中分配,也可能在PGA中分配,这取决于是用共享服务器还是用专用服务器来连接数据库。如果使用共享服务器,UGA就在SGA中分配;如果使用专用服务器,UGA就会在PGA(即进程内存区)中。

PGA包含进程内存,还可能包含UGA。PGA内存中的其他区通常用于完成内存中的排序、位图合并以及散列。可以肯定地说,除了UGA内存,这些区在PGA中的比重最大。

如果采用手动PGA内存管理,有些参数对PGA大小的影响最大,这是指PGA中除了会话为PL/SQL表和其他变量分配的内存以外的部分,这些参数如下:
? SORT_AREA_SIZE:在信息换出到磁盘之前,用于对信息排序的RAM总量。
? SORT_AREA_RETAINED_SIZE:排序完成后用于保存已排序数据的内存总量。也就是说,如果SORT_AREA_SIZE是512 KB,SORT_AREA_RETAINED_SIZE是256 KB,那么服务器进程最初处理查询时会用512 KB的内存对数据排序。等到排序完成时,排序区会“收缩”为256 KB,这256 KB内存中放不下的已排序数据会写出到临时表空间中。
? HASH_AREA_SIZE:服务器进程在内存中存储散列表所用的内存量。散列联结时会使用这些散列表结构,通常把一个大集合与另一个集合联结时就会用到这些结构。两个集合中较小的一个会散列到内存中,散列区中放不下的部分都会通过联结键存储在临时表空间中。
SORT_AREA_SIZE~SORT_AREA_ RETAINED_SIZE这部分内存一般从PGA分配,SORT_AREA_RETAINED_SIZE这部分内存会在UGA中分配。

在使用*_AREA_SIZE参数时,需要记住以下重要的几点:
? 这些参数控制着SORT、HASH和/或BITMAP MERGE操作所用的最大内存量。
? 一个查询可能有多个操作,这些操作可能都要使用这个内存,这样会创建多个排序/散列区。要记住,可以同时打开多个游标,每个游标都有自己的SORT_AREA_RETAINED需求。所以,如果把排序区大小设置为10 MB,在会话中实际上可以使用10 MB、100 MB、1 000 MB或更多RAM。这些设置并非对会话的限制;它们只是对一个操作的限制。你的会话中,一个查询可以有多个排序,或者多个查询需要一个排序。
? 这些内存区都是根据需要来分配的。如果像我们一样,将排序区大小设置为1 GB,这并不是说你要分配1 GB的RAM,而只是说,你允许Oracle进程为一个排序/散列操作最多分配1 GB的内存。

SGA是一段大小固定的内存,得到了SGA的大小后你就要在这么多内存中分配所有工作区,所谓工作区(work area)只是排序区和散列区的另一种通用说法。

如何建立和打开自动PGA内存管理。
建立自动PGA内存管理时,需要为两个实例初始化参数确定适当的值:
 WORKAREA_SIZE_POLICY:这个参数可以设置为MANUAL或AUTO,如果是MANUAL,会使用排序区和散列区大小参数来控制分配的内存量;如果是AUTO,分配的内存量会根据数据库中的当前工作负载而变化。默认值是AUTO,这也是推荐的设置。
PGA_AGGREGATE_TARGET:这个参数会控制实例为完成数据排序/散列的所有工作区(即排序区和散列区)总共应分配多少内存。

确定如何分配内存?这个问题很难回答,因为是自动管理,由底层算法确定做什么以及如何进行控制
PGA_AGGREGATE_TARGET是一个上限目标,而不是启动数据库时预分配的内存大小。
串行(非并行查询)会话会使用PGA_AGGREGATE_TARGET中的很少一部分,大约5%或者更少。
一个并行查询最多可以使用PGA_AGGREGATE_TARGET的30%,每个并行进程会在这30%中得到自己的那一份。也就是说,每个并行进程能使用的内存量大约是0.3*PGA_ AGGREGATE_TARGET / (并行进程数)。
随着服务器上工作负载的增加(可能有更多的并发查询和更多的并发用户),分配给各个工作区的PGA内存量会减少。
光是登录动作就会创建0.5MB的PGA。

SGA分为不同的池(pool):
? Java池(Java pool):Java池是为数据库中运行的JVM分配的一段固定大小的内存。在Oracle10g中,Java池可以在数据库启动并运行时在线调整大小。
? 大池(Large pool):共享服务器连接使用大池作为会话内存,并行执行特性使用大池作为消息缓冲区,另外RMAN备份可能使用大池作为磁盘I/O缓冲区。在Oracle 10g 和 9i Release 2中,大池都可以在线调整大小。
? 共享池(Shared pool):共享池包含共享游标(cursor)、存储过程、状态对象、字典缓存和诸如此类的大量其他数据。在Oracle 10g和9i中,共享池都可以在线调整大小。
? 流池(Stream pool):这是Oracle流(Stream)专用的一个内存池,Oracle流是数据库中的一个数据共享工具。这个工具是Oracle 10g中新增的,可以在线调整大小。如果未配置流池,但是使用了流功能,Oracle会使用共享池中至多10%的空间作为流内存。
? “空”池(“Null”pool):这个池其实没有名字。这是块缓冲区(缓存的数据库块)、重做日志缓冲区和“固定SGA”区专用的内存。

不论是使用自动内存管理还是手动内存管理,都会发现各个池的内存以一种称为颗粒(granule,也称区组)的单位来分配。一个颗粒是大小为4 MB、8 MB或16 MB的内存区。颗粒是最小的分配单位,所以如果想要一个5 MB的Java池,而且颗粒大小为4 MB,Oracle实际上会为这个Java池分配8 MB(在4的倍数中,8是大于或等于5的最小的数)

LGWR会在以下某个情况发生时启动对这个区的刷新输出(flush):
? 每3秒一次
? 无论何时有人提交请求
? 要求LGWR切换日志文件
? 重做缓冲区1/3满,或者包含了1 MB的缓存重做日志数据

重做缓冲区的默认大小由LOG_BUFFER参数控制,取值为512 KB和(128 * CPU个数)KB中的较大者。这个区的最小大小取决于操作系统。如果想知道到底是多少,只需将LOG_BUFFER设置为1字节,再重启数据库。

在Oracle的较早版本中,只有一个块缓冲区缓存,所有段的所有块都放在这个区中。从Oracle 8.0开始,可以把SGA中各个段的已缓存块放在3个位置上:
? 默认池(default pool):所有段块一般都在这个池中缓存。这就是原先的缓冲区池(原来也只有一个缓冲区池)。
? 保持池(keep pool):按惯例,访问相当频繁的段会放在这个候选的缓冲区池中,如果把这些段放在默认缓冲区池中,尽管会频繁访问,但仍有可能因为其他段需要空间而老化(aging)。
? 回收池(recycle pool):按惯例,访问很随机的大段可以放在这个候选的缓冲区池中,这些块会导致过量的缓冲区刷新输出,而且不会带来任何好处,因为等你想要再用这个块时,它可能已经老化退出了缓存。要把这些段与默认池和保持池中的段分开,这样就不会导致默认池和保持池中的块老化而退出缓存。

在Oracle8i 及 以上版本中,块缓冲区不再像以前那样移到块列表的最前面;而是原地留在块列表中,只是递增它的接触计数。不过,过一段时间后,块会很自然地在列表中“移 动”。这里把“移动”一词用引号括起来,这是因为块并不是物理地移动;只是因为维护了多个指向块的列表,所以块会在这些列表间“移动”。例如,已修改的块 由脏列表指示(要由DBWn写至磁盘)。过一段时间要重用块时,如果缓冲区缓存满了,就要将接触计数较小的某个块释放,将其“放回到”新数据块列表的接近于中间的位置。

现在,由于我还没有配置一个16 KB的缓存,所以无法创建这样一个表空间。要解决这个问题,可以在以下方法中选择一种。我可以设置DB_16K_CACHE_SIZE参数,并重启数据库。也可以缩小另外的某个SGA组件,从而在现有的SGA中腾出空间来建立一个16 KB的缓存。或者,如果SGA_MAX_SIZE参数大于当前的SGA大小,我还可以直接分配一个16 KB的缓存。

大池专门用于以下情况:
? 共享服务器连接,用于在SGA中分配UGA区
? 语句的并行执行,允许分配进程间的消息缓冲区,这些缓冲区用于协调并行查询服务器。
? 备份,在某些情况下用于RMAN磁盘I/O 缓冲区。

共享池根据LRU来管理内存,这对于缓存和重用数据很合适。不过,大块内存分配则是得到一块内存后加以使用,然后就到此为止,没有必要缓存这个内存。
大池中分配的内存在堆上管理,这与C语言通过malloc()和free()管理内存很相似。一旦“释放”了一块内存,它就能由其他进程使用。在共享池中,实际上没有释放内存块的概念。你只是分配内存,然后使用,再停止使用而已。过一段时间,如果需要重用那个内存,Oracle会让你的内存块老化。

在Oracle 10g中,与内存相关的参数可以归为两类:
? 自动调优的SGA参数:目前这些参数包括DB_CACHE_SIZE、SHARED_POOL_SIZE、LARGE_POOL_SIZE和JAVA_POOL_SIZE。
? 手动SGA参数:这些参数包括LOG_BUFFER、STREAMS_POOL、DB_NK_CACHE_SIZE、DB_KEEP_CACHE_SIZE和DB_RECYCLE_CACHE_SIZE。

随着时间的推移,当实例的内存需求越来越确定时,各个SGA组件的大小也越来越固定。即便数据库关闭后又启动,数据库还能记得组件的大小,因此不必每次都从头再来确定实例的正确大小。这是通过4个带双下划线的参数做到的:__DB_CACHE_SIZE、__JAVA_POOL_SIZE、__LARGE_POOL_SIZE和__SHARED_POOL_SIZE。如果正常或立即关闭数据库,则数据库会把这些值记录到存储参数文件(SPFILE)中,并在启动时再使用这些值来设置各个区的默认大小。
另外,如果知道4个区中某个区的最小值,那么除了设置SGA_TARGET外,还可以设置这个参数。实例会使用你的设置作为下界(即这个区可能的最小大小)。

连接(connection)就是客户进程与Oracle实例之间的一条物理路径(例如,客户与实例之间的一个网络连接)。会话(session)则不同,这是数据库中的一个逻辑实体,客户进程可以在会话上执行SQL等。多个独立的会话可以与一个连接相关联,这些会话甚至可以独立于连接存在。

Oracle专用/共享服务器进程解析查询,并把它放在共享池中(或者最好能发现这个查询已经在共享池中)。这个进程要提出查询计划,如果必要,还要执行这个查询计划,可能在缓冲区缓存中找到必要的数据,或者将数据从磁盘读入缓冲区缓存中。

客户应用中链接着Oracle库,这些库提供了与数据库通信所需的API。这些API知道如何向数据库提交查询,并处理返回的游标。它们知道如何把你的请求打包为网络调用,专用服务器则知道如何将这些网络调用解开。这部分软件称为Oracle Net。
从V$PROCESS得到的SPID是执行该查询时所用进程的操作系统PID。

连接(connection):连接是从客户到Oracle实例的一条物理路径。连接可以在网络上建立,或者通过IPC机制建立。通常会在客户进程与一个专用服务器或一个调度器之间建立连接。不过,如果使用Oracle的连接管理器(Connection Manager ,CMAN),还可以在客户和CMAN之间以及CMAN和数据库之间建立连接。CMAN的介绍超出了本书的范围,不过Oracle Net Services Administrator’s Guide(可以从http://otn.oracle.com免费得到)对CMAN有详细的说明。
? 会话(session):会话是实例中存在的一个逻辑实体。这就是你的会话状态(session state),也就是表示特定会话的一组内存中的数据结构。提到“数据库连接”时,大多数人首先想到的就是“会话”。你要在服务器中的会话上执行SQL、提交事务和运行存储过程。

这两个会话都使用同一个专用服务器进程,从它们都有同样的PADDR值就能看出这一点。从操作系统也可以得到确认,因为没有创建新的进程,对这两个会话只使用了一个进程(一条连接)。需要注意,其中一个会话(原来的会话)是ACTIVE(活动的)。这是有道理的:它正在运行查询来显示这个信息,所以它当然是活动的。但是那个INACTIVE(不活动的)会话呢?那个会话要做什么?这就是 AUTOTRACE 会话,它的任务是“监视”我们的实际会话,并报告它做了什么。
在SQL*Plus 中启用(打开)AUTOTRACE时,如果我们执行DML操作(INSERT、UPDATE、DELETE、SELECT和MERGE),SQL*Plus会完成以下动作:
(1) 如果还不存在辅助会话[1],它会使用当前连接创建一个新会话。
(2) 要求这个新会话查询V$SESSTAT视图来记住实际会话(即运行DML的会话)的初始统计值。这与第4章中watch_stat.sql脚本完成的功能非常相似。
(3) 在原会话中运行DML操作。
(4) DML语句执行结束后,SQL*Plus会请求另外那个会话(即“监视”会话)再次查询V$SESSTAT,并生成前面所示的报告,显示出原会话(执行DML的会话)的统计结果之差。

使用共享服务器时,你可能还会观察到另一个有意思的现象,这就是人工死锁(artificial deadlock)。对于共享服务器,多个服务器进程被多个用户所“共享”,用户数量可能相当大。考虑下面这种情况,你有5个共享服务器,并建立了100个用户会话。现在,一个时间点上最多可以有5个用户会话是活动的。假设其中一个用户会话更新了某一行,但没有提交。正当这个用户呆坐在那里对是否修改还有些迟疑时,可能又有另外5个用户会话力图锁住这一行。当然,这5个会话会被阻塞,只能耐心地等待这一行可用。现在,原来的用户会话(它持有这一行的锁)试图提交事务,相应地释放行上的锁。这个用户会话发现所有共享服务器都已经被那5个 等待的会话所垄断。这就出现了一个人工死锁的情况:锁的持有者永远也拿不到共享服务器来完成提交,除非某个等待的会话放弃其共享服务器。但是,除非等待的 会话所等待的是一个有超时时间的锁,否则它们绝对不会放弃其共享服务器(当然,你也可以让管理员通过一个专用服务器“杀死”(撤销)等待的会话来摆脱这种 困境)。

共享服务器主要为我们做3件事:减少操作系统进程/线程数,刻意地限制并发度,以及减少系统所需的内存。

Oracle实例包括两部分:SGA和 一组后台进程。
有两类后台进程:有一个中心(focused)任务的进程(如前所述)以及完成各种其他任务的进程(即工具进程)。

1. PMON:进程监视器(Process Monitor)
这个进程负责在出现异常中止的连接之后完成清理。PMON还负责监视其他的Oracle后台进程,并在必要时(如果可能的话)重启这些后台进程,也可能适当地终止实例。例如,如果数据库日志写入器进程(LGWR)失败,就最好让实例失败。这是一个严重的错误,最安全的做法就是立即终止实例,并根据正常的恢复来修正数据.
PMON还会为实例做另一件事,这就是向Oracle TNS监听器注册这个实例。实例启动时,PMON进程会询问公认的端口地址(除非直接指定),来查看是否启动并运行了一个监听器。Oracle使用的公认/默认端口是1521。如果此时监听器在另外某个端口上启动会怎么样呢?在这种情况下,原理是一样的,只不过需要设置LOCAL_LISTENER参数来显式地指定监听器地址。如果数据库实例启动时有监听器在运行,PMON会与这个监听器通信,并向它传递相关的参数,如服务名和实例的负载度量等。如果监听器未启动,PMON则会定期地试图与之联系来注册实例。

SMON进程要完成所有“系统级”任务。PMON感兴趣的是单个的进程,而SMON与之不同,它以系统级为出发点,这是一种数据库“垃圾收集器”。SMON所做的工作包括:
? 清理临时空间
? 合并空闲空间
只有当字典管理的表空间有一个默认的存储子句,而且pctincrease设置为一个非0值时,才会出现空闲空间的合并。
? 针对原来不可用的文件恢复活动的事务
例如,磁盘上的文件可能不可用或者未装载,当文件确实可用时,SMON就会由此恢复事务。
? 执行RAC中失败节点的实例恢复
? 清理OBJ$
? 收缩回滚段
? “离线”回滚段:DBA有可能让一个有活动事务的回滚段离线(offline),或置为不可用。也有可能活动事务会使用离线的回滚段。在这种情况下,回滚段并没有真正离线;它只是标记为“将要离线”。在后台,SMON会定期尝试真正将其置为离线,直至成功为止。

RECO:分布式数据库恢复
RECO有一个很中心的任务:由于两段提交(two-phase commit,2PC)期间的崩溃或连接丢失等原因,有些事务可能会保持准备状态,这个进程就是要恢复这些事务。2PC是一种分布式协议,允许影响多个不同数据库的修改实现原子提交。它力图在提交之前尽可能地关闭分布式失败窗口[4]。如果在N个数据库之间采用2PC,其中一个数据库(通常是客户最初登录的那个数据库,但也不一定)将成为协调器(coordinator)。这个站点会询问其他N1个站点是否准备提交。实际上,这个站点会转向另外这N1个站点,问它们是否准备好提交。这N1个站点都会返回其“准备就绪状态”,报告为YES或NO[5]。如果任何一个站点投票(报告)NO,整个事务都要回滚。如果所有站点都投票YES,站点协调器就会广播一条消息,使这N1个站点真正完成提交(提交得到持久地存储)。

CKPT:检查点进程(Checkpoint Process)
检查点进程并不像它的名字所暗示的那样真的建立检查点(检查点在第3章介绍重做日志一节中已经讨论过),建立检查点主要是DBWn的任务。CKPT只是更新数据文件的文件首部,以辅助真正建立检查点的进程(DBWn)

数据库块写入器(DBWn)是负责将脏块写入磁盘的后台进程。DBWn会写出缓冲区缓存中的脏块,通常是为了在缓存中腾出更多的空间(释放缓冲区来读入其他数据),或者是为了推进检查点(将在线重做日志文件中的位置前移,如果出现失败,Oracle会从这个位置开始读取来恢复实例)。Oracle切换日志文件时就会标记(建立)一个检查点。Oracle 需要推进检查点,推进检查点后,就不再需要它刚填满的在线重做日志文件了。如果需要重用这个重做日志文件,而此时它还依赖于原来的重做日志文件,我们就会得到一个“检查点未完成”消息,而必须等待。

关于DBWn,还有最后一点需要说明。根据定义,块写入器进程会把块写出到所有磁盘,即分散在各个磁盘上;也就是说,DBWn会做大量的分散写(scattered write) 。执行一个更新时,你会修改多处存储的索引块,还可能修改随机地分布在磁盘上的数据块。另一方面,LGWR则是向重做日志完成大量的顺序写(sequential write) 。这是一个很重要的区别,Oracle之所以不仅有一个重做日志和LGWR进程,还有DBWn进程,其原因就在于此。分散写比顺序写慢多了。

LGWR进程负责将SGA中重做日志缓冲区的内容刷新输出到磁盘。如果满足以下某个条件,就会做这个工作: 
? 每3秒会刷新输出一次 
? 任何事务发出一个提交时 
? 重做日志缓冲区1/3 满,或者已经包含1 MB的缓冲数据

ARCn 进程的任务是:当 LGWR 将在线重做日志文件填满时,就将其复制到另一个位置。此后这些归档的重做日志文件可以用于完成介质恢复。

I/O从属进程用于为不支持异步I/O的系统或设备模拟异步I/O。例如,磁带设备(相当慢)就不支持异步 I/O。通过使用 I/O 从属进程,可以让磁带机模仿通常只为磁盘驱动器提供的功能。就好像支持真正的异步 I/O 一样,写设备的进程(调用者)会收集大量数据,并交由写入器写出。数据成功地写出时,写入器(此时写入器是 I/O 从属进程,而不是操作系统)会通知原来的调用者,调用者则会从要写的数据列表中删除这批数据。采用这种方式,可以得到更高的吞吐量,这是因为会由 I/O 从属进程来等待慢速的设备,而原来的调用进程得以脱身,可以做其他重要的工作来收集下一次要写的数据。
I/O从属进程在Oracle中有两个用途。DBWn和LGWR可以利用I/O 从属进程来模拟异步I/O,另外RMAN写磁带时也可能利用I/O从属进程。
有两个参数控制着I/O从属进程的使用:
? BACKUP_TAPE_IO_SLAVES:这个参数指定RMAN是否使用I/O从属进程将数据备份、复制或恢复到磁带上。由于这个参数是围绕着磁带设备设计的,而且磁带设备一次只能由一个进程访问,所以这个参数是一个布尔值,而不是所用从属进程的个数(这可能出乎你的意料)。 RMAN会为所用的物理设备启动多个必要的从属进程。BACKUP_TAPE_IO_SLAVES = TRUE 时,则使用一个I/O从属进程从磁带设备读写。如果这个参数为FALSE(默认值) ,就不会使用I/O从属进程完成备份。相反,完成备份的专用服务器进程会直接访问磁带设备。 
? DBWR_IO_SLAVES:这个参数指定了DBW0进程所用I/O从属进程的个数。DBW0进程及其从属进程总是将缓冲区缓存中的脏块写至磁盘。这个值默认为0,表示不使用I/O从属进程。注意,如果将这个参数设置为一个非0的值,LGWR和ARCH也会使用其自己的I/O从属进程,LGWR和ARCH最多允许4 个I/O从属进程。

锁(lock)机制用于管理对共享资源的并发访问。共有两种锁定策略:悲观锁定或乐观锁定。
悲观锁定(pessimistic locking)仅用于有状态(stateful)或有连接(connected)环境,也就是说,你的应用与数据库有一条连续的连接,而且至少在事务生存期中只有你一个人使用这条连接.

ORA_ROWSCN建立在内部Oracle系统时钟(SCN)基础上。默认情况下,一个块上的多行会共享相同的ORA_ROWSCN值。如果更新一个块上的某一行,而且这个块上还有另外50行,那么这些行的ORA_ROWSCN也会推进.
从Oracle 10g开始,还可以利用ROWDEPENDENCIES特性用ORA_ROWSCN来实现一种有效的乐观锁定技术。它会为每行增加6字节的开销(所以与自己增加版本列的方法(即DIY版本列方法)相比,并不会节省空间),而实际上,也正是因为这个原因,所以需要重新创建表,而不只是简单地ALTER TABLE:必须修改物理块结构来适应这个特性。可以使用DBMS_REDEFINITION中的在线重建功能来启用ROWDEPENDENCIES.

尽管Oracle有无限多个行级锁,但是enqueue锁(这是一种队列锁)的个数则是有限的。如果在会话中插入大量行,而没有提交,可能就会发现创建了太多的enqueue队列锁,而耗尽了系统的队列资源(超出了ENQUEUE_RESOURCES系统参数设置的最大值),因为每行都会创建另一个enqueue锁。如果确实发生了这种情况,就需要增大ENQUEUE_RESOURCES参数的值。

不论采用哪一种锁定方法都可以这样做。不论是悲观锁定还是乐观锁定都可以利用SELECT FOR UPDATE NOWAIT查询来验证行未被修改。悲观锁定会在用户有意修改数据那一刻使用这条语句。乐观锁定则在即将在数据库中更新数据时使用这条语句。

在以下两种情况下,Oracle在修改父表后会对子表加一个全表锁:
? 如果更新了父表的主键(倘若遵循关系数据库的原则,即主键应当是不可变的,这种情况就很少见),由于外键上没有索引,所以子表会被锁住。
? 如果删除了父表中的一行,整个子表也会被锁住(由于外键上没有索引)

Oracle从来不会升级锁,但是它会执行锁转换(lock conversion)或锁提升(lock promotion) ,这些词通常会与锁升级混淆。
Oracle会尽可能地在最低级别锁定(也就是说,限制最少的锁) ,如果必要,会把这个锁转换为一个更受限的级别。例如,如果用 FOR  UPDATE 子句从表中选择一行,就会创建两个锁。一个锁放在所选的行上(这是一个排他锁;任何人都不能以独占模式锁定这一行) 。另一个锁是ROW SHARE TABLE 锁,放在表本身上。这个锁能防止其他会话在表上放置一个排他锁,举例来说,这样能相应地防止这些会话改变表的结构。另一个会话可以修改这个表中的任何其他行,而不会有冲突。假设表中有一个锁定的行,这样就可以成功执行尽可能多的命令。

Oracle中主要有3类锁,具体是: 
? DML锁(DML lock) :DML代表数据操纵语言(Data Manipulation Language) 。一般来讲,这表示 SELECT、INSERT、UPDATE、MERGE 和 DELETE 语句。DML 锁机制允许并发执行数据修改。例如,DML锁可能是特定数据行上的锁,或者是锁定表中所有行的表级锁。 
? DDL锁(DDL lock) :DDL代表数据定义语言(Data Definition Language) ,如 CREATE 和ALTER语句等。DDL锁可以保护对象结构定义。 
? 内部锁和闩:Oracle 使用这些锁来保护其内部数据结构。例如,Oracle 解析一个查询并生成优化的查询计划时,它会把库缓存“临时闩” ,将计划放在那里,以供其他会话使用。闩(latch)是Oracle采用的一种轻量级的低级串行化设备,功能上类似于锁。不要被“轻量级”这个词搞糊涂或蒙骗了,你会看到,闩是数据库中导致竞争的一个常见原因。轻量级指的是闩的实现,而不是闩的作用。

由于闩为数据的一个属性,Oracle不需要传统的锁管理器。事务只是找到数据[5],如果数据还没有被锁定,则对其锁定。有意思的是,找到数据时,它可能看上去被锁住了,但实际上并非如此。在Oracle中对数据行锁定时,行指向事务ID的一个副本,事务ID存储在包含数据的块中,释放锁时,事务ID却会保留下来。这个事务ID是事务所独有的,表示了回滚段号、槽和序列号。事务ID留在包含数据行的块上,可以告诉其他会话:你“拥有”这个数据(并非块上的所有数据都是你的,只是你修改的那一行“归你所有”)。另一个会话到来时,它会看到锁ID,由于锁ID表示一个事务,所以可以很快地查看持有这个锁的事务是否还是活动的。如果锁不活动,则允许会话访问这个数据。如果锁还是活动的,会话就会要求一旦释放锁就得到通知。因此,这就有了一个排队机制:请求锁的会话会排队,等待目前拥有锁的事务执行,然后得到数据。

这里使用了3个V$ 表:
? V$TRANSACTION,对应每个活动事务都包含一个条目。
? V$SESSION,显示已经登录的会话。
? V$LOCK,对应持有所有enqueue队列锁以及正在等待锁的会话,都分别包含一个条目。这并不是说,对于表中被会话锁定的每一行,这个视图中就有相应的一行。你不会看到这种情况。如前所述,不存在行级锁的一个主列表。如果某个会话将EMP表中的一行锁定,V$LOCK视图中就有对应这个会话的一行来指示这一事实。如果一个会话锁定了EMP表中的数百万行,V$LOCK视图中对应这个会话还是只有一行。这个视图显示了各个会话有哪些队列锁。

对于锁定了该块中某些数据的各个“实际”事务,在这个事务表中都有一个相应的条目。这个结构的大小由创建对象时CREATE语句上的两个物理属性参数决定:
? INITRANS:这个结构初始的预分配大小。对于索引和表,这个大小默认为2(不过我已经提出,Oracle SQL Reference手册中与此有关的说明有问题)。
? MAXTRANS:这个结构可以扩缩到的最大大小。它默认为255,在实际中,最小值为2。在Oracle 10g中,这个设置已经废弃了,所以不再使用。这个版本中的MAXTRANS总是255。在Oracle 10g中,只要块上的空间允许,即使设置了MAXTRANS,Oracle也会不受约束地扩大事务表。

默认情况下,每个块最开始都有两个事务槽。一个块上同时的活动事务数受MAXTRANS值的约束,另外也受块上空间可用性的限制。如果没有足够的空间来扩大这个结构,块上就无法得到255个并发事务。

如果INITRANS设置得很低,而且块上没有足够的空间来动态地扩缩事务,也会出现阻塞。大多数情况下,INITRANS的默认值2就足够了,因为事务表会动态扩大(只要空间允许)。但是在某些环境中,可能需要加大这个设置来提高并发性,并减少等待。比如,在频繁修改的表上就可能要增加INITRANS设置,或者更常见的是,对于频繁修改的索引也可能需要这么做,因为索引块中的行一般比表中的行多。你可能需要增加PCTFREE(见第10章的讨论)或INITRANS,从而在块上提前预留足够的空间以应付可能的并发事务数。尤其是,如果你预料到块开始时几乎是满的(这说明块上没有空间来动态扩缩事务结构),则更需要增加PCTFREE或INITRANS。

系统中允许的TM锁总数可以由你来配置(有关细节请见Oracle Database Reference手册中的DML_LOCKS参数定义)。实际上,这个数可能设置为0。但这并不是说你的数据库变成了一个只读数据库(没有锁),而是说不允许DDL。在非常专业的应用(如RAC实现)中,这一点就很有用,可以减少实例内可能发生的协调次数。通过使用ALTER TABLE TABLENAME DISABLE TABLE LOCK命令,还可以逐对象地禁用TM锁。这是一种快捷方法,可以使意外删除表的“难度更大”,因为在删除表之前,你必须重新启用表锁。还能用它来检测由于外键未加索引而导致的全表锁。

有3种类型的DDL锁:
? 排他DDL锁(Exclusive DDL lock):这会防止其他会话得到它们自己的DDL锁或TM(DML)锁。这说明,在DDL操作期间你可以查询一个表,但是无法以任何方式修改这个表。
? 共享DDL锁(Share DDL lock):这些锁会保护所引用对象的结构,使之不会被其他会话修改,但是允许修改数据。例如,在创建存储的编译对象(如过程和视图)时,会对依赖的对象加这种共享DDL锁
? 可中断解析锁(Breakable parse locks):这些锁允许一个对象(如共享池中缓存的一个查询计划)向另外某个对象注册其依赖性。如果在被依赖的对象上执行DDL,Oracle会查看已经对该对象注册了依赖性的对象列表,并使这些对象无效。因此,这些锁是“可中断的”,它们不能防止DDL出现。例如,你的会话解析一条语句时,对于该语句引用的每一个对象都会加一个解析锁。加这些锁的目的是:如果以某种方式删除或修改了一个被引用的对象,可以将共享池中已解析的缓存语句置为无效(刷新输出)。

闩(latch)是轻量级的串行化设备,用于协调对共享数据结构、对象和文件的多用户访问。
闩用于保护某些内存结构,如数据库块缓冲区缓存或共享池中的库缓存。一般会在内部以一种“愿意等待”(willing to wait)模式请求闩。这说明,如果闩不可用,请求会话会睡眠很短的一段时间,并在以后再次尝试这个操作。还可以采用一种“立即”(immediate)模式请求其他闩,这与SELECT FOR UPDATE NOWAIT的 思想很相似,说明这个进程会做其他事情(如获取另一个与之相当的空闲闩),而不只是坐而等待这个闩直到它可用。由于许多请求者可能会同时等待一个闩,你会 看到一些进程等待的时间比其他进程要长一些。闩的分配相当随机,这要看运气好坏了。闩释放后,紧接着不论哪个会话请求闩都会得到它。等待闩的会话不会排 队,只是一大堆会话在不断地重试。

队列锁(enqueue)在前面已经讨论过,这也是一种更复杂的串行化设备,例如,在更新数据库表中的行时就会使用队列锁。与闩的区别在于,队列锁允许请求者“排队”等待资源。对于闩请求,请求者会话会立即得到通知是否得到了闩。而对于队列锁,请求者会话会阻塞,直至真正得到锁。

等待闩可能是一个代价很高的操作。如果闩不是立即可用的,我们就得等待(大多数情况下都是如此),在一台多CPU机器上,我们的会话就会自旋(spin),也就是说,在循环中反复地尝试来得到闩。出现自旋的原因是,上下文切换(context switching)的开销很大(上下文切换是指被“踢出”CPU,然后又必须调度回CPU)。所以,如果进程不能立即得到闩,我们就会一直呆在CPU上,并立即再次尝试,而不是先睡眠,放弃CPU,等到必须调度回CPU时才再次尝试。

我们需要闩定共享池来解析SQL语句,为什么要闩定共享池呢?因为这是一个共享数据结构,别人在读取这个共享资源时,我们不能对其进行修改,另外如果别人正在修改它,我们就不能读取。

并发控制(concurrency control)是数据库提供的函数集合,允许多个人同时访问和修改数据。

多版本是指,Oracle能同时物化多个版本的数据,这也是Oracle提供数据读一致视图的机制(读一致视图即read-consistent view,是指相对于某个时间点有一致的结果)。多版本有一个很好的副作用,即数据的读取器(reader)绝对不会被数据的写入器(writer)所阻塞。换句话说,写不会阻塞读。这是Oracle与其他数据库之间的一个根本区别。在Oracle中,如果一个查询只是读取信息,那么永远也不会被阻塞。它不会与其他会话发生死锁,而且不可能得到数据库中根本不存在的答案。

Oracle一般有三种隔离级别:read commited,repeatable read;serializable。SERIALIZABLE是在事务级别的,不容许脏读,不可重复读,幻像读。 除此外还有一种称为READ ONLY的隔离级别。它有着SERIALIZABLE隔离级别的所有性质,另外还会限制修改。需要指出,SYS用户(或作为SYSDBA连接的用户)不能有READ ONLY或SERIALIZABLE事务。在这方面,SYS很特殊。

SERIALIZABLE隔离性是有代价的,可能会得到以下错误:
ERROR at line 1:
ORA-08177: can't serialize access for this transaction
只要你试图更新某一行,而这一行自事务开始后已经修改,你就会得到这个消息。
Oracle试图完全在行级得到这种隔离性,但是即使你想修改的行尚未被别人修改后,也可能得到一个ORA-01877错误。发生ORA-01877错误的原因可能是:包含这一行的块上有其他行正在被修改。

设置隔离级别:alter session set isolation_level=serializable;

Oracle能把同一个块的多个版本保存在缓冲区缓存中。你撤销对这个块的修改时,也就把相应的版本留在缓存中了,这样以后执行查询时就可以直接访问。

Oracle处理修改语句时会完成两类块获取。它会执行:
? 一致读(Consistent read):“发现”要修改的行时,所完成的获取就是一致读。
? 当前读(Current read):得到块来实际更新所要修改的行时,所完成的获取就是当前读。

ops$tkyte@ORA10G> update t set x = x+1 where x > 0;
old.x = 1, old.y = 1
new.x = 2, new.y = 1
old.x = 2, old.y = 1
new.x = 3, new.y = 1
1 row created.
可以看到,行触发器看到这一行有两个版本。行触发器会触发两次:一次提供了行原来的版本以及我们想把原来这个版本修改成什么,另一次提供了最后实际更新的行。由于这是一个BEFORE FOR EACH ROW触发器,Oracle看到了记录的读一致版本,以及我们想对它做的修改。不过,Oracle以当前模式获取块,从而在BEFORE FOR EACH ROW触发器触发之后具体执行更新。它会等待触发器触发后再以当前模式得到块,因为触发器可能会修改:NEW值。因此Oracle在触发器执行之前无法修改这个块,而且触发器的执行可能要花很长时间。由于一次只有一个会话能以当前模式持有一个块;所以Oracle需要对处于当前模式下的时间加以限制。
     触发器触发后,Oracle以当前模式获取这个块,并注意到用来查找这一行的X列已经修改过。由于使用了X来定位这条记录,而且X已经修改,所以数据库决定重启动查询。注意,尽管X从1更新到2,但这并不会使该行不满足条件(X>0);这条UPDATE语句还是会更新这一行。而且,由于X用于定位这一行,而X的一致读值(这里是1)不同于X的当前模式读值(2),所以在重启动查询时,触发器把值X=2(被另一个会话修改之后)看作是:OLD值,而把X=3看作是:NEW值。
     :NEW和:OLD列值在触发器中引用时,也会被Oracle用于完成重启动检查。在触发器中引用:NEW.X和:OLD.X时,会比较X的一致读值和当前读值,并发现二者不同。这就会带来一个重启动。从触发器将这一列的引用去掉后,就没有重启动了。

事务会把数据库从一种一致状态转变为另一种一致状态.
Oracle中的事务体现了所有必要的ACID特征。ACID是以下4个词的缩写:
? 原子性(atomicity):事务中的所有动作要么都发生,要么都不发生。
? 一致性(consistency):事务将数据库从一种一致状态转变为下一种一致状态。
? 隔离性(isolation):一个事务的影响在该事务提交前对其他事务都不可见。
? 持久性(durability):事务一旦提交,其结果就是永久性的。

Oracle中不需要专门的语句来“开始事务”。隐含地,事务会在修改数据的第一条语句处开始(也就是得到TX锁的第一条语句)。也可以使用SET TRANSACTION或DBMS_TRANSACTION包来显示地开始一个事务,如果发出COMMIT或ROLLBACK语句,就会显式地结束一个事务。

一定要显式地使用COMMIT或ROLLBACK来终止你的事务;否则,你使用的工具/环境就会从中挑一个来结束事务。如果正常地退出SQL*Plus会话,而没有提交或回滚事务,SQL*Plus就会认为你希望提交前面做的工作,并为你完成提交。

需要指出到底什么时候检查完整性约束。默认情况下,完整性约束会在整个SQL语句得到处理之后才进行检查。也有一些可延迟的约束允许将完整性约束的验证延迟到应用请求时(发出一个SET CONSTRAINTS ALL IMMEDIATE命令)才完成,或者延迟到发出COMMIT时再检查。

? IMMEDIATE约束:在这种情况下,完整性约束会在整个SQL语句得到处理之后立即检查。注意,这里我用的是“SQL语句”而不只是“语句”。如果一个PL/SQL存储过程中有多条SQL语句,那么在每条SQL语句执行之后都会立即验证其完整性约束,而不是在这个存储过程完成后才检查它。
为什么约束要在SQL语句执行之后才验证呢?为什么不是在SQL语句执行期间验证?这是因为,一条语句可能会使表中的各行暂时地“不一致”,这是很自然的。尽管一条语句全部完成后的最终结果是对的,但如果查看这条语句所做的部分工作,会导致Oracle拒绝这个结果。例如,假设有下面这样一个表:
create table t ( x int unique );
insert into t values ( 1 );
insert into t values ( 2 );
update t set x = x+1;
如果Oracle每更新一行之后都检查约束,那么无论什么时候,UPDATE都有一半的可能性(50%的机会)会失败。由于会以某种顺序来访问T中的行,如果Oracle先更新X=1这一行,那么X就会临时地有一个重复的值,这就会拒绝UPDATE。由于Oracle会耐心等待语句结束(而不是在语句执行期间检查约束),所以这条语句最后会成功,因为等到语句完成时已经不存在重复值了。

? DEFERRABLE约束和级联更新
有了可延迟的约束后,这就变得易如反掌了。代码如下:
create table p ( pk int primary key )
create table c  ( fk constraint c_fk references p(pk) deferrable initially immediate )
insert into p values ( 1 );
insert into c values ( 1 );
我们有一个父表P,还有一个子表C。表C引用了表P,保证这个规则的约束是C_FK(子外键)。这个约束创建为一个DEFERRABLE约束,但是设置为INITIALLY IMMEDIATE。这说明,可以把这个约束延迟到COMMIT或另外某个时间才检查。不过,默认情况下,这个约束在语句级验证。这是可延迟约束最常见的用法。大多数现有的应用不会在COMMIT语句上检查约束冲突,你最好也不要这么做。根据定义,表C与一般的表一样有正常的表现,不过我们可以显式地改变它的行为。下面,在这些表上尝试一些DML,看看会发生什么:
update p set pk = 2;
*
ERROR at line 1:
ORA-02292: integrity constraint (OPS$TKYTE.C_FK) violated - child record found
由于约束是IMMEDIATE模式,这个UPDATE会失败。下面换个模式再试一次:
set constraint c_fk deferred;
update p set pk = 2;
现在更新成功了。为了便于说明,下面将显示如何在提交前显式地检查了一个延迟约束,才中了解我们所做的修改与业务规则是否一致(换句话说,检查目前确实没有 违反约束)。应该在提交之前或者在把控制交给程序的另外某个部分(这一部分可能不希望有延迟约束)之前做这个工作,这是一个很好的主意:
set constraint c_fk immediate;
*
ERROR at line 1:
ORA-02291: integrity constraint (OPS$TKYTE.C_FK) violated - parent key not found
不出所料,它会失败,并立即返回一个错误,因为我们知道以上更新会违反约束。对P的UPDATE没有回滚(否则会违反语句级原子性);它仍在进行。还要注意,我们的事务仍把C_FK当作延迟约束,因为SET CONSTRAINT命令失败了。下面继续将UPDATE级联到C:
update c set fk = 2;
set constraint c_fk immediate;
commit;
这就是级联更新的做法。注意,要延迟一个约束,必须这样来创建它们:先将其删除,再重新创建约束,这样才能把不可延迟的约束改变为可延迟约束。

2PC会限制可能出现的严重错误的窗口(时间窗)。在2PC上“投票“之前,任何分布式错误都会导致所有这点回滚。对于事务的结果来说,这里不存在疑义。在提交或回滚之后,分布式事务的结果同样没有疑义。只有一个非常短的时间窗除外,此时协调器要收集投票结果,只有在这个时候如果失败,结果可能有疑义。
例如,假设有3个站点参与一个事务,其中站点1是协调器。站点1问站点2是否准备好提交,站点2报告说是。站点1再问站点3是否准备好提交,站点3也说准备好了。在这个时间点,站点1就是惟一知道事务结果的站点,它现在要负责把这个结果广播给其他站点。如果现在出现一个错误,比如说网络失败了,站点1掉电,或者其他某个原因,站点2 和站点3就会“挂起“它们就会有所谓的可疑分布式事务(in-doubt distributed transaction)。2PC协议力图尽可能地关闭这个错误窗口,但是无法完全将其关闭。站点2和站点3必须保持事务打开,等待站点1发出的结果通知。如果还记得第5章讨论的体系结构,应该知道这个问题要由RECO进程来解决。有FORCE选项的COMMIT和ROLLBACK在这里就有了用武之地。如果问题的原因是站点1、2和3之间的网络故障,站点2和站点3的DBA可以打电话给站点1的DBA,问他结果是什么,并相应地手动应用提交或回滚。

对于分布式事务中能做的事情,还存在一些限制(不过并不多),这些限制是合理的(在我看来,它们确实是合理的)。其中重要的限制如下:
? 不能在数据库链接上发出COMMIT。也就是说,不能发出COMMIT@remote_site。只能从发起事务的那个站点提交。
? 不能在数据库链接上完成DDL。这是上一个问题带来的直接结果。DDL会提交,而除了发起事务的站点外,你不能从任何其他站点提交,所以不能在数据库链接上完成DDL。
? 不能在数据库链接上发出SAVEPOINT。简单地说,不能在数据库链接发出任务事务控制语句。所有事务控制都有最初打开数据库链接的会话继承得来;对于事务中的分布式实例,不能有不同的事务控制。

如果在一个“正常”的过程中COMMIT,它不仅会持久保留自己的工作,也会使该会话中未完成的工作成为永久性的。不过,如果在一个自治事务过程中完成COMMIT,只会让这个过程本身的工作成为永久性的。

事务的根本原则:一方面,事务应该尽可能短(也就是不应该不必要地建立大事务);另一方面,要根据需要是事务足够大。决定事务大小的关键是数据完整 性,这是本章阐述的一个关键概念。能决定事务大小的惟一因素就是控制系统的业务规则。记住,不是undo空间,不是锁,而是业务规则。

redo(重做信息)是Oracle在在线(或归档)重做日志文件中记录的信息,万一出现失败时可以利用这些数据来“重放”(或重做)事务。undo(撤销信息)是Oracle在undo段中记录的信息,用于取消或回滚事务。

redo用于在失败时重放事务(即恢复事务),undo则用于取消一条语句或一组语句的作用。与redo不同,undo在数据库内部存储在一组特殊的段中,这称为undo段(undo segment)。

“回滚段”(rollback segment)和“undo段“(undo segment)一般认为是同义词。使用手动undo管理时,DBA会创建”回滚段“。使用自动undo管理时,系统将根据需要自动地创建和销毁”undo段“。对于这里的讨论来说,这些词的意图和作用都一样。

前面的INSERT导致将一些块增加到表的高水位线(high-water mark,HWM)之下,这些块没有因为回滚而消失,它们还在那里,而且已经格式化,只不过现在为空。全表扫描必须读取这些块,看看其中是否包含行。这说明,回滚只是一个“将数据库还原“的逻辑操作。数据库并非真的还原成原来的样子,只是逻辑上相同而已。

如果不使用绑定变量,而且频繁地完成硬解析,这会严重地降低并发性,原因是存在库缓存竞争和过量的CPU占用。即使转而使用绑定变量,如果过于频繁地软解析,也会带来大量的开销(导致过多软解析的原因可能是:执意地关闭游标,尽管稍后就会重用这些游标)。必须在必要时才完成操作,COMMIT就是这样的一种操作。最好根据业务需求来确定事务的大小,而不是错误地为了减少数据库上的资源使用而“压缩”事务。

COMMIT的开销存在两个因素:
? 显然会增加与数据库的往返通信。如果每个记录都提交,生成的往返通信量就会大得多。
? 每次提交时,必须等待redo写至磁盘。这会导致“等待”。在这种情况下,等待称为“日志文件同步”(log file sync)。

在数据库中执行COMMIT之前,困难的工作都已经做了。我们已经修改了数据库中的数据,所以99.9%的工作都已经完成。例如,已经发生了以下操作:
? 已经在SGA中生成了undo块。
? 已经在SGA中生成了已修改数据块。
? 已经在SGA中生成了对于前两项的缓存redo。
? 取决于前三项的大小,以及这些工作花费的时间,前面的每个数据(或某些数据)可能已经刷新输出到磁盘。
? 已经得到了所需的全部锁。

执行COMMIT时,余下的工作只是:
? 为事务生成一个SCN。如果你还不熟悉SCN,起码要知道,SCN是Oracle使用的一种简单的计时机制,用于保证事务的顺序,并支持失败恢复。SCN还用于保证数据库中的读一致性和检查点。可以把SCN看作一个钟摆,每次有人COMMIT时,SCN都会增1.
? LGWR将所有余下的缓存重做日志条目写到磁盘,并把SCN记录到在线重做日志文件中。这一步就是真正的COMMIT。如果出现了这一步,即已经提交。事务条目会从V$TRANSACTION中“删除”,这说明我们已经提交。
? V$LOCK中记录这我们的会话持有的锁,这些所都将被释放,而排队等待这些锁的每一个人都会被唤醒,可以继续完成他们的工作。
? 如果事务修改的某些块还在缓冲区缓存中,则会以一种快速的模式访问并“清理”。块清除(Block cleanout)是指清除存储在数据库块首部的与锁相关的信息。实质上讲,我们在清除块上的事务信息,这样下一个访问这个块的人就不用再这么做了。我们采用一种无需生成重做日志信息的方式来完成块清除,这样可以省去以后的大量工作.

ROLLBACK时,要做以下工作:
? 撤销已做的所有修改。其完成方式如下:从undo段读回数据,然后实际上逆向执行前面所做的操作,并将undo条目标记为已用。如果先前插入了一行,ROLLBACK会将其删除。如果更新了一行,回滚就会取消更新。如果删除了一行,回滚将把它再次插入。
? 会话持有的所有锁都将释放,如果有人在排队等待我们持有的锁,就会被唤醒。

除了可以在BEFORE触发器中修改一行的值外,BEFORE和AFTER触发器之间还有没有其他的区别?”嗯,对于这个问题,答案是当然有。BEFORE触发器要额外的redo信息,即使它根本没有修改行中的任何值。
我们会发现:
? BEFORE或AFTER触发器不影响DELETE生成的redo。
? 在Oracle9i Release 2 及以前版本中,BEFORE或AFTER触发器会使INSERT生成同样数量的额外redo。在Oracle 10g中,则不会生成任何额外的redo。
? 在Oracle9i Release 2及以前的所有版本中,UPDATE生成的redo只受BEFORE触发器的影响。AFTER触发器不会增加任何额外的redo。不过,在Oracle 10g中,情况又有所变化。具体表现为:
? 总的来讲,如果一个表没有触发器,对其更新期间生成的redo量总是比Oracle9i及以前版本中要少。看来这是Oracle着力解决的一个关键问题:对于触发器的表,要减少这种表更新所生成的redo量。
? 在Oracle 10g中,如果表有一个BEFORE触发器,则其更新期间生成的redo量比9i中更大。
? 如果表有AFTER触发器,则更新所生成的redo量与9i中一样。

现在你应该知道怎么来估计redo量,这是每一个开发人员应该具备的能力。你可以:
? 估计你的“事务”大小(你要修改多少数据)。
? 在要修改的数据量基础上再加10%~20%的开销,具体增加多大的开销取决于你要修改的行数。修改行越多,增加的开销就越小。
? 对于UPDATE,要把这个估计值加倍。

关于NOLOGGING操作,需要注意以下几点:
? 事实上,还是会生成一定数量的redo。这些redo的作用是保护数据字典。这是不可避免的。与以前(不使用NOLOGGING)相比,尽管生成的redo量要少多了,但是确实会有一些redo。
? NOLOGGING不能避免所有后续操作生成redo。在前面的例子中,我创建的并非不生成日志的表。只是创建表(CREATE TABLE)这一个操作没有生成日志。所有后续的“正常“操作(如INSERT、UPDATE和DELETE)还是会生成日志。其他特殊的操作(如使用SQL*Loader的直接路径加载,或使用INSERT /*+ APPEND */语法的直接路径插入)不生成日志(除非你ALTER这个表,再次启用完全的日志模式)。不过,一般来说,应用对这个表执行的操作都会生成日志。
? 在一个ARCHIVELOG模式的数据库上执行NOLOGGING操作后,必须尽快为受影响的数据文件建立一个新的基准备份,从而避免由于介质失败而丢失对这些对象的后续修改。实际上,我们并不会丢失后来做出的修改,因为这些修改确实在重做日志中;我们真正丢失的只是要应用这些修改的数据(即最初的数据)

如果DBWR还没有完成重做日志所保护数据的检查点(checkpointing),或者ARCH还没有把重做日志文件复制到归档目标,就会发生Checkpoint not complete情况。DBWR或ARCH将得到最大的优先级以将redo块刷新输出的磁盘。完成了检查点或归档之后,一切又回归正常。数据库之所以暂停用户的活动,这是因为此时已经没地方记录用户所做的修改了。Oracle试图重用一个在线重做日志文件,但是由于归档进程尚未完成这个文件的复制(Archival required),所以Oracle必须等待(相应地,最终用户也必须等待),直到能安全地重用这个重做日志文件为止。
要解决这个问题,有几种做法:
? 让DBWR更快一些。让你的DBA对DBWR调优,为此可以启用ASYNC I/O、使用DBWR I/O从属进程,或者使用多个DBWR进程。看看系统产生的I/O,查看是否有一个磁盘(或一组磁盘)“太热”,相应地需要将数据散布开。这个建议对ARCH也适用。这种做法的好处是,你不用付出什么代价就能有所收获,性能会提高,而且不必修改任何逻辑/结构/代码。这种方法确实没有缺点。
? 增加更多重做日志文件。在某些情况下,这会延迟Checkpoint not complete的出现,而且过一段时间后,可以把Checkpoint not complete延迟得足够长,使得这个错误可能根本不会出现(因为你给DBWR留出了足够的活动空间来建立检查点)。这个方法也同样适用于Archival required消息。这种方法的好处是可以消除系统中的“暂停”。其缺点是会消耗更多的磁盘空间,但是在此利远远大于弊。
? 重新创建更大的日志文件。这会扩大填写在线重做日志与重用这个在线重做日志文件之间的时间间隔。如果重做日志文件的使用呈“喷射状”,这种方法同样适用于Archival required消息。倘若一段时间内会大量生成日志(如每晚加载、批处理等),其后一段数据却相当平静,如果有更大的在线重做日志,就能让ARCH在平静的期间有足够的时间“赶上来”。这种方法的优缺点与前面增加更多文件的方法是一样的。另外,它可能会延迟检查点的发生,由于(至少)每个日志切换都会发生检查点,而现在日志切换间隔会更大。
? 让检查点发生得更频繁、更连续。可以使用一个更小的块缓冲区缓存(不太好),或者使用诸如FAST_START_MTTR_TARGET、LOG_CHECKPOINT_INTERVAL和LOG_CHECKPOINT_TIMEOUT之类的参数设置。这会强制DBWR更 频繁地刷新输出脏块。这种方法的好处是,失败恢复的时间会减少。在线重做日志中应用的工作肯定更少。其缺点是,如果经常修改块,可能会更频繁地写至磁盘。 缓冲区缓存本该更有效的,但由于频繁地写磁盘,会导致缓冲区缓存不能充分发挥作用,这可能会影响下一节将讨论的块清除机制。

数据锁实际上是数据的属性,存储在块首部。这就带来一个副作用,下一次访问这个块时,可能必须“清理”这个块,换句话说,要将这些事务信息删除。这个动作会生成redo,并导致变脏(原本并不脏,因为数据本身没有修改),这说明一个简单的SELECT有可能生成redo,而且可能导致完成下一个检查点时将大量的块写至磁盘。应该知道,COMMIT时处理的步骤之一是:如果块还在SGA中,就要再次访问这些块,如果可以访问(没有别人在修改这些块),则对这些块完成清理。这个 活动称为提交清除(commit cleanout),即清除已修改块上事务信息。最理想的是,COMMIT可以完成块清除,这样后面的SELECT(读)就不必再清理了。只有块的UPDATE才会真正清除残余的事务信息,由于UPDATE已经在生成redo,所用注意不到这个清除工作。

在与我们的事务相关的提交列表中,Oracle会记录已修改的块列表。这些列表都有20个块,Oracle会根据需要分配多个这样的列表,直至达到某个临界点。如果我们修改的块加起来超过了块缓冲区缓存大小的10%,Oracle会停止为我们分配新的列表。COMMIT时,Oracle会处理这些包含20个块指针的列表,如果块仍可用,它会执行一个很快的清理。所以,只要我们修改的块数没有超过缓存中总块数的10%,而且块仍在缓存中并且是可用的,Oracle就会在COMMIT时清理这些块。否则,它只会将其忽略(也就是说不清理)。

假设你的所有事务都有适当的大小(完全遵从业务规则的要求,而没有过于频繁地提交),但还是看到了这种日志文件等待,这就有其他原因了。其中最常见的原因如下:
? redo放在一个慢速设备上:磁盘表现不佳。该购买速度更快的磁盘了。
? redo与其他频繁访问的文件放在同一个设备上。redo设计为要采用顺序写,而且要放在专用的设备上。如果系统的其他组件(甚至其他Oracle组件)试图与LGWR同时读写这个设备,你就会遭遇某种程度的竞争。在此,只要有可能,你就会希望确保LGWR拥有这些设备的独占访问权限。
? 已缓冲方式装载日志设备。你在使用一个“cooked”文件系统(而不是RAW磁盘)。操作系统在缓冲数据,而数据库也在缓冲数据(重做日志缓冲区)。这种双缓冲会让速度慢下来。如果可能,应该以一种“直接”方式了装载设备。具体操作依据操作系统和设备的不同而有所变化,但一般都可以直接装载。
? redo采用了一种慢速技术,如RAID-5。RAID-5很合适读,但是用于写时表现则很差。前面已经了解了COMMIT期间会发生什么,我们必须等待LGWR以确保数据写到磁盘上。倘若使用的技术会导致这个工作变慢,这就不是一个好主意。

对于Oracle9i和以上版本,管理系统中的undo有两种方法:
? 自动undo管理(Automatic undo management):采用这种方法,通过UNDO_RETENTION参数告诉Oracle要把undo保留多长时间。Oracle会根据并发工作负载来确定要创建多少个undo段,以及每个undo段应该多大。数据库甚至在运行时可以在各个undo段之间重新分配区段,以满足DBA设置的UNDO_RETENTION目标。这是undo管理的推荐方法。
? 手动undo管理(Manual undo management):采用这种方法的话,要由DBA来完成工作。DBA要根据估计或观察到的工作负载,确定要手动地创建多少个undo段。DBA根据事务量(生成多少undo)和长时间运行查询的长度来确定这些undo段应该多大。

造成ORA-01555:snapshot too old只有两个原因,但是其中之一有一个特例,而且这种特例情况发生得如此频繁,所以我要说存在3个原因:
? undo段太小,不足以在系统上执行工作。
? 你的程序跨COMMIT获取(实际上这是前一点的一个变体)。
? 块清除。

ORA-01555错误的几种解决方案,一般来说可以采用下面的方法:
? 适当地设置参数UNDO_RETENTION(要大于执行运行时间最长的事务所需的时间)。可以用V$UNDOSTAT来确定长时间运行的查询的持续时间。另外,要确保磁盘上已经预留了足够的空间,使undo段能根据所请求的UNDO_RETENTION增大。
? 使用手动undo管理时加大或增加更多的回滚段。这样在长时间运行的查询执行期间,覆盖undo数据的可能性就能降低。这种方法可以解决上述的所有3个问题。
? 减少查询的运行时间(调优)。如果可能的话,这绝对是一个好办法,所以应该首先尝试这种方法。这样就能降低对undo段的需求,不需求太大的undo段。这种方法可以解决上述的所有3个问题。
? 收集相关对象的统计信息。这有助于避免前面所列的第三点。由于大批量的UPDATE或INSERT会导致块清除(block cleanout),所以需要在大批量UPDATE或大量加载之后以某种方式收集统计信息。
? 保证使用的事务“大小适当”。确保没有不必要地过于频繁地提交。

在块清除过程中,如果一个块已被修改,下一个会话访问这个块时,可能必须查看最 后一个修改这个块的事务是否还是活动的。一旦确定该事务不再活动,就会完成块清除,这样另一个会话访问这个块时就不必再历经同样的过程。要完成块清除,Oracle会从块首部确定前一个事务所用的undo段,然后确定从undo首部能不能看出这个块是否已经提交。可以用以下两种方式完成这种确认。一种方式是Oracle可以确定这个事务很久以前就已经提交,它在undo段事务表中的事务槽已经被覆盖。另一种情况是COMMIT SCN还在undo段的事务表中,这说明事务只是稍早前刚提交,其事务槽尚未被覆盖。
要从一个延迟的块清除收到ORA-01555错误,以下条件都必须满足:
? 首先做了一个修改并COMMIT,块没有自动清理(即没有自动完成“提交清除”,例如修改了太多的块,在SGA块缓冲区缓存的10%中放不下)。
? 其他会话没有接触这些块,而且在我们这个“倒霉”的查询(稍后显示)命中这些块之前,任何会话都不会接触它们。
? 开始一个长时间运行的查询。这个查询最后会读其中的一些块。这个查询从SCN t1开始,这就是读一致SCN,必须将数据回滚到这一点来得到读一致性。开始查询时,上述修改事务的事务条目还在undo段的事务表中。
? 查询期间,系统中执行了多个提交。执行事务没有接触执行已修改的块(如果确实接触到,也就不存在问题了)。
? 由于出现了大量的COMMIT,undo段中的事务表要回绕并重用事务槽。最重要的是,将循环地重用原来修改事务的事务条目。另外,系统重用了undo段的区段,以避免对undo段首部块本身的一致读。
? 此外,由于提交太多,undo段中记录的最低SCN现在超过了t1(高于查询的读一致SCN)。




来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/13129975/viewspace-627168/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/13129975/viewspace-627168/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值