Oracle 9i & 10g编程艺术-深入数据库体系结构——第5章:Oracle进程

来源:互联网 发布:酷音铃声 mac 编辑:程序博客网 时间:2024/06/14 08:00

第5章                      Oracle进程 

现在要谈到Oracle体系结构的最后一部分了。我们已经研究了数据库以及构成数据库的物理文件集。讨论Oracle使用的内存时,我们分析了实例的前半部分。Oracle体系结构中还有一个问题没有讲到,这就是构成实例另一半的进程(process)集。

Oracle中的各个进程要完成某个特定的任务或一组任务,每个进程都会分配内部内存(PGA内存)来完成它的任务。Oracle实例主要有3类进程:

q         服务器进程(server process):这些进程根据客户的请求来完成工作。我们已经对专用服务器和共享服务器有了一定的了解。它们就是服务器进程。

q         后台进程(background process):这些进程随数据库而启动,用于完成各种维护任务,如将块写至磁盘、维护在线重做日志、清理异常中止的进程等。

q         从属进程(slave process):这些进程类似于后台进程,不过它们要代表后台进程或服务器进程完成一些额外的工作。

其中一些进程(如数据库块写入器(DBWn)和日志写入器(LGWR))在前面已经提到过,不过现在我们要更详细地介绍这些进程的功能,说明这些进程会做什么,并解释为什么。

注意    这一章谈到“进程”时,实际上要把它理解为两层含义,在某些操作系统(如Windows)上,Oracle使用线程实现,所以在这种操作系统上,就要把我们所说的“进程”理解为“线程”的同义词。在这一章中,“进程”一词既表示进程,也涵盖线程。如果你使用的是一个多进程的Oracle实现,比如说UNIX上的Oracle实现,“进程”就很贴切。如果你使用的是单进程的Oracle实现,如Windows上的Oracle实现,“进程”实际是指“Oracle进程中的线程”。所以,举例来说,当我谈到DBWn进程时,在Windows上就对应为Oracle进程中的DBWn线程 。

5.1   5.1  服务器进程

服务器进程就是代表客户会话完成工作的进程。应用向数据库发送的SQL语句最后就要由这些进程接收并执行。

在第2章中,我们简要介绍了两种Oracle连接,包括:

q         专用服务器(dedicated server)连接,采用专用服务器连接时,会在服务器上得到针对这个连接的一个专用进程。数据库连接与服务器上的一个进程或线程之间存在一对一的映射。

q         共享服务器(shared server)连接,采用共享服务器连接时,多个会话可以共享一个服务器进程池,其中的进程由Oracle实例生成和管理。你所连接的是一个数据库调度器(dispatcher),而不是特意为连接创建的一个专用服务器进程。

注意    有一点很重要,要知道Oracle术语中连接和会话之间的区别。连接(connection)就是客户进程与Oracle实例之间的一条物理路径(例如,客户与实例之间的一个网络连接)。会话(session)则不同,这是数据库中的一个逻辑实体,客户进程可以在会话上执行SQL等。多个独立的会话可以与一个连接相关联,这些会话甚至可以独立于连接存在。稍后将进一步讨论这个问题。

专用服务器进程和共享服务器进程的任务是一样的:要处理你提交的所有SQL。当你向数据库提交一个SELECT * FROM EMP查询时,会有一个Oracle专用/共享服务器进程解析这个查询,并把它放在共享池中(或者最好能发现这个查询已经在共享池中)。这个进程要提出查询计划,如果必要,还要执行这个查询计划,可能在缓冲区缓存中找到必要的数据,或者将数据从磁盘读入缓冲区缓存中。

这些服务器进程是干重活的进程。在很多情况下,你都会发现这些进程占用的系统CPU时间最多,因为正是这些进程来执行排序、汇总、联结等等工作,几乎所有工作都是这些进程做的。

5.1.1             专用服务器连接

在专用服务器模式下,客户连接和服务器进程(或者有可能是线程)之间会有一个一对一的映射。如果一台UNIX主机上有100条专用服务器连接,就会有相应的100个进程在执行。可以用图来说明,如图5-1所示。

客户应用中链接着Oracle库,这些库提供了与数据库通信所需的API。这些API知道如何向数据库提交查询,并处理返回的游标。它们知道如何把你的请求打包为网络调用,专用服务器则知道如何将这些网络调用解开。这部分软件称为Oracle Net,不过在以前的版本中可能称之为SQL*NetNet8。这是一个网络软件/协议,Oracle利用这个软件来支持客户/服务器处理(即使在一个n层体系结构中也会“潜伏”着客户/服务器程序)。不过,即使从技术上讲没有涉及Oracle NetOracle也采用了同样的体系结构。也就是说,即使客户和服务器在同一台机器上,也会采用这种两进程(也称为两任务)体系结构。这个体系结构有两个好处:

5-1  典型的专用服务器连接

q         远程执行(remote execution):客户应用可能在另一台机器上执行(而不是数据库所在的机器),这是很自然的。

q         地址空间隔离(address space isolation):服务器进程可以读写SGA。如果客户进程和服务器进程物理地链接在一起,客户进程中一个错误的指针就能轻松地破坏SGA中的数据结构。

我们在第2章中了解了这些专用服务器如何“产生”,也就是如何由Oracle监听器进程创建它们。这里不再介绍这个过程;不过,我们会简要地说明如果不涉及监听器会是什么情况。这种机制与使用监听器的机制基本上是一样的,但不是由监听器通过fork()/exec()调用(UNIX)或进程间通信IPC调用(Windows)来创建专用服务器,而是由客户进程自己来创建。

注意    有多种fork()exec()调用,如vfork()execve()等。Oracle所用的调用可能根据操作系统和实现的不同而有所不同,但是最后的结果是一样的。fork()创建一个新进程,这是父进程的一个克隆,而且在UNIX上这也是创建新进程的惟一途径。exec()在内存中现有的程序映像上加载一个新的程序映像,这就启动了一个新程序。所以SQL*Plus可以先“fork”(即复制自身),然后“execOracle二进制可执行程序,用这个新程序覆盖它自己的副本。

UNIX上,可以在同一台机器上运行客户和服务器,就能很清楚地看出这种父/子进程的创建:

ops$tkyte@ORA10G> select a.spid dedicated_server,

2                   b.process clientpid

3          from v$process a, v$session b

4          where a.addr = b.paddr

5                   and b.sid = (select sid from v$mystat where rownum=1)

6 /

DEDICATED_SE                       CLIENTPID

  -----------------------                       ----------------

                       5114                                   5112

 

ops$tkyte@ORA10G> !/bin/ps -p 5114 5112

   PID TTY   STAT   TIME COMMAND

5112 pts/1               R        0:00       sqlplus

5114 ?        S          0:00   oracleora10g (DESCRIPTION=(LOCAL=YES)..(PROTOCOL=beq)))

在此,我使用了一个查询来发现与专用服务器相关联的进程IDPID),从V$PROCESS得到的SPID是执行该查询时所用进程的操作系统PID

5.1.2             共享服务器连接

下面更详细地介绍共享服务器进程。共享服务器连接强制要求必须使用Oracle Net,即使客户和服务器都在同一台机器上也不例外。如果不使用Oracle TNS监听器,就无法使用共享服务器。如前所述,客户应用会连接到Oracle TNS监听器,并重定向或转交给一个调度器。调度器充当客户应用和共享服务器进程之间的“导管”。图5-2显示了与数据库建立共享服务器连接时的体系结构。

5-2 典型的共享服务器连接

在此可以看到,客户应用(其中链接了Oracle库)会与一个调度器进程物理连接。对于给定的实例,可以配置多个调度器,但是对应数百个(甚至数千个)用户只有一个调度器的情况并不鲜见。调度器只负责从客户应用接收入站请求,并把它们放入SGA中的一个请求队列。第一个可用的共享服务器进程(与专用服务器进程实质上一样)从队列中选择请求,并附加相关会话的UGA(图5-2中标有“S”的方框)。共享服务器处理这个请求,把得到的输出放在响应队列中。调度器一直监视着响应队列来得到结果,并把结果传回给客户应用。就客户而言,它分不清到底是通过一条专用服务器连接还是通过一条共享服务器连接进行连接,看上去二者都一样,只是在数据库级二者的区别才会明显。

5.1.3             连接与会话

连接并不是会话的同义词,发现这一点时很多人都很诧异。在大多数人眼里,它们都是一样的,但事实上并不一定如此。在一条连接上可以建立0个、一个或多个会话。各个会话是单独而且独立的,即使它们共享同一条数据库物理连接也是如此。一个会话中的提交不会影响该连接上的任何其他会话。实际上,一条连接上的各个会话可以使用不同的用户身份!

Oracle中,连接只是客户进程和数据库实例之间的一条特殊线路,最常见的就是网络连接。这条连接可能连接到一个专用服务器进程,也可能连接到调度器。如前所述,连接上可以有0个或多个会话,这说明可以有连接而无相应的会话。另外,一个会话可以有连接也可以没有连接。使用高级Oracle Net特性(如连接池)时,客户可以删除一条物理连接,而会话依然保留(但是会话会空闲)。客户在这个会话上执行某个操作时,它会重新建立物理连接。下面更详细地定义这些术语:

q         连接(connection):连接是从客户到Oracle实例的一条物理路径。连接可以在网络上建立,或者通过IPC机制建立。通常会在客户进程与一个专用服务器或一个调度器之间建立连接。不过,如果使用Oracle的连接管理器(Connection Manager CMAN),还可以在客户和CMAN之间以及CMAN和数据库之间建立连接。CMAN的介绍超出了本书的范围,不过Oracle Net Services Administrator’s Guide(可以从http://otn.oracle.com免费得到)对CMAN有详细的说明。

q         会话(session):会话是实例中存在的一个逻辑实体。这就是你的会话状态(session state),也就是表示特定会话的一组内存中的数据结构。提到“数据库连接”时,大多数人首先想到的就是“会话”。你要在服务器中的会话上执行SQL、提交事务和运行存储过程。

可以使用SQL*Plus来看一看实际的连接和会话是什么样子,从中还可以了解到,实际上一条连接有多个会话的情况相当常见。这里使用了AUTOTRACE命令,并发现有两个会话。我们在一条连接上使用一个进程创建了两个会话。以下是其中的第一个会话:

ops$tkyte@ORA10G> select username, sid, serial#, server, paddr, status

2 from v$session

3 where username = USER

4 /

USERNAME SID    SERIAL#    SERVER         PADDR         STATUS

---------             ----      --------          ---------              --------            --------

OPS$TKYTE             153             3196                DEDICATED AE4CF614       ACTIVE

这说明现在有一个会话:这是一个与单一专用服务器连接的会话。以上PADDR列是这个专用服务器进程的地址。下面,只需打开AUTOTRACE来查看SQL*Plus中所执行语句的统计结果:

ops$tkyte@ORA10G> set autotrace on statistics

ops$tkyte@ORA10G> select username, sid, serial#, server, paddr, status

2 from v$session

3 where username = USER

4 /

 

USERNAME SID    SERIAL#    SERVER         PADDR         STATUS

---------             ----      --------          ---------              --------            --------

OPS$TKYTE             151             1511                DEDICATED AE4CF614       INACTIVE

OPS$TKYTE             153             3196                DEDICATED AE4CF614       ACTIVE

 

Statistics

----------------------------------------------------------

            0 recursive calls

            0 db block gets

            0 consistent gets

            0 physical reads

            0 redo size

       756 bytes sent via SQL*Net to client

       508 bytes received via SQL*Net from client

            2 SQL*Net roundtrips to/from client

            0 sorts (memory)

            0 sorts (disk)

            2 rows processed

ops$tkyte@ORA10G> set autotrace off

这样一来,我们就有了两个会话,但是这两个会话都使用同一个专用服务器进程,从它们都有同样的PADDR值就能看出这一点。从操作系统也可以得到确认,因为没有创建新的进程,对这两个会话只使用了一个进程(一条连接)。需要注意,其中一个会话(原来的会话)是ACTIVE(活动的)。这是有道理的:它正在运行查询来显示这个信息,所以它当然是活动的。但是那个INACTIVE(不活动的)会话呢?那个会话要做什么?这就是 AUTOTRACE 会话,它的任务是“监视”我们的实际会话,并报告它做了什么。

SQL*Plus 中启用(打开)AUTOTRACE时,如果我们执行DML操作(INSERTUPDATEDELETESELECTMERGE),SQL*Plus会完成以下动作:

 (1) 如果还不存在辅助会话[1],它会使用当前连接创建一个新会话。

(2) 要求这个新会话查询V$SESSTAT视图来记住实际会话(即运行DML的会话)的初始统计值。这与第4章中watch_stat.sql脚本完成的功能非常相似。

(3) 在原会话中运行DML操作。

(4) DML语句执行结束后,SQL*Plus会请求另外那个会话(即“监视”会话)再次查询V$SESSTAT,并生成前面所示的报告,显示出原会话(执行DML的会话)的统计结果之差。

如果关闭AUTOTRACESQL*Plus会终止这个额外的会话,在V$SESSION中将无法看到这个会话。你可能会问:“SQL*Plus 为什么要这样做,为什么要另建一个额外的会话?”答案很简单。我们在第4章中曾经使用第二个SQL*Plus会话来监视内存和临时空间的使用情况,原因是:如果使用同一个会话来监视内存使用,那执行监视本身也要使用内存。SQL*Plus之所以会另外创建一个会话来执行监视,原因也是一样的。如果在同一个会话中观察统计结果,就会对统计结果造成影响(导致对统计结果的修改)。倘若SQL*Plus使用一个会话来报告所执行的I/O次数,网络上传输了多少字节,以及执行了多少次排序,那么查看这些详细信息的查询本身也会影响统计结果。这些查询可能自己也要排序、执行I/O以及在网络上传输数据等(一般来说都会如此!)。因此,我们需要使用另一个会话来正确地测量。

到目前为止,我们已经看到一条连接可以有一个或两个会话。现在,我们想使用SQL*Plus来查看一条没有任何会话的连接。这很容易。在上例所用的同一个SQL*Plus窗口中,只需键入一个“很容易误解”的命令即DISCONNECT

ops$tkyte@ORA10G> disconnect

Disconnected from Oracle Database 10g Enterprise Edition Release 10.1.0.3.0

Production

With the Partitioning, OLAP and Data Mining options

ops$tkyte@ORA10G>

从技术上讲,这个命令应该叫DESTROY_ALL_SESSIONS更合适,而不是DISCONNECT,因为我们并没有真正物理地断开连接。

注意    SQL*Plus中要真正地断开连接,应该执行“exit”命令,因为你必须退出才能完全撤销连接。

不过,我们已经关闭了所有会话。如果使用另一个用户账户[2]打开另一个会话,并查询(当然要用你原来的账户名代替OPS$TKYTE)。

sys@ORA10G> select * from v$session where username = 'OPS$TKYTE';

no rows selected

可以看到,这个账户名下没有会话,但是仍有一个进程,相应地有一条物理连接(使用前面的ADDR值)[3]

sys@ORA10G> select username, program

2 from v$process

3 where addr = hextoraw('AE4CF614');

 

USERNAME    PROGRAM

---------------       ------------------------------------------------

         tkyte         oracle@localhost.localdomain (TNS V1-V3)

所以,这就有了一条没有相关会话的“连接”。可以使用SQL*PlusCONNECT命令(这个命令的名字也起得不恰当),在这个现有的进程中创建一个新会话(CONNECT命令叫CREATE_SESSION更合适):

ops$tkyte@ORA10G> connect /

Connected.

 

ops$tkyte@ORA10G> select username, sid, serial#, server, paddr, status

2 from v$session

3 where username = USER

4 /

 

USERNAME SID    SERIAL#    SERVER         PADDR         STATUS

---------             ----      --------          ---------              --------            --------

OPS$TKYTE             150             233                   DEDICATED AE4CF614       ACTIVE

可以注意到,PADDR还是一样的,所以我们还是在使用同一条物理连接,但是(可能)有一个不同的SID。我说“可能有”,是因为也许还会分配同样的SID,这取决于在我们注销时是否有别人登录,以及我们原来的SID是否可用。

到此为止,这些测试都是用一条专用服务器连接执行的,所以PADDR正是专用服务器进程的进程地址。如果使用共享服务器会怎么样呢?

注意    要想通过共享服务器连接,你的数据库实例必须先做必要的设置才能启动。有关如何配置共享服务器,这超出了本书的范围,不过这个主题在Oracle Net Services Administrator’s Guide中有详细说明。

那好,下面使用共享服务器登录,并在这个会话中查询:

ops$tkyte@ORA10G> select a.username, a.sid, a.serial#, a.server,

2 a.paddr, a.status, b.program

3 from v$session a left join v$process b

4 on (a.paddr = b.addr)

5 where a.username = 'OPS$TKYTE'

6 /

 

USERNAME SID    SERIAL# SERVER PADDR         STATUS   PROGRAM

---------             ---       -------          -------          --------            ------           ----------------------

OPS$TKYTE             150            261            SHARED      AE4CF118        ACTIVE                           oracle@localhost(S000)

这个共享服务器连接与一个进程关联,利用PADDR可以联结到V$PROCESS来得出进程名。在这个例子中,可以看到这确实是一个共享服务器,由文本S000标识。

不过,如果使用另一个SQL*Plus窗口来查询这些信息,而保持我们的共享服务器会话空闲,就会看到下面的信息:

sys@ORA10G> select a.username, a.sid, a.serial#, a.server,

2          a.paddr, a.status, b.program

3 from v$session a left join v$process b

4          on (a.paddr = b.addr)

5 where a.username = 'OPS$TKYTE'

6 /

 

USERNAME SID    SERIAL# SERVER PADDR         STATUS      PROGRAM

---------             ---       -------          ------           --------            --------           -----------------------

OPS$TKYTE             150            261            NONE           AE4CEC1C      INACTIVE                           oracle@localhost(D000)

可以注意到,PADDR不一样了,与连接关联的进程名也改变了。空闲的共享服务器连接现在与一个调度器D000关联。这样一来,我们又有了一种方法来观察指向一个进程的多个会话。可以有数百个(甚至数千个)会话指向一个调度器。

共享服务器连接有一个很有意思的性质:我们使用的共享服务器进程可能随调用而改变。如果只有我一个人在使用这个系统(因为我在执行这些测试),作为OPS$TKYTE反复运行这个查询,会反复生成同样的PADDRAE4CF118。不过,如果打开多条共享服务器连接,并开始在其他会话中使用这个共享服务器,可能就会注意到使用的共享服务器有变化。

考虑下面的例子。这里要查询我当前的会话信息,显示所用的共享服务器。然后在另一个共享服务器会话中完成一个运行时间很长的操作(也就是说,我要独占这个共享服务器)。再次询问数据库我所用的共享服务器时,很有可能看到一个不同的共享服务器(如果原来的共享服务器已经开始为另一个会话提供服务)。在下面的例子中,粗体显示的代码表示通过共享服务器连接的第二个SQL*Plus 会话:

ops$tkyte@ORA10G> select a.username, a.sid, a.serial#, a.server,

2                a.paddr, a.status, b.program

3       from v$session a left join v$process b

4                on (a.paddr = b.addr)

5       where a.username = 'OPS$TKYTE'

6 /

 

USERNAME SID    SERIAL# SERVER PADDR         STATUS      PROGRAM

---------             ---       -------          -------          --------            ------             ----------------------

OPS$TKYTE             150            261            SHARED      AE4CF118        ACTIVE                           oracle@localhost(S000)

sys@ORA10G> connect system/manager@shared_server.us.oracle.com

Connected.

system@ORA10G> exec dbms_lock.sleep(20)

ops$tkyte@ORA10G> select a.username, a.sid, a.serial#, a.server,

2                a.paddr, a.status, b.program

3       from v$session a left join v$process b

4                on (a.paddr = b.addr)

5       where a.username = 'OPS$TKYTE'

6 /

USERNAME SID    SERIAL# SERVER PADDR         STATUS      PROGRAM

----------------- -----    -------          ------------   --------------    ------------     -------------------------------

OPS$TKYTE             150            261            SHARED      AE4CF614        ACTIVE                           oracle@localhost(S001)

注意第一次是怎么查询的,我使用了S000作为共享服务器。然后在另一个会话中执行了一个长时间运行的语句,它会独占这个共享服务器,而此时所独占的这个共享服务器恰好是S000。要执行这个长时间运行的操作,这个工作会交给第一个空闲的共享服务器来完成,由于此时没有人要求使用S000共享服务器,所以DBMS_LOCK命令就选择了S000。现在,再次在第一个SQL*Plus会话中查询时,将分配另一个共享服务器进程,因为S000共享服务器正在忙。

有一点很有意思,解析查询(尚未返回任何行)可能由共享服务器S000处理,第一行的获取可能由S001处理,第二行的获取可能由S002处理,而游标的关闭可能由S003负责。也就是说,一条语句可能由多个共享服务器一点一点地处理。

总之,从这一节可以了解到,一条连接(从客户到数据库实例的一条物理路径)上可以建立0个、1个或多个会话。我们看到了这样一个用例,其中使用了SQL*PlusAUTOTRACE工具。还有许多其他的工具也利用了这一点。例如,Oracle Forms就使用一条连接上的多个会话来实现其调度功能。Oracle n层代理认证特性可用于提供从浏览器到数据库的端到端用户鉴别,这个特性也大量使用了有多个会话的单连接概念,但是每个会话中可能会使用一个不同的用户账户。我们已经看到,随着时间的推移,会话可能会使用多个进程,特别是在共享服务器环境中这种情况更常见。另外,如果使用Oracle Net的连接池,会话可能根本不与任何进程关联;连接空闲一段时间后,客户会将其删除,然后再根据检测活动(是否需要连接)透明地重建连接。

简单地说,连接和会话之间有一种多对多的关系。不过,最常见的是专用服务器与单一会话之间的一对一关系,这也是大多数人每天所看到的情况。

5.1.4             专用服务器与共享服务器

在继续介绍其他进程之前,下面先来讨论为什么会有两种连接模式,以及各个模式在哪些情况下更适用。

1.      什么时候使用专用服务器

前面提到过,在专用服务器模式中,客户连接与服务器进程之间存在一种一对一的映射。对于目前所有基于SQL的应用来说,这是应用连接Oracle数据库的最常用的方法。设置专用服务器最简单,而且采用这种方法建立连接也最容易,基本上不需要什么配置。

因为存在一对一的映射,所以不必担心长时间运行的事务会阻塞其他事务。其他事务只通过其自己的专用进程来处理。因此,在非OLTP环境中,也就是可能有长时间运行事务的情况下,应该只考虑使用这种模式。专用服务器是Oracle的推荐配置,它能很好地扩缩。只要服务器有足够的硬件(CPURAM)来应对系统所需的专用服务器进程个数,专用服务器甚至可以用于数千条并发连接。

某些操作必须在专用服务器模式下执行,如数据库启动和关闭,所以每个数据库中可能同时有专用服务器和共享服务器,也可能只设置一个专用服务器。

2.      什么时候使用共享服务器

共享服务器的设置和配置尽管并不困难,但是比设置专用服务器要多一步。不过,二者之间的主要区别还不在于其设置;而是操作的模式有所不同。对于专用服务器,客户连接和服务器进程之间存在一对一的映射。对于共享服务器,则有一种多对一的关系:多个客户对应一个共享服务器。

顾名思义,共享服务器是一种共享资源,而专用服务器不是。使用共享资源时,必须当心,不要太长时间独占这个资源。如前所示,在会话中使用一个简单的DBMS_LOCK.SLEEP(20)就会独占共享服务器进程20秒的时间。如果独占了共享服务器资源,会导致系统看上去好像挂起了一样。

5-2中有两个共享服务器。如果我有3个客户,这些客户都试图同时运行一个45秒左右的进程,那么其中两个会在45秒内得到响应,第3个进程则会在90秒内才得到响应。这就是共享服务器的首要原则:要确保事务的持续时间尽量短。事务可以频繁地执行,但必须在短时间内执行完(这正是OLTP系统的特点)。如果事务持续时间很长,看上去整个系统都会慢下来,因为共享资源被少数进程独占着。在极端情况下,如果所有共享服务器都很忙,除了少数几个独占着共享服务器的幸运者以外,对其他用户来说,系统就好像挂起了。

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

因此,由于这些原因,共享服务器只适用于OLTP系统,这种系统的特点是事务短而且频繁。在一个OLTP系统中,事务以毫秒为单位执行,任何事务的执行都会在1秒以内的片刻时间内完成。共享服务器对数据仓库很不适用,因为在数据仓库中,可能会执行耗时1分钟、2分钟、5分钟甚至更长时间的查询。如果采用共享服务器模式,其后果则是致命的。如果你的系统中90%都是OLTP,只有10%“不那么OLTP”,那么可以在同一个实例上适当地混合使用专用服务器和共享服务器。采用这种方式,可以大大减少机器上针对OLTP用户的服务器进程个数,并使得“不那么OLTP”的用户不会独占共享服务器。另外,DBA可以使用内置的资源管理器(Resource Manager)进一步控制资源利用率。

当然,使用共享服务器还有一个很重要的原因,这就是有时你别无选择。许多高级连接特性都要求使用共享服务器。如果你想使用Oracle Net连接池,就必须使用共享服务器。如果你想在数据库之间使用数据库链接集合(database link concentration),也必须对这些连接使用共享服务器。

注意    如果你的应用中已经使用了一个连接池特性(例如,你在使用J2EE连接池),而且适当地确定了连接池的大小,再使用共享服务器只会成为性能“杀手”,导致性能下降。你已经确定了连接池的大小,以适应任何时间点可能的并发连接数,所以你希望这些连接都是直接的专用服务器连接。否则,在你应用的连接池特性中,只是“嵌套”了另一个连接池特性。

3.      共享服务器的潜在好处

前面说明了要当心哪些事务类型能使用共享服务器,那么,如果记住了这些原则,共享服务器能带来什么好处呢?共享服务器主要为我们做3件事:减少操作系统进程/线程数,刻意地限制并发度,以及减少系统所需的内存。在后面几节中将更详细地讨论这几点。

l          减少操作系统进程/线程数

在一个有数千个用户的系统上,如果操作系统力图管理数千个进程,可能很快就会疲于奔命。在一个典型的系统中,尽管有数千个用户,但任何时间点上只有其中很少一部分用户同时是活动的。例如,我最近参与开发了一个有5 000个并发用户的系统。在任何时间点上,最多只有50个用户是活动的。这个系统利用50个共享服务器进程就能有效地工作,这使得操作系统必须管理的进程数下降了两个数量级(100倍)。从很大程度上讲,这样一来,操作系统可以避免上下文切换。

l          刻意地限制并发度

由于我曾经参加过大量的基准测试,所以在这方面我应该很有发言权。运行基准测试时,人们经常要求支持尽可能多的用户,直至系统崩溃。这些基准测试的输出之一往往是一张图表,显示出并发用户数和事务数之间的关系(见图5-3)。

5-3  并发用户数与每秒事务数

最初,增加并发用户数时,事务数会增加。不过到达某一点时,即使再增加额外的用户,也不会增加每秒完成的事务数,图中的曲线开始下降。吞吐量有一个峰值,从这个峰值开始,响应时间开始增加(你每秒完成的事务数还是一样,但是最终用户观察到响应时间变慢)。随着用户数继续增加,会发现吞吐量实际上开始下降。这个下降点之前的并发用户数就是系统上允许的最大并发度。从这一点开始,系统开始出现拥塞,为了完成工作,用户将开始排队。就像收费站出现的阻塞一样,系统将无法支撑。此时不仅响应时间开始大幅增加,系统的吞吐量也可能开始下跌,另外上下文切换要占用资源,而且在这么多消费者之间共享资源就存在着开销,这本身也会占用额外的资源。如果把最大并发度限制为这个下降点之前的某一点,就可以保持最大吞吐量,并尽可能减少大多数用户的响应时间。利用共享服务器,我们就能把系统上的最大并发度限制为这个合适的数。

可以把这个过程比作一扇简单的门。门有宽度,人也有宽度,这就限制了最大吞吐量,即每分钟最多可以有多少人通过这扇门。如果“负载”低,就没有什么问题;不过,随着越来越多的人到来,就会要求某些人等待(CPU时间片)。如果很多人都想通过这扇门,反而会“退步”,假如有太多的人在你身后排队,吞吐量就开始下降。每个人通过时都会有延迟。使用队列意味着吞吐量增加,有些人会很快地通过这扇门,就好像没有队列一样,而另外一些人(排在队尾的人)却要忍受最大的延迟,焦燥地认为“这是一个不好的做法”。但实际上,在测量每个人(包括最后一个人)通过门的速度时,排队模型(共享服务器)确实比“各行其是”(free-for-all)的方法要好。如果是各行其是,即使大家都很文明,也不乏有这样的情形:商店大降价时,开门后,每个人都争先恐后地涌入大门,结果却是大家都挤不动。

l          减少系统所需的内存

这是使用共享服务器最主要的原因之一:它能减少所需的内存量。共享服务器确实能减少内存需求,但是没有你想象中的那么显著,特别是假设采用了第4章介绍的自动PGA内存管理,那么共享服务器在减少内存需求方面更没有太大意义。自动PGA内存管理是指,为进程分配工作区后,使用完毕就释放,而且所分配工作区的大小会根据并发工作负载而变化。所以,这个理由在较早版本的Oracle中还算合理,但对当前来讲意义不大。另外要记住,使用共享服务器时,UGASGA中分配。这说明,转变为共享服务器时,必须能准确地确定需要多少UGA内存,并适当地在SGA中分配(通过LARGE_POOL_SIZE参数)。所以,共享服务器配置中对SGA的需求通常很大。这个内存一般要预分配,从而只能由数据库实例使用。

注意    对于大小可以调整的SGA,随着时间的推移确实可以扩大或收缩这个内存,但是在大多数情况下,它会由数据库实例所“拥有”,其他进程不能使用。

专用服务器与此正好相反,任何人都可以使用未分配给SGA的任何内存。那么,既然UGASGA中分配而使得SGA相当大,又怎么能节省内存呢? 之所以能节省内存,这是因为共享服务器会分配更少的PGA。每个专用/共享服务器都有一个PGA,即进程信息。PGA是排序区、散列区以及其他与进程相关的结构。采用共享服务器,就可以从系统去除这部分内存的需求。如果从使用5 000个专用服务器发展到使用100个共享服务器,那么通过使用共享服务器,累计起来就能节省4 900PGA的内存(但不包括其UGA)。

5.1.5             专用/共享服务器小结

除非你的系统负载过重,或者需要为一个特定的特性使用共享服务器,否则专用服务器可能最合适。专用服务器设置起来很简单(实际上,根本没有设置!),而且调优也更容易。

注意    对于共享服务器连接,会话的跟踪信息(SQL_TRACE=TRUE时的输出)可能分布在多个独立的跟踪文件中,重建会话可能更困难。

如果用户群很大,而且你知道要部署共享服务器,强烈建议你先开发并测试这个共享服务器。如果只是在一个专用服务器下开发,而且从来没有对共享服务器进行测试,出现失败的可能性会更大。要对系统进行压力测试,建立基准测试,并确保应用使用共享服务器时也能很好地工作。也就是说,要确保它不会独占共享服务器太长时间。如果在开发时发现它会长时间地独占共享服务器,与等到部署时才发现相比,修正会容易得多。你可以使用诸如高级排队(Advanced QueuingAQ)之类的特性使长时间运行的进程变得很短,但是必须在应用中做相应的设计。这种工作最好在开发时完成。另外,共享服务器连接和专用服务器连接可用的特性集之间还有一些历史上的差别。例如,我们已经讨论过Oracle 9i中没有自动PGA内存管理,但是过去有的一些基本特性(如两个表之间的散列联结)在共享服务器连接中反倒没有了。

5.2   5.2  后台进程

Oracle实例包括两部分:SGA和一组后台进程。后台进程执行保证数据库运行所需的实际维护任务。例如,有一个进程为我们维护块缓冲区缓存,根据需要将块写出到数据文件。另一个进程负责当在线重做日志文件写满时将它复制到一个归档目标。另外还有一个进程负责在异常中止进程后完成清理,等等。每个进程都专注于自己的任务,但是会与所有其他进程协同工作。例如,负责写日志文件的进程填满一个日志后转向下一个日志时,它会通知负责对填满的日志文件进行归档的进程,告诉它有活干了。

可以使用一个V$视图查看所有可能的Oracle后台进程,确定你的系统中正在使用哪些后台进程:

ops$tkyte@ORA9IR2> select paddr, name, description

2 from v$bgprocess

3 order by paddr desc

4 /

 

PADDR      NAME DESCRIPTION

--------          ----         ------------------------------------------------------------

5F162548 ARC1   Archival Process 1

5F162198 ARC0   Archival Process 0

5F161A38 CJQ0    Job Queue Coordinator

5F161688 RECO distributed recovery

5F1612D8                SMON      System Monitor Process

5F160F28 CKPT   checkpoint

5F160B78 LGWR Redo etc.

5F1607C8                DBW0       db writer process 0

5F160418 PMON process cleanup

00                DIAG    diagnosibility process

00                FMON File Mapping Monitor Process

00                LMON global enqueue service monitor

00                LMD0   global enqueue service daemon 0

...

00                LMS7   global cache service process 7

00                LMS8   global cache service process 8

00                LMS9   global cache service process 9

69 rows selected.

这个视图中PADDR不是00的行都是系统上配置和运行的进程(线程)。

有两类后台进程:有一个中心(focused)任务的进程(如前所述)以及完成各种其他任务的进程(即工具进程)。例如,内部作业队列(job queue)有一个工具后台进程,可以通过DBMS_JOB包使用它。这个进程会监视作业队列,并运行其中的作业。在很多方面,这就像一个专用服务器进程,但是没有客户连接。下面会分析各种后台进程,先来看有中心任务的进程,然后再介绍工具进程。

5.2.1             中心后台进程

5-4展示了有一个中心(focused)用途的Oracle后台进程。

5-4  中心后台进程

启动实例时也许不会看到所有这些进程,但是其中一些主要的进程肯定存在。如果在ARCHIVELOG模式下,你可能只会看到ARCn(归档进程),并启用自动归档。如果运行了Oracle RAC,这种Oracle配置允许一个集群中不同机器上的多个实例装载并打开相同的物理数据库,就只会看到LMD0LCKnLMONLMSn(稍后会更详细地介绍这些进程)。

注意    为简洁起见,图5-4中没有画出共享服务器调度器(Dnnn)和共享服务器(Snnn)进程。

因此,图5-4大致展示了启动 Oracle实例并装载和打开一个数据库时可能看到哪些进程。例如,在我的Linux系统上,启动实例后,有以下进程:

$ ps -aef | grep 'ora_.*_ora10g$'

ora10g 5892   1     0    16:17   ?           00:00:00           ora_pmon_ora10g

ora10g 5894   1     0    16:17   ?           00:00:00           ora_mman_ora10g

ora10g 5896   1      0    16:17   ?           00:00:00           ora_dbw0_ora10g

ora10g 5898   1     0    16:17   ?           00:00:00           ora_lgwr_ora10g

ora10g 5900   1     0    16:17   ?           00:00:00           ora_ckpt_ora10g

ora10g 5902   1     0    16:17   ?           00:00:00           ora_smon_ora10g

ora10g 5904   1     0    16:17   ?           00:00:00           ora_reco_ora10g

ora10g 5906   1     0    16:17   ?           00:00:00           ora_cjq0_ora10g

ora10g 5908   1     0    16:17   ?           00:00:00           ora_d000_ora10g

ora10g 5910   1     0    16:17   ?           00:00:00           ora_s000_ora10g

ora10g 5916   1     0    16:17   ?           00:00:00           ora_arc0_ora10g

ora10g 5918   1     0    16:17   ?           00:00:00           ora_arc1_ora10g

ora10g 5920   1     0    16:17   ?           00:00:00           ora_qmnc_ora10g

ora10g 5922   1     0    16:17   ?           00:00:00           ora_mmon_ora10g

ora10g 5924   1     0    16:17   ?           00:00:00           ora_mmnl_ora10g

ora10g 5939   1     0    16:28   ?           00:00:00           ora_q000_ora10g

这些进程所用的命名约定很有意思。进程名都以ora_开头。后面是4个字符,表示进程的具体名字,再后面是_ora10g。因为我的ORACLE_SID(站点标识符)是ora10g。在UNIX上,可以很容易地标识出Oracle后台进程,并将其与一个特定的实例关联(在 Windows上则没有这么容易,因为在Windows上这些后台进程实际上只是一个更大进程中的线程)。最有意思的是(但从前面的代码可能不太容易看出来),这些进程实际上都是同一个二进制可执行程序,对于每个“程序”,并没有一个单独的可执行文件。你可以尽可能地查找一下,但是不论在磁盘的哪个位置上肯定都找不到一个arc0二进制可执行程序,同样也找不到LGWRDBW0。这些进程实际上都是oracle(也就是所运行的二进制可执行程序的名字)。它们只是在启动时对自己建立别名,以便更容易地标识各个进程。这样就能在UNIX平台上高效地共享大量对象代码。Windows上就没有什么特别的了,因为它们只是进程中的线程,因此,当然只是一个大的二进制文件。

下面来介绍各个进程完成的功能,先从主要的Oracle后台进程开始。

1.      PMON:进程监视器(Process Monitor

这个进程负责在出现异常中止的连接之后完成清理。例如,如果你的专用服务器“失败”或者出于某种原因被撤销,就要由PMON进程负责修正(恢复或撤销工作),并释放你的资源。PMON会回滚未提交的工作,并释放为失败进程分配的SGA资源。

除了出现异常连接后完成清理外,PMON还负责监视其他的Oracle后台进程,并在必要时(如果可能的话)重启这些后台进程。如果一个共享服务器或调度器失败(崩溃),PMON会介入,并重启另一个共享服务器或调度器(对失败进程完成清理之后)。PMON会查看所有Oracle进程,可能重启这些进程,也可能适当地终止实例。例如,如果数据库日志写入器进程(LGWR)失败,就最好让实例失败。这是一个严重的错误,最安全的做法就是立即终止实例,并根据正常的恢复来修正数据(注意,这是一种很罕见的情况,要立即报告给Oracle Support)。

PMON还会为实例做另一件事,这就是向Oracle TNS监听器注册这个实例。实例启动时,PMON进程会询问公认的端口地址(除非直接指定),来查看是否启动并运行了一个监听器。Oracle使用的公认/默认端口是1521。如果此时监听器在另外某个端口上启动会怎么样呢?在这种情况下,原理是一样的,只不过需要设置LOCAL_LISTENER参数来显式地指定监听器地址。如果数据库实例启动时有监听器在运行,PMON会与这个监听器通信,并向它传递相关的参数,如服务名和实例的负载度量等。如果监听器未启动,PMON则会定期地试图与之联系来注册实例。

2.      SMON:系统监视器(System Monitor

SMON进程要完成所有“系统级”任务。PMON感兴趣的是单个的进程,而SMON与之不同,它以系统级为出发点,这是一种数据库“垃圾收集器”。SMON所做的工作包括:

q         清理临时空间:原先清理临时空间这样的杂事都要由我们来完成,随着引入了“真正” 的临时表空间,这个负担已经减轻,但并不是说完全不需要清理临时空间。例如,建立一个索引时,创建时为索引分配的区段标记为TEMPORARY。如果出于某种原因CREATE INDEX会话中止了,SMON就要负责清理。其他操作创建的临时区段也要由SMON负责清理。

q         合并空闲空间:如果你在使用字典管理的表空间,SMON要负责取得表空间中相互连续的空闲区段,并把它们合并为一个更大的空闲区段。只有当字典管理的表空间有一个默认的存储子句,而且pctincrease设置为一个非0值时,才会出现空闲空间的合并。

q         针对原来不可用的文件恢复活动的事务:这类似于数据库启动时SMON的作用。在实例/崩溃恢复时由于某个文件(或某些文件)不可用,可能会跳过一些失败的事务(即无法恢复),这些失败事务将由SMON来恢复。例如,磁盘上的文件可能不可用或者未装载,当文件确实可用时,SMON就会由此恢复事务。

q         执行RAC中失败节点的实例恢复:在一个Oracle RAC配置中,集群中的一个数据库实例失败时(例如,实例所在的主机失败),集群中的另外某个节点会打开该失败实例的重做日志文件,并为该失败实例完成所有数据的恢复。

q         清理OBJ$OBJ$是一个低级数据字典表,其中几乎对每个对象(表、索引、触发器、视图等)都包含一个条目。很多情况下,有些条目表示的可能是已经删除的对象,或者表示“not there”(不在那里)对象(“not there”对象是Oracle依赖机制中使用的一种对象)。要由SMON进程来删除这些不再需要的行。

q         收缩回滚段:如果有设置,SMON会自动将回滚段收缩为所设置的最佳大小。

q         离线回滚段:DBA有可能让一个有活动事务的回滚段离线(offline),或置为不可用。也有可能活动事务会使用离线的回滚段。在这种情况下,回滚段并没有真正离线;它只是标记为“将要离线”。在后台,SMON会定期尝试真正将其置为离线,直至成功为止。

从以上介绍你应该对SMON做些什么有所认识了。除此之外,它还会做许多其他的事情,如将DBA_TAB_MONITORING视图中的监视统计信息刷新输出,将SMON_SCN_TIME表中的SCN-时间戳映射信息刷新输出,等等。随着时间的推移,SMON进程可能会累积地占用大量CPU时间,这应该是正常的。SMON会定期地醒来(或者被其他后台进程唤醒),来执行这些维护工作。

3.      RECO:分布式数据库恢复(Distributed Database Recovery

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

如果某个站点投票YES,称其准备好要提交,但是在此之后,并且在得到协调器的指令真正提交之前,网络失败了,或者出现了另外某个错误,事务就会成为一个可疑的分布式事务(in-doubt distributed transaction)。2PC力图限制出现这种情况的时间窗口,但是无法根除这种情况。如果正好在那时(这个时间窗口内)出现一个失败,处理事务的工作就要由RECO负责。RECO会试图联系事务的协调器来发现协调的结果。在此之前,事务会保持未提交状态。当再次到达事务协调器时,RECO可能会提交事务,也可能将事务回滚。

需要说明,如果失败(outrage)持续很长一段时间,而且你有一些很重要的事务,可以自行手动地提交或回滚。有时你可能想要这样做,因为可疑的分布式事务可能导致写入器阻塞读取器(Oracle中只有此时会发生“写阻塞读”的情况)。你的DBA可以通知另一个数据库的DBA,要求他查询那些可疑事务的状态。然后你的DBA再提交或回滚,而不再由RECO完成这个任务。

4.      CKPT:检查点进程(Checkpoint Process

检查点进程并不像它的名字所暗示的那样真的建立检查点(检查点在第3章介绍重做日志一节中已经讨论过),建立检查点主要是DBWn的任务。CKPT只是更新数据文件的文件首部,以辅助真正建立检查点的进程(DBWn)。以前CKPT是一个可选的进程,但从8.0版本开始,这个进程总会启动,所以,如果你在UNIX上执行一个ps命令,就肯定能看到这个进程。原先,用检查点信息更新数据文件首部的工作是LGWR的任务;不过,一段时间后,随着数据库大小的增加以及文件个数的增加,对LGWR来说,这个额外的工作负担太重了。如果LGWR必须更新数十个、数百个甚至数千个文件,等待提交事务的会话就可能必须等待太长的时间。有了CKPT,这个任务就不用LGWR操心了。

5.      DBWn:数据库块写入器(Database Block Writer

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

注意    推进日志文件只是导致检查点活动的途径之一。有一些增量检查点由诸如FAST_START_ MTTR_TARGET之类的参数以及导致脏块刷新输出到磁盘的其他触发器控制。

可以看到,DBWn的性能可能很重要。如果它写出块的速度不够快,不能很快地释放缓冲区(可以重用来缓存其他块),就会看到Free Buffer WaitsWrite Complete Waits的等待数和等待时间开始增长。

可以配置多个DBWn;实际上,可以配置多达20DBWnDBW0DBW9DBWaDBWj)。大多数系统都只有一个数据库块写入器,但是更大的多CPU系统可以利用多个块写入器。通常为了保持SGA中的一个大缓冲区缓存“干净” ,将脏的(修改过的)块刷新输出到磁盘,就可以利用多个DBWn来分布工作负载。

最好的情况下,DBWn使用异步I/O将块写至磁盘。采用异步I/ODBWn会收集一批要写的块,并把它们交给操作系统。DBWn并不等待操作系统真正将块写出;而是立即返回,并收集下一批要写的块。当操作系统完成写操作时,它会异步地通知DBWn写操作已经完成。这样,与所有工作都串行进行相比,DBWn可以更快地工作。在后面的“从属进程”一节中,我们还将介绍如何使用I/O从属进程在不支持异步I/O的平台或配置上模拟异步I/O

关于DBWn,还有最后一点需要说明。根据定义,块写入器进程会把块写出到所有磁盘,即分散在各个磁盘上;也就是说,DBWn会做大量的分散写(scattered write)。执行一个更新时,你会修改多处存储的索引块,还可能修改随机地分布在磁盘上的数据块。另一方面,LGWR则是向重做日志完成大量的顺序写(sequential write)。这是一个很重要的区别,Oracle之所以不仅有一个重做日志和LGWR进程,还有DBWn进程,其原因就在于此。分散写比顺序写慢多了。通过在SGA中缓存脏块,并由LGWR进程完成大规模顺序写(可能重建这些脏缓冲区),这样可以提升性能。DBWn在后台完成它的任务(很慢),而LGWR在用户等待时完成自己的任务(这个任务比较快),这样我们就能得到更好的整体性能。尽管从技术上讲这样会使Oracle执行更多不必要的I/O(写日志以及写数据文件),但整体性能还是会提高。从理论上讲,如果提交期间Oracle已经将已修改的块物理地写出到磁盘,就可以跳过写在线重做日志文件。但在实际中并不是这样[6]LGWR还是会把每个事务的重做信息写至在线重做日志,DBWn则在后台将数据库块刷新输出到磁盘。

6.      LGWR:日志写入器(Log Writer

LGWR进程负责将SGA中重做日志缓冲区的内容刷新输出到磁盘。如果满足以下某个条件,就会做这个工作:

q         3秒会刷新输出一次

q         任何事务发出一个提交时

q         重做日志缓冲区1/3满,或者已经包含1 MB的缓冲数据

由于这些原因,分配超大的(数百MB)重做日志缓冲区并不实际,Oracle根本不可能完全使用这个缓冲区。日志会通过顺序写来写至磁盘,而不像DBWn那样必须执行分散I/O。与向文件的各个部分执行多个分散写相比,像这样大批的写会高效得多。这也是使用LGWR和重做日志的主要原因。通过使用顺序I/O,只写出有变化的字节,这会提高效率;尽管可能带来额外的I/O,但相对来讲所提高的效率更为突出。提交时,Oracle可以直接将数据库块写至磁盘,但是这需要对已满的块执行大量分散I/O,而让LGWR顺序地写出所做的修改要比这快得多。

7.      ARCn:归档进程(Archive Process

ARCn进程的任务是:当LGWR将在线重做日志文件填满时,就将其复制到另一个位置。此后这些归档的重做日志文件可以用于完成介质恢复。在线重做日志用于在出现电源故障(实例终止)时“修正”数据文件,而归档重做日志则不同,它是在出现硬盘故障时用于“修正”数据文件。如果丢失了包含数据文件/d01/oradata/ora10g/system.dbf的磁盘,可以去找上一周的备份,恢复旧的文件副本,并要求在数据库上应用自这次备份之后生成的所有归档和在线重做日志。这样就能使这个数据文件“赶上”数据库中的其他数据文件,所以我们可以继续处理而不会丢失数据。

ARCn通常将在线重做日志文件复制到至少两个位置(冗余正是不丢失数据的关键所在!)。这些位置可能是本地机器上的磁盘,或者更确切地讲,至少有一个位置在另一台机器上,以应付灾难性的失败。在许多情况下,归档重做日志文件会由另外某个进程复制到一个第三辅存设备上,如磁带。也可以将这些归档重做日志文件发送到另一台机器上,应用于“备用数据库”(standby database),这是Oracle提供的一个故障转移选项。稍后将讨论其中涉及的进程。

8.      其他中心进程

取决于所用的Oracle特性,可能还会看到其他一些中心进程。这里只是简单地列出这些进程,并提供其功能的简要描述。前面介绍的进程都是必不可少的,如果运行一个Oracle实例,就肯定会有这些进程。以下进程则是可选的,只有在利用了某个特定的特性时才会出现。下面的进程是使用ASM的数据库实例所特有的,见第3章的讨论:

q         自动存储管理后台(Automatic Storage Management BackgroundASMB)进程:ASMB进程在使用了ASM的数据库实例中运行。它负责与管理存储的ASM实例通信、向ASM实例提供更新的统计信息,并向ASM实例提供一个“心跳”,让ASM实例知道它还活着,而且仍在运行。

q         重新平衡(RebalanceRBAL)进程:RBAL进程也在使用了ASM的数据库实例中运行。向ASM磁盘组增加或去除磁盘时,RBAL进程负责处理重新平衡请求(即重新分布负载的请求)。

q         以下进程出现在Oracle RAC 实例中。RAC是一种Oracle配置,即集群中的多个实例可以装载和打开一个数据库,其中每个实例在一个单独的节点上运行(通常节点是一个单独的物理计算机)。这样,你就能有多个实例访问(以一种全读写方式)同样的一组数据库文件。RAC的主要目标有两个:

q         高度可用性:利用Oracle RAC,如果集群中的一个节点/计算机由于软件、硬件或人为错误而失败,其他节点可以继续工作,还可以通过其他节点访问数据库。你也许会丧失一些计算能力,但是不会因此而无法访问数据库。

q         可扩缩性:无需购买更大的机器来处理越来越大的工作负载(这称为垂直扩缩),RAC允许以另一种方式增加资源,即在集群中增加更多的机器(称为水平扩缩)。举例来说,不必把你的 4 CPU机器扩缩为有8个或16CPU,通过利用RAC,你可以选择增加另外一个相对廉价的4 CPU机器(或多台这样的机器)。

以下进程是RAC环境所特有的,如果不是RAC环境,则看不到这些进程。

q         锁监视器(Lock monitorLMON)进程:LMON监视集群中的所有实例,检测是否有实例失败。这有利于恢复失败实例持有的全局锁。它还负责在实例离开或加入集群时重新配置锁和其他资源(实例失败时会离开集群,恢复为在线时又会加入集群,或者可能有新实例实时地增加到集群中)。

q         锁管理器守护(Lock manager daemonLMD)进程:LMD进程为全局缓存服务(保持块缓冲区在实例间一致)处理锁管理器服务请求。它主要作为代理(broker)向一个队列发出资源请求,这个队列由LMSn进程处理。LMD会处理全局死锁的检测/解析,并监视全局环境中的锁超时。

q         锁管理器服务器(Lock manager serverLMSn)进程:前面已经提到,在一个RAC环境中,各个Oracle实例在集群中的不同机器上运行,它们都以一种读写方式访问同样的一组数据库文件。为了达到这个目的,SGA块缓冲区缓存相互之间必须保持一致。这也是 LMSn进程的主要目标之一。在以前版本的Oracle并行服务器(Oracle Parallel ServerOPS)中,这是通过ping实现的。也就是说,如果集群中的一个节点需要块的一个读一致视图,而这个块以一种独占模式被另一个节点锁定,数据的交换就要通过磁盘刷新输出来完成(块被ping)。如果本来只是要读取数据,这个操作(ping)的代价就太昂贵了。现在则不同,利用LMSn,可以在集群的高速连接上通过非常快速的缓存到缓存交换来完成数据交换。每个实例可以有多达10LMSn进程。

q         锁(LockLCK0)进程:这个进程的功能与前面所述的LMD进程非常相似,但是它处理所有全局资源的请求,而不只是数据库块缓冲区的请求。

q         可诊断性守护(Diagnosability daemonDIAG)进程:DIAG只能用于RAC环境中。它负责监视实例的总体“健康情况”,并捕获处理实例失败时所需的信息。

5.2.2             工具后台进程

这些后台进程全都是可选的,可以根据你的需要来选用。它们提供了一些工具,不过这些工具并不是每天运行数据库所必需的,除非你自己要使用(如作业队列),或者你要利用使用了这些工具的特性(如新增的Oracle 10g 诊断功能)。

UNIX中,这些进程可以像其他后台进程一样可见,如果你执行ps命令,就能看到这些进程。在介绍中心后台进程那一节的开始,我列出了ps命令的执行结果(这里列出其中一部分),可以看到,我有以下进程:

q         配置了作业队列。CJQ0进程是作业队列协调器(job queue coordinator)。

q         配置了Oracle AQ,从Q000AQ队列进程,AQ queue process)和QMNCAQ监视器进程,AQ monitor process)可以看出。

q         启用了自动设置SGA大小,由内存管理器(memory manager MMAN)进程可以看出。

q         启用了Oracle 10g可管理性/诊断特性,由可管理性监视器(manageability monitorMMON)和可管理性监视器灯(manageability monitor lightMMNL)进程可以看出。

ora10g 5894 1 0 16:17 ?       00:00:00           ora_mman_ora10g

ora10g 5906 1 0 16:17 ?      00:00:00           ora_cjq0_ora10g

ora10g 5920 1 0 16:17 ?      00:00:00           ora_qmnc_ora10g

ora10g 5922 1 0 16:17 ?      00:00:00           ora_mmon_ora10g

ora10g 5924 1 0 16:17 ?      00:00:00           ora_mmnl_ora10g

ora10g 5939 1 0 16:28 ?      00:00:00           ora_q000_ora10g

下面来看看根据所用的特性可能会看到哪些进程。

1.      CJQ0Jnnn进程:作业队列

在第一个7.0 版本中,Oracle通过一种称为快照(snapshot)的数据库对象来提供复制特性。作业队列就是刷新快照(或将快照置为当前快照)时使用的内部机制。

作业队列进程监视一个作业表,这个作业表告诉它何时需要刷新系统中的各个快照。在Oracle 7.1中,Oracle公司通过一个名为DBMS_JOB的数据库包来提供这个功能。所以,原先7.0中与快照相关的进程到了7.1及以后版本中变成了“作业队列”。后来,控制作业队列行为的参数(检查的频度,以及应该有多少个队列进程)的名字也发生了变化,从 SNAPSHOT_REFRESH_INTERVALSNAPSHOT_REFRESH_PROCESSES变成了JOB_QUEUE_INTERVALJOB_QUEUE_PROCESSES。在当前的版本中,只有JOB_QUEUE_PROCESSES参数的设置是用户可调的。

最多可以有1 000个作业队列进程。名字分别是J000J001,…,J999。这些进程在复制中大量使用,并作为物化视图刷新进程的一部分。基于流的复制(Oracle9i Release 2中新增的特性)使用AQ来完成复制,因此不使用作业队列进程。开发人员还经常使用作业队列来调度一次性(后台)作业或反复出现的作业,例如,在后台发送一封电子邮件,或者在后台完成一个长时间运行的批处理。通过在后台做这些工作,就能达到这样一种效果:尽管一个任务耗时很长,但在性急的最终用户看来所花费的时间并不多(他会认为任务运行得快多了,但事实上可能并非如此)。这与OracleLGWRDBWn进程所做的工作类似,他们在后台做大量工作,所以你不必实时地等待它们完成所有任务。

Jnnn进程与共享服务器很相似,但是也有专用服务器中的某些方面。它们处理完一个作业之后再处理下一个作业,从这个意义上讲是共享的,但是它们管理内存的方式更像是一个专用服务器(其UGA内存在PGA中,而不是在SGA中)。每个作业队列进程一次只运行一个作业,一个接一个地运行,直至完成。正因为如此,如果我们想同时运行多个作业,就需要多个进程。这里不存在多线程或作业的抢占。一旦运行一个作业,就会一直运行到完成(或失败)。

你会注意到,经过一段时间,Jnnn进程会不断地来来去去,也就是说,如果配置了最多1 000Jnnn进程,并不会看到真的有1 000个进程随数据库启动。相反,开始时只会启动一个进程,即作业队列协调器(CJQ0),它在作业队列表中看到需要运行的作业时,会启动Jnnn进程。如果Jnnn进程完成其工作,并发现没有要处理的新作业,此时Jnnn进程就会退出,也就是说,会消失。因此,如果将大多数作业都调度为在凌晨2:00运行(没有人在场),你可能永远也看不到这些Jnnn进程。

2.      QMNCQnnn:高级队列

QMNC进程对于AQ表来说就相当于CJQ0进程之于作业表。QMNC进程会监视高级队列,并警告从队列中删除等待消息的“出队进程”(dequeuer):已经有一个消息变为可用。QMNCQnnn还要负责队列传播(propagation),也就是说,能够将在一个数据库中入队(增加)的消息移到另一个数据库的队列中,从而实现出队(dequeueing)。

Qnnn进程对于QMNC进程就相当于Jnnn进程与CJQ0进程的关系。QMNC进程要通知Qnnn进程需要完成什么工作,Qnnn进程则会处理这些工作。

QMNCQnnn进程是可选的后台进程。参数AQ_TM_PROCESSES 可以指定最多创建10个这样的进程(分别名为Q000,…,Q009),以及一个QMNC进程。如果AQ_TM_PROCESSES设置为0,就没有QMNCQnnn进程。不同于作业队列所用的Jnnn进程,Qnnn进程是持久的。如果将 AQ_TM_PROCESSES设置为10,数据库启动时可以看到10Qnnn进程和一个QMNC进程,而且在实例的整个生存期中这些进程都存在。

3.      EMNn:事件监视器进程(Event Monitor Process

EMNn进程是AQ体系结构的一部分,用于通知对某些消息感兴趣的队列订购者。通知会异步地完成。可以用一些Oracle调用接口(Oracle Call InterfaceOCI)函数来注册消息通知的回调。回调是OCI程序中的一个函数,只要队列中有了订购者感兴趣的消息,就会自动地调用这个函数。EMNn后台进程用于通知订购者,第一次向实例发出通知时会自动启动EMNn进程。然后应用可以发出一个显式的message_receive(dequeue)来获取消息。

4.      MMAN:内存管理器(Memory Manager

这个进程是Oracle 10g 中新增的,自动设置SGA大小特性会使用这个进程。MMAN进程用于协调共享内存中各组件(默认缓冲区池、共享池、Java池和大池)的大小设置和大小调整。

5.      MMONMMNLMnnn:可管理性监视器(Manageability Monitor

这些进程用于填充自动工作负载存储库(Automatic Workload RepositoryAWR),这是Oracle 10g中新增的一个特性。MMNL进程会根据调度从SGA将统计结果刷新输出至数据库表。MMON进程用于“自动检测”数据库性能问题,并实现新增的自调整特性。Mnnn进程类似于作业队列的JnnnQnnn进程;MMON进程会请求这些从属进程代表它完成工作。Mnnn进程本质上是临时性的,它们将根据需要来来去去。

6.      CTWR:修改跟踪进程(Change Tracking Process

这是Oracle 10g数据库中新增的一个可选进程。CTWR进程负责维护新的修改跟踪文件,有关内容见第3章的介绍。

7.      RVWR:恢复写入器(Recovery Writer

这个进程也是Oracle 10g数据库中新增的一个可选进程,负责维护闪回恢复区中块的“前”映像(见第3章的介绍),要与FLASHBACK DATABASE命令一起使用。

8.      其他工具后台进程

这就是完整的列表吗?不,还有另外一些工具进程没有列出。例如,Oracle Data Guard有一组与之相关的进程,有利于将重做信息从一个数据库移送到另一个数据库,并应用这些重做信息(详细内容请见OracleData Guard Concepts and Administration Guide)。还有一些进程与Oracle 10g新增的数据泵工具有关,在某些数据泵操作中会看到这些进程。另外还有一些流申请和捕获进程。不过,以上所列已经基本涵盖了你可能遇到的大多数常用的后台进程。

5.3   从属进程

下面来看最后一类Oracle进程:从属进程(slave process)。Oracle中有两类从属进程:I/O从属进程和并行查询从属进程。

5.3.1             I/O从属进程

I/O从属进程用于为不支持异步I/O的系统或设备模拟异步I/O。例如,磁带设备(相当慢)就不支持异步I/O。通过使用I/O从属进程,可以让磁带机模仿通常只为磁盘驱动器提供的功能。就好像支持真正的异步I/O一样,写设备的进程(调用者)会收集大量数据,并交由写入器写出。数据成功地写出时,写入器(此时写入器是I/O从属进程,而不是操作系统)会通知原来的调用者,调用者则会从要写的数据列表中删除这批数据。采用这种方式,可以得到更高的吞吐量,这是因为会由I/O从属进程来等待慢速的设备,而原来的调用进程得以脱身,可以做其他重要的工作来收集下一次要写的数据。

I/O从属进程在Oracle中有两个用途。DBWnLGWR可以利用I/O从属进程来模拟异步I/O,另外RMAN写磁带时也可能利用I/O从属进程。

有两个参数控制着I/O从属进程的使用:

q         BACKUP_TAPE_IO_SLAVES:这个参数指定RMAN是否使用I/O从属进程将数据备份、复制或恢复到磁带上。由于这个参数是围绕着磁带设备设计的,而且磁带设备一次只能由一个进程访问,所以这个参数是一个布尔值,而不是所用从属进程的个数(这可能出乎你的意料)。RMAN会为所用的物理设备启动多个必要的从属进程。BACKUP_TAPE_IO_SLAVES = TRUE时,则使用一个I/O从属进程从磁带设备读写。如果这个参数为FALSE(默认值),就不会使用I/O从属进程完成备份。相反,完成备份的专用服务器进程会直接访问磁带设备。

q         DBWR_IO_SLAVES:这个参数指定了DBW0进程所用I/O从属进程的个数。DBW0进程及其从属进程总是将缓冲区缓存中的脏块写至磁盘。这个值默认为0,表示不使用I/O从属进程。注意,如果将这个参数设置为一个非0的值,LGWRARCH也会使用其自己的I/O从属进程,LGWRARCH最多允许4I/O从属进程。

DBWR I/O从属进程的名字是I1nnLGWR I/O从属进程的名字是I2nn,这里nn是一个数。

5.3.2      并行查询从属进程

Oracle 7.1.6 引入了并行查询功能。这个功能是指:对于SELECTCREATE TABLECREATE INDEXUPDATESQL语句,创建一个执行计划,其中包含可以同时完成的多个(子)执行计划。将每个执行计划的输出合并在一起构成一个更大的结果。其目标是仅用少量的时间来完成操作,这只是串行完成同一操作所需时间的一小部分。例如,假设有一个相当大的表,分布在10个不同的文件上。你配置有16CPU,并且需要在这个表上执行一个即席查询。另一种方法是:可以将这个查询计划分解为32个小部分,并充分地利用机器;而不是只使用一个进程串行地读取和处理所有数据。相比之下,前一种做法要好得多。

使用并行查询时,会看到名为Pnnn的进程,这些就是并行查询从属进程。处理一条并行语句时,服务器进程则称为并行查询协调器(parallel query coordinator)。操作系统上服务器进程的名字并不会改变,但是阅读有关并行查询的文档时,如果提到了协调器进程,你应该知道这就是原来的服务器进程。

5.4   小结

我们已经介绍了Oracle使用的文件,涵盖了从低级但很重要的参数文件到数据文件、重做日志文件等等。此外深入分析了Oracle使用的内存结构,包括服务器进程中的内存(PGA)和SGA,还了解了不同的服务器配置(如共享服务器模式和专用服务器模式的连接)对于系统如何使用内存有着怎样显著的影响。最后,我们介绍了进程(或线程,这取决于操作系统),Oracle正是通过这些进程来完成功能。下面可以具体看看Oracle另外一些特性的实现,如锁定、并发控制和事务。