Oracle编程艺术

第二章 - 体系结构

1
.绑定变量,不写死常量: 1)绑定变量的话,因为SQL语句是不变的,数据库只解析一次语句(软解析),但是如果绑定的是常量的话,则会多次解析(硬解析),效率就会变慢,差一个数量级。 硬解析一个查询时,数据库会更长时间的占用一种低级串行化设备,称为闩。 for i in 1 .. 100000 loop execute immediate 'insert into t values (:x)' using i; -- 绑定变量,时间:3.932 execute immediate 'insert into t values (' || i || ')'; -- 绑定常量,时间:29.701 end loop; 2)使用拼接字符会有“SQL注入”的危险。 2.并发控制: 1)锁机制: oracle只在修改时对数据加“行级锁”,正常情况下不会升级到“块级锁”或“表级锁”。 如果只是读数据,oracle绝不会对数据加锁。 写操作不会阻塞读操作(读不会被写阻塞)。 如果会话A想改一行数据,但是这行数据已经被另外一个会话B锁定,此时会话A才会被阻塞。但是如果会话A仅仅是想读数据则不会被阻塞。 2)多版本控制: 读一致查询:对于一个给定的时间点,查询会产生一致的结果。 非阻塞查询:查询的会话不会被写入的会话阻塞,但在其他数据库中可能不是这样。 3)实际上是DELETE命令在删除数据之前,把这些数据放在一个被称为undo段(undo segment)的数据区,这个数据区也被称为回滚段(rollback segment) DECLARE CURSOR C_EMP IS SELECT * FROM t; c_row t%rowtype; BEGIN OPEN C_EMP; DELETE FROM t; COMMIT; LOOP fetch C_EMP into c_row; EXIT WHEN C_EMP%NOTFOUND; DBMS_OUTPUT.put_line(c_row.username); -- 虽然前面已经删除了,但是结果是有输出的 END LOOP; CLOSE C_EMP; END; 4)闪回: 从9i版本开始,我们可以通过闪回查询特性来指示oracle提供任何指定时间的查询结果(对于这个时间可能会有一些限制)。 使用闪回查询(即as of scn 或者 as of timestamp)来看某个时间点上表有什么 select cnt_now, cnt_then, :scn then_scn, dbms_flashback.get_system_change_number now_scn from (select count(*) cnt_now from emp), (select count(*) cnt_then from emp as of scn :scn) 5)读一致性和非阻塞读 oracle会利用多版本控制来得到结果,也就是查询开始是那个时间点的结果,在查询的过程中,不需要对任何数据加锁。 只要我们更新数据,oracle就会在2个不同的位置(redo、undo)进行记录(大多数其他数据库可能会把redo/undo放在一起,并将其当成“事务数据”)。放在redo中的记录用于重做或者“前滚”事务,比如insert oracle就会在redo记下插入的行;如果是delete,则是一个简单的消息(要删除的行放在undo)。放在undo中的记录是为了对应事务失败或者回滚而准备的,此时,oracle会undo中读取“修改前的数据镜像”来 恢复数据(当时的scn号来确定当时读取的数据)。此外oracle还会用它构建数据库修改前的状态,也就是说可以把块恢复到查询开始时的状态。这样一来,你不仅可以得到正确一致的答案,也不会对任何数据加锁。 体系结构概述: 数据库:操作系统文件或磁盘的集合。当使用oracle的自动存储管理或裸设备分区时,数据库可能不是操作系统中单独的文件,但是定义任然不变。 1.单租户数据库:这种数据库完整的包含了全套的数据文件、控制文件、重做日志文件、参数文件等等。它包含了oracle内部使用的所有的元数据(比如ALL_OBJECTS的定义)、数据、代码(比如DBMS_OUTPUT), 以及所有应用的元数据、数据和代码。版本12c之前所有的oracle数据库都是这种类型的数据库。 2.容器数据库或根数据库(CDB):这种数据库包含了一整套数据文件、控制文件、重做日志文件及参数文件等,但它仅用于存放oracle的元数据、数据库自用数据库及oracle内部代码。在这个数据库的数据文件中不(应) 存放应用的数据及代码而仅仅(应)存放上面提到的oracle自用的实体。这种数据库是完全自包含的,它不依赖于其他对象就可以被实例装载和打开。 3.可插拔式数据库(PDB):这种数据库仅包含数据文件。它并不是完全自包含的:我们必须将其插在某个容器数据库上才可对其打开进而进行读写。这个数据库仅包含应用的元数据及应用的对象、数据及代码,它不包含oracle 的元数据或oracle的内部代码。它仅仅包含数据文件,没有(以前我们所常见的oracle数据库的)重做日志文件、控制文件、参数文件等,但它在使用过程中会利用其所插在的CDB上的这些类型的文件。 实例:一组oracle后台进程/线程以及一个共享内存区域,这些内存由同一台电脑上运行的线程/进程所共享。oracle在这里存放及维护易失的、非持久性的内容(有些可以刷到磁盘)。需要注意的是,就算没有磁盘存储, 数据库实例也能存在。 数据库和实例的关系:单租户或容器数据库可以由多个实例装载和打开,而实例在任何时间点只能装载和打开一个数据库。实际上,准确地讲,实例在其整个生存期中最多能装载和打开一个数据库! 对于可插拔式数据库来说,它在任何时间点只能与一个容器数据库关联,进而(间接地)只能与一个实例关联,实例打开并装载了CDB之后,其包含的PDB也会使用这个实例。 因此,与CDB一样,PDB在任何时刻可能会被一个或多个实例打开。但与CDB/单租户数据库不同的是,一个实例每次可以同时访问许多(最多约250)个PDB,即每个实例可以为 多个PDB同时提供服务,但是它只能打开一个CDB或单租户数据库。 重申一遍: 1.实例是一组后台进程和共享内存 2.数据库(CDB或单租户数据库)是磁盘上存储的数据集合。 3.实例“一生”只能装载并打开一个数据库。后面我们会看到“可插拔式数据库”可多次的“打开”并“关闭”,但是对于像单租户数据库及容器数据库这样的完全自包含的数据库来说,每个实例只能打开/关闭一次。 4.数据库可以由一个或多个实例(使用RAC)装载和打开,装载一个数据库的实例数量可能会随时变化。 SGA和后台进程: oracle有一个很大的内存块,称为系统全局区(SGA)用于(但不限于) 1.维护所有进程需要访问的内部数据结构 2.缓存磁盘上的数据,另外重做数据写至磁盘之前先在这里缓存 3.保存已解析的SQL计划,等等 oracle有一组“附加到”SGA的进程,附加机制因操作系统而异。在Unix/Linux环境中,这些进程会附到一个很大的共享内存段,这是操作系统中分配的一个内存块,可以由多个进程并发的访问。 在windows中,这些进程使用C调用(malloc())来分配内存,因为它们实际上是一个大进程中的线程,所以会共享相同的虚拟内存空间。 连接oracle:oracle服务器处理请求的2种最常见的方式:专用服务器和共享服务器 专用服务器: 在我们登录时,oracle总会为我们创建一个新的进程。这通常称为专用服务器配置,因为这个服务器进程会在我们的会话生存期中专门为我们服务。对于每一个会话,都会出现一个新的专用服务器进程, 会话与专用服务器之间存在一对一的映射。这个服务器不是实例的一部分,我们的客户进程(或任何连接数据库的程序)会通过某种网络通道(如TCP/IP套接字)与这个专用服务器直接通信,并由这个服务器 进程接收和执行我们的SQL。 共享服务器: 数据库不会对每个用户连接创建新的线程或Unix/Linux进程。在共享服务器中,oracle使用一个“共享进程”池为多个用户提供服务。共享服务器实际上就是一种连接池机制。利用共享服务器,我们不必为10000个 数据库会话创建10000个专用服务器进程/线程(这样进程/线程就太多了),而只需建立很少的一部分进程/线程。这些进程/线程由所以会话共享,这个oracle就能让更多的用户与数据库连接。共享服务器通常与 实例一同启动。 共享服务器连接和专用服务器连接之间有一个重大区别:与数据库连接的客户进程不会与共享服务器直接通信,但专用服务器则不然,客户进程会与专用服务器直接通信。之所以不能与共享服务器直接对话,原因是 在于这个服务器进程是共享的。为了共享这些进程,还需要另外一个机制,通过这种机制才能与服务器进程对话。为此,oracle使用一个或一组称为调度程序的进程。客户进程通过网络与一个调度程序进程通信,这个 调度程序进程将客户的请求放到SGA中的请求队列(这也是SGA的用途之一)。即需要调度程序把多个客户请求放到SGA的请求队列上再排队分配到多个共享服务器。 可插拔式数据库: PDB在多租户架构下是一组非自包含的数据文件集合,它仅包含应用的数据与元数据。oracle自用的数据并不存放在这些数据文件中,而放在容器数据库中。如果要使用或者查询PDB,我们就必须要将其插到一个容器数据库中。 CDB中只有oracle自用的数据及元数据,这些是oracle运行时所需的必要的信息。PDB会存放“剩下的”数据及元数据。 oracle设计PDB及多租户架构主要有2个目的: 1.在单个主机上,有效的降低多个数据库/应用对资源的使用量。 2.在单个主机上,降低DBA对多个数据库/应用进行维护的工作量。

 

 

第三章 - 文件

与实例相关的8类主要文件:
1.参数文件:这些文件告诉oracle实例在哪里可以找到控制文件,应当给某种内存结构设置多大空间等。 2.跟踪文件:这通常是一个服务器进程对某种异常错误条件作出响应时创建的诊断文件。 3.警告文件:与跟踪文件类似,但是包含“期望”事件的有关信息,并且通过一个集中式文件(其中包括多个数据库事件)警告DBA。 4.数据文件:这些文件是数据库中主要文件,其中包括表、索引和所有其他的段。 5.临时文件:这些文件能告诉你数据文件、临时文件和重做日志文件在哪里,还会指出与文件状态有关的其他元数据。RMAN(oracle提供的备份恢复工具)也会在这个文件存放数据库备份的信息 6.重做日志文件:这些就是事务日志。 7.密码文件:这些文件用于认证数据库的SYS角色用户。 从oracle database 10g开始,又增加2种新的可选文件,它们可以帮助oracle实现更快的备份和恢复操作。 1.修改跟踪文件:这个文件用于oracle数据库建立增量备份。修改跟踪文件不一定非得放在快速恢复区。 2.闪回日志文件:这些文件存储数据库块的“前映象”,它用于新增的FLASHBACK DATABASE命令。 通常与数据库有关的其他类型的文件: 1.转储文件:这些文件由oracle提供的一个工具exp(导出)生成,并由工具imp(导入)使用。需要注意的是exp命令在当前版本已经被弃用了,但是imp命令还是可以继续使用,这是 为了支持数据库从老版本数据库导入到新版本的数据库当中。 2.数据泵文件:这些文件由oracle数据泵expdp导出进程生成,并由数据泵impdp导入进程使用。外部表也可以创建和使用这种文件格式。 3.平面文件:这些无格式文件可以在文本编辑器中查看。通常会使用这些文件向数据库中加载数据。 以上文件中最重要的是数据文件和重做日志文件。 参数文件: 数据库的参数文件通常称为初始化文件,或init.ora文件。这是因为历史上它的默认名就是init<ORACLE_SID>.ora。之所以称之为“历史上”的默认名,原因是从Oracle Datebase 9iRelease1以来,对于存储数据库的参数 设置,引入了一个有很大改进的新方法:服务器参数文件,简称为SPFILE。这个文件的默认名为spfile<ORACLE_SID>.ora 什么是参数: 我们可以把数据库参数看成是一个键值对。 init.ora 参数文件(PFILE参数文件): Oracle init.ora 文件相当简单,里面包含一些键值对。文件示例: control_files='/u01/dbfile/ORA12CR1/control01.ctl','/u01/dbfile/ORA12CR1/control02.ctl' db_block_size=8192 db_name='ORA12CR1' 参数文件由很多参数可以使用默认值而不用设置,但是它至少要告诉示例2件事:1.数据库的名字 2.控制文件的位置。Oracle是从控制文件来找到其他文件的位置。 默认情况下: 名字: init$ORACLE_SID.ora (UNIX/LINUX 环境变量) init%ORACLE_SID%.ora (Windows 环境变量) 目录: $ORACLE_HOME/dbs (UNIX/LINUX) %ORACLE_HOME%\DATABASE (Windows) 缺点: init.ora参数文件不一定位于数据库服务器上,之所以会引入存储参数文件,原因之一就是为了补救这种情况。当你在客户端使用init.ora参数文件来启动数据库时,客户端必须有这个文件。 因此Oracle引入了服务器参数文件,数据库可以将其作为参数设置的、唯一正确的“信息来源”。 服务器参数文件: 在访问和维护实例参数设置方面,SPFILE是Oracle做出的一个重要改变。有了SPFILE,可以消除传统参数文件存在的2个严重问题; 1.它杜绝了参数文件的繁殖。SPFILE总是存储在数据库服务器上,它必须存在服务器主机本身,不能放在客户机上,这样就只有一个“正确的”参数“来源”。 2.它再也无需(实际是不能)在数据库之外使用文本编辑器手动的维护参数文件。你必须利用ALTER SYSTEN 命令才能将参数设置写入SPFILE。 命名规则: $ORACLE_HOMR/dbs/spfile$ORACLE_SID.ora (UNIX/LINUX 环境变量) %ORACLE_HOMR/database/spfile%ORACLE_SID%.ora (Windows 环境变量) 1.从PFILE转换为SPFILE: 命令: create SPFILE from pfile; 从SPFILE参数文件可以看出:集群中所有实例共享的参数设置都以*.开头。单个实例特有的参数设置都以实例名(Oracle SID)为前缀。 2.设置SPFILE中的参数值: SPFILE是二进制文件,它们不可以用文本编辑器来编辑。需要使用alter system 命令,语句如下(<>中的部分是可选的,其中的管道符号(|)表示“取候选列表中的一个选项”) 默认情况下,alter system set 命令会更新当前会话所连接的实例(如果使用可插拔式数据库,则会更新当前连接的可插拔式数据库的数据字典,并且同时更新SPFILE。 Alter system set parameter=value <comment='text'> <deferred> <scope=memory|spfile|both> <sid='sid|*'> <container=current|all> parameter=value:表示我们要改的参数名以及参数的新值。 comment='text':是一个与此参数设置相关的可选注释。 deferred:指定系统修改是否只对以后的会话生效(对当前建立的会话无效,包括执行此修改的会话),默认情况下是会立即生效,但是有些参数不能“立即”修改,只有新建的会话才能 使用这些修改之后的参数。这个命令可以查看哪些参数需要用到这个参数:SELECT NAME FROM V$parameter WHERE issys_modifiable = 'DEFERRED'; scope=memory|spfile|both:指示了这个参数设置的“作用域”。设置参数值时作用域有以下选择。 scope=memory:只在实例中修改,数据库重启后本次修改将会丢失。下次重启数据库时,此参数设置以SPFILE为准。 scope=SPFILE:只修改SPFILE只的值。数据库重启之后这个修改才会生效。有些参数只能使用这个选项来修改,例如:processes参数就必须使用SCOPE=SPFILE。 scope=both:指内存和SPFILE中都会完成参数修改 sid='sid|*':主要用于集群环境,默认值为sid='*',SID选项可为集群中的每个实例单独的指定参数。如果不使用Oracle RAC,一般不需要指定SID=。 container=current|all:适用于多租户架构的数据库,用于指定参数设置是应用到当前连接的或者所有可插拔式数据库。可插拔式数据库的参数存储于其自身的数据字典当中,而非SPFILE 当我们将其移动到另外一个容器时,其自身的参数设置也会随之一并转移。 3.取消SPFILE中的设置: 命令: Alter system reset parameter <scope=memory|spfile|both> sid='sid|*' 4.从SPFILE创建PFILE 命令: create PFILE from spfile; 这个命令根据二进制格式的SPFILE文件创建一个纯文本格式的PFILE文件,生成这个文件可以在任何文本编辑器中编辑,并且以后可以用来启动数据库。正常情况下,使用这个命令至少有2个原因 1.创建一个“一次性”参数文件,用于启动数据库来完成维护工作。执行命令生成PFILE参数文件,再对PFILE文件进行修改。重启数据库,注意不能使用SPFILE来启动,而是要用刚才编辑的PFILE 并且在STARTUP命令中使用从句PFILE=<FILENAME>。进行维护工作后,正常启动数据库。 2.维护修改历史,在注释中记录修改。SPFILE不支持历史注释记录。可以备份参数PFILE来实现。 5.修正被破坏的SPFILE: 可以从警告日志恢复参数文件的信息。每次启动数据库时,警告日志都会显示出SPFILE的信息。 有了内容就可以创建一个SPFILE,再用create SPFILE来转换为新的SPFILE 6.可插拔式数据库: 可插拔式数据库由一组文件构成,你可以方便的将其从一个容器数据库移动到另外一个上。在移动的过程中,PDB内所包含的所有的模式、用户、元数据、授权、数据以及PDB的参数设置(非从CDB 继承的参数)也会一并转移。PDB的参数之所以能够转移,因为Oracle将其存放在一个字典表sys.PDB_SPFILE$中。 跟踪文件: 跟踪文件能提供调试信息。服务器遇到问题时,它会生成一个包含大量诊断信息的跟踪文件。 Oracle数据库具有良好的可测量性,数据库中这种可测量性反映在以下几个方面: 1.V$视图:大多数V$视图都包含“同时”信息。V$WAITSTAT、V$SESSION_EVENT还有其他许多V$视图能让我们知道内核到底发生了什么。 2.审计命令:利用这个命令,能指定数据库要记录哪些事件以便日后分析。 3.资源管理器:这个特效允许对数据库所使用的资源进行更精细的管理。Oracle的资源管理器充分利用了内核中的可测量代码来了解数据库运行时 各种资源的使用状况,这样它就有可能对这些资源的使用做进一步的控制。 4.Oracle“事件”:能让Oracle生成所需的跟踪或诊断信息。 5.DBMS_TRACE:这是PL/SQL引擎中的一个工具,它会全面的记录存储过程的调用树、产生的异常以及遇到的错误。 6.数据库事件触发器:这些触发器允许你监控和记录你觉得“意外”或非正常的情况。例如。你可以记录发生“临时空间用尽”错误时正在运行的SQL。 7.SQL_TRACE/DBMS_MONITOR:这个工具用于查看数据库执行的SQL语句、等待事件以及其他特效/行为的诊断信息。我们还可以通过一些Oracle的扩展功能比如说 10046 Oracle Event来启动SQL跟踪。 跟踪文件可分成2大类: 1.计划内、由用户请求所产生的跟踪文件:如启用DBMS_MONITOR.SESSION_TRACE_ENABLE 产生的跟踪文件。这类文件包含会话相关的诊断信息,它有助于你调整及优化应用的性能, 并诊断出瓶颈到底在哪里。 2.计划外、数据库服务器自动产生的跟踪文件:当数据库服务器发生某种严重错误时,它会自动生成这些跟踪文件。这些错误包括(但不限于)ORA-0600 “Internal Error” (内部错误)。 这些跟踪文件包含一些诊断信息,它主要对Oracle Support 的分析人员有用,但对我们来说,除了能看出应用中哪里出现内部错误外,用处不大。 计划内、由用户请求所产生的跟踪文件: 这类跟踪文件通常可由DBMS_MONITOR 来生成。这些跟踪文件包含与诊断和性能有关的信息。它们对于了解数据库应用的内部工作有着非凡的意义。在一个正常运行的数据库中,这类跟踪文件最为常见。 1.跟踪文件的位置: 不论是使用SQL_TRACE/DBMS_MONITOR还是扩展的跟踪工具,Oracle都会在数据库服务器主机的以下2个位置之一生成跟踪文件: 1.如果使用专用服务器连接,会在 user_dump_dest 参数指定的目录中生成跟踪文件。 2.如果使用共享服务器连接,则在 backgroud_dump_dest 参数指定的目录中生成跟踪文件。 我们有很多途径可以指定跟踪文件的存放位置:比如你可以从SQL*PLUS执行 show parameter dump_dest,也可以直接查询V$PARAMETER视图。 background_dump_dest:后台转储目标用于所以“服务器”进程。 core_dump_dest:当发生严重问题时(如进程崩溃)服务器会在内核转储目标自动生成相应的跟踪文件,其中包含了发生异常的进程的详细信息。 user_dump_dest:用于专用/共享服务器的跟踪文件 在Oracle Database 11g 增加Default Trace File信息之前,你必须手动(通过组织各种信息)才能查找到跟踪文件。如果你使用的是一个共享服务器连接,那么真正为你服务的是一个后台进程, 所以跟踪文件的位置由 backgroud_dump_dest 确定。如果你在使用一个专用服务器连接,那你是使用一个用户或前台进程与Oracle交互,因此跟踪文件会放在由 user_dump_dest 参数指定的 目录中。一旦出现一个严重的Oracle内部错误,或者Oracle Support 要求你生成一个跟踪文件来得到额外的调试信息,这时生成“内核”转储文件,而 core_dump_dest 参数就定义这类“内核” 文件还在那里生成。 2.命名约定: 跟踪文件名可以分为以下几个部分: 1.文件名的第一部分是 ORACLE_SID (9i Release 1 例外) 2.文件名的下一部分只有一个 ora 3.跟踪文件名中的数字部分是专用/共享服务器在操作系统中的进程ID,可以在 V$PARAMETER 视图得到。 3.对跟踪文件加标记: 当我们无权限访问 V$PARAMETER 和 V$SESSION 的时候,想得到跟踪文件的名字就比较困难。这时就可以对跟踪文件“加标记”来找到它。 alter SESSION set tracefile_identifier = 'Look_For_Me'; 这个命令就会使跟踪文件名包含这个“Look_For_Me”,用ls就可以找到这个文件。 计划内、由用户请求所产生的跟踪文件: 这些跟踪文件不是给你我用的,它们只对Oracle Support 有用。不过,当我们向 Oracle Support 提交服务请求时,这些跟踪文件会很有用。这样做很重要:如果你碰到了数据库的内部错误,更正这个错误 的唯一办法就是提交一个服务请求。 警告文件: 警告文件就是数据库的日记。这是一个简单的文本文件,数据库从创建那天起就会不停的写入改文件,直到数据库被删除为止。在这个文件中你可以看到数据库的“编年史”。比如在线日志文件的切换、内部错误、 表空间的创离线以及恢复为在线等等。 数据文件: 数据文件和重做日志文件是数据库中最重要的文件,你的数据库最终会存储在这些文件中。每个数据库都至少有一个数据文件,通常不止一个。用最简单的crate database 命令根据默认设置创建一个数据库, 这个数据库会有3个数据文。其中一个对应system 表空间(存放Oracle的数字字典),一个对应sysaux表空间(在10g 及以上版本中,非字典对象都存储在这个表空间中),最后一个对应user表空间。 所有数据库都至少有这3个数据文件。 .../dbs1ora12c.dbf .../dbx1ora12c.dbf .../dbu1ora12c.dbf 简单回顾文件系统的机制: 在Oracle中,可以用4种文件系统机制存储你的数据(12c只有3种)。这里指你的数据是指数据字典、redo、undo记录、表、索引以及LOB等,也就是你每天关注的数据。有以下几种方式存放这些数据。 1.格式化好(cooked)的操作系统(OS)文件系统:数据库的文件就像文字处理文档一样放在文件系统中。在Windows上你可以使用资源管理器来查看这些文件,在UNIX/LINUX上,可以通过ls命令。 你也可以使用简单的OS工具(如Windows上的xcopy或UNIX/LINUX上的cp)来移动文件。Cooked OS 文件一直是 Oracle 中存储数据的“最流行”的方法,不过我个人认为,随之ASM的引入,这种情况 会有所改观。数据库服务器中,格式化好的文件系统通常也会缓存数据,也就是说在数据库读写磁盘时,OS会为你缓存一些信息。 2.裸分区:这不是文件,而是裸磁盘。你不能用ls来查看,也不能在Windows资源管理器中查看其内容。它们就是磁盘上的一些大扇区,上面没有任何文件系统。对Oracle来说,整个裸分区就是一个大文件, 这与前面的文件系统不同,文件系统上可能有几十个甚至数百个数据库文件。目前,只有极少数Oracle数据库使用裸分区,这是因为裸分区的管理开销很大。此外,裸分区没有缓冲,所以的 I/O都是对存储设备的直接操作,它没有任何OS缓冲(对于数据库来说,这通常是个优点)。 (Oracle从11g就已经弃用裸分区,并且在12c中不再提供支持) 3.自动存储管理(ASM):这是Oracle Database 10g Release 1 的一个新特性。在11g r2 之前,ASM是一个仅供数据库使用的文件系统,你可以简单的把它看做一个数据库文件系统。在这个文件系统上, 你不能存放如<购物清单.txt>这样的文件,只能存储与数据库相关的信息:表、索引、备份、控制文件、参数文件、重做日志文件以及归档文件等。不过,即使是ASM,也同样、 存在着数据文件。它概念上讲,数据库仍存储在文件中,不过现在的文件系统是ASM。ASM可以在单机或者集群环境中工作。从11g R2起,你甚至还可以通过ASM构建一个集群文件系统 4.集群文件系统(ACFS):Oracle RAC (真正应用集群)需要使用这种文件系统,它为集群中所有的节点提供一个共享的文件系统。传统的文件系统只能由集群环境中的一台计算机使用。你可以使用诸如Samba或者NFS 服务(Samba与NFS类似,可以在Windows/UNIX/LINUX环境之间共享磁盘),来向集群提供一个共享文件系统,但是如果这个共享服务器出问题的话,将会导致它所提供的文件系统不可用。在 11hR2之前,Oracle有集群文件系统(Oracle Cluster File System,OCFS),但是它只能在Windows和UNIX/LINUX上使用。你也可以使用一些经过Oracle认证的第三方的集群文件系统。 Oracle在11gR2开始提供了自动存储管理集群文件系统(ACFS),这个集群文件系统让cooked文件系统的优点延伸到了集群环境中。 你的数据库所组成的文件可以使用上述文件系统中的任意组合,Oracle不会限制你只能选其中的一个。 Oracle 数据库中的存储层次体系: 数据库由一个或多个表空间构成。表空间是Oracle中的一个逻辑存储容器,位于存储层次体系的顶层,包括一个或多个数据文件。 1.段 段是表空间中主要的组织结构。段就是占用存储空间的数据库对象,如表、索引以及回滚段等。你在创建表时,会创建一个表段。你在创建分区表时,创建的不是表的段,而是为每个分区创建一个段。 当你创建一个索引时,就会创建一个索引段,以此类推。占用存储空间的每个对象最后都会存储在一个段中,除了刚才介绍的那些类段外,还有回滚段、临时段以及聚簇段等。 2.区段: 段本身又由一个或者多个区段组成。区段是文件中一个逻辑上连续分配的空间。(一般来讲,文件本身在磁盘上并不是连续的,否则根本就不需要消除磁盘碎片的工具了!另外利用诸如独立磁盘冗余阵列之类的 磁盘技术,你可以会发现,一个文件不仅在一个磁盘上不连续,还有可能跨多个物理磁盘)传统的每个段都至少有一个区段,自11gR2 起Oracle引入了“延迟”段的概念——当你创建对象时,Oracle不会立即为段分配 一个新的区段,只有当数据真正写入到这个对象时(insert语句),Oracle才会为这个段分配第一个区段。如果一个对象初始区段不足以容纳新增的数据,Oracle就会再为他分配另一个区段。第二个区段不一定在 磁盘位置上紧挨第一个区段,甚至有可能不在第一个区段所在的文件中。第二个区段可能与第一个区段相距甚远,但是区段内的空间总是文件中的一个逻辑连续空间。区段的大小可能不太,可以是Oracle数据块, 也可以是达到2GB。 3.块: 区段又进一步由块组成。块是Oracle中最小的空间分配单位。行数据、索引条目或临时排序结果就存储在块中。通常Oracle从磁盘读写的就是块,Oracle中块的常见大小有4种:2KB、4KB、8KB、16KB(尽管 在某些情况下32KB也是允许的,但是操作系统可能对最大大小有限制)。 一个段由一个或多个区段组成,区段则由连续分配的一些块组成。 数据库之所以允许有多个块大小,是为了实现一个名为“传输表空间”的功能。DBA可以利用这个功能来从一个数据库移动或复制已经格式化好的数据文件,把它放在另一个数据库中。例如一个数据库使用的块为 2KB,另一个数据库是8KB,如果一个数据库中不支持多种块大小,就无法传输这些信息。 在表空间内部,所有的块大小都是一致的。对于一个多段对象,如一个包含LOB列的表,可能会将这些段存放在不同的表空间(块大小可能不一样)中,但是段内块的大小一定是相同的。 块的基本格式: 1.首部:包含块类型的有关信息(表块、索引块等)、块上发生的活动事务和过去事务的相关信息(仅事务管理的块有此信息,例如临时排序块就没有事务信息)、以及块在磁盘上的地址(位置) 2.表目录:会记录在这个块上存储数据的所有的表(可能一个块存储了多个表的数据) 3.行目录:包含块中行的描述信息,这是一个指针数组,指出块中数据部分中的行。 4.空闲空间:未存放数据的空间 5.数据:已经存放数据的空间 6.尾部:标识块结束 4.表空间: 表空间是一个容器,其中包含段。每个段都只属于一个表空间。一个表空间中可能有多个段。一个段的所有区段都在段所属的表空间中。段绝不会跨越表空间边界。表空间本身可以有一个或多个数据文件。一个 区段仅会存放在一个数据文件中。不过段可以有来自多个不同数据文件的区段。 5.存储层次体系小结: 1.数据库由多个一个或者多个表空间组成 2.表空间由一个或者多个数据文件组成。这些文件可以是文件系统中的文件、裸分区、ASM管理的数据库文件】或集群文件系统上的文件。表空间包含段。 3.段由一个或者多个区段组成。段不能跨表空间存放,但是段中的数据可以跨文件(当然,隶属于同一个表空间)存放。 4.区段是磁盘上一组逻辑连续的块。区段只能在一个表空间中,而且总是在该表空间内的一个文件中。 5.块是数据库中最小的分配单位,也是数据库使用的最小I/O单位。 6.字典管理和本地管理的表空间: 表空间是如何管理区段? 在Oracle 8.1.5之前是通过数据字典,相应的这种表空间叫字典管理的表空间。所有的空间管理维护操作都是在数据字典表中,如果要存储新的数据则增加(更新、删除)一条数据。但是这个的缺点是不能 并发的处理而且查询需要更新、删除的数据的SQL效率(递归SQL)太慢。 在7.3版本中,Oracle引入了一个真正的临时表空间概念,这是一个新的表空间类型,专门用于存储临时数据,从而缓解上面问题。临时表空间中不能参加持久对象,这是它与持久的表空间最基本的区别, 此外它的空间管理还是通过数据字典表完成的。不过一旦在临时表空间分配了一个区段,系统就会一直持有(不会回收空间)。下次有人向临时表空间申请空间时,Oracle会在已分配区段列表中查找空闲 的区段。如果找到就重用,否则还是分配一个区段。这样就不用执行代价昂贵的递归SQL。 在Oracle 8.1.5及以后版本中,Oracle引入了本地管理表空间概念。这个本地管理表空间采用了与Oracle7.3中对临时表空间的相同的方法来管理空间,这样就无需使用数据字典来进行维护。本地管理表空间 会在每个数据文件中使用一个位图来管理区段,如果得到一个区段,系统所做的只是在位图中将某一位设置为1,要释放一些空间,系统再把这一位设置为0。相比于字典方式的处理,本地管理表空间在分配 和释放空间的速度就快很多。这样我们的空间分配操作就从数据库级别的串行长时间操作(递归SQL)变成了表空间级别的串行短时间操作了。 临时文件: 是一种特殊类型的数据文件。当内存不足时,Oracle 会使用它来存储一些临时文件,比如说一些比较大的排序或散列操作的中间结果、临时表中的数据以及结果集数据。 自12c起,对临时表的操作所产生的undo也会放到临时表空间中,而在以前的版本中,这部分undo是放在undo表空间中的,因而会联动产生redo。 Oracle以一种特殊的方式处理临时文件。一般而言,你对数据的每个修改都会存储在重做日志中。这些事务日志会在以后某个时间重新应用以“重做事务”,例如,数据库实例失败后进行恢复时就可能需要“重做事务”。 临时文件不包括在这个重新应用过程内,对临时文件内的数据的修改不会生成重做日志,由于undo总是受redo的“保护”(undo数据就像是表数据或索引数据一样,对undo的修改会生成一些redo,而这些redo会记入日志) 因此,这就会生成使用临时表的重做日志, 关于“真正的”临时文件:如果操作系统允许创建的话,临时表空间的文件则会以稀疏的方式创建,也就是说,这样的文件会“按需”占用磁盘的空间。即创建临时文件时,是不会占用实际的磁盘空间,只有当你使用到的 时候才会开始写入磁盘。 控制文件: 是一个相当小的文件(极端情况下能增长到64MB),它存储了数据库需要的一些文件的位置。数据库启动时,实例会从参数文件中知道控制文件的位置,而通过控制文件则会知道数据库的数据文件和在线操作日志 文件的位置。控制文件还记录了Oracle的其他一些信息,如检查点的有关信息,数据库名(与db_name参数一致)、数据库创建的时间戳、归档重做日志的历史(有时这会让控制文件变大)以及RMAN信息等。 控制文件应该通过硬件多路保存,如果硬件条件不支持,则要通过Oracle自身多路保存。每个数据库都应该有多个控制文件的副本,而且它们应该保存在不同的磁盘上,以防止出现磁盘故障而丢失控制文件。 重做日志文件: 它是数据库的事务日志。通常情况下重做日志是用于恢复的,但也可以用于: 1.系统崩溃后的实例恢复 2.从备份复原出来的数据文件的介质恢复 3.备用数据库处理 4.Streams和Golden Gate,这2个工具可以对重做日志进行挖掘,从而实现信息共享 5.让管理员能够通过Oracle LogMiner 功能查看数据库的历史事务 重做日志文件的主要目的是,当实例或存储介质出问题时,他就能派上用场。它也可以用于维护备用数据库从而实现故障时的切换。 你在Oracle中完成的几乎所有操作都会生成redo,并写入到在线重做日志文件中。当你向表中插入一行时,这一行也会写入到重做日志文件中。当你删除一行时,则会在重做日志文件中记下“删除”这个消息。 在线重做日志: 每个Oracle数据库都至少有2个在线重做日志文件组,每个重做日志组都包含一个或多个重做日志成员(redo按成员组来管理)。在同一个组内的重做日志文件是完全一样的镜像。这些在线重做日志文件的 大小是固定的,并以循环方式使用。例如,Oracle先写日志文件组1,当这组文件写满之后,会切换到日志文件组2,它会覆盖原来的内容。当日志文件组2填满时,Oracle会再切换回到日志文件组1(假设只有 2个重做日志文件组)。 从一个日志文件组切换到另一个日志文件组的动作称为日志切换。在这里重点注意:如果数据库配置不当,日志切换可能会导致整个系统临时性“暂停”。由于重做日志最重要的目标是在实例失败之后我们能 够恢复已经提交的事务,所以我们必须要保证,日志切换时所覆盖的重做日志中的内容不能包含实例所需要恢复的数据。 数据库高速缓存是临时存储数据库块的地方,这是Oracle SGA中的一个结构。当数据库从磁盘读取数据时,它会将数据库存储在这个区域中,这样以后就不必再去磁盘重新读取它们。设计数据库高速缓存主要是为了 提高性能,相对于内存I/O,物理磁盘的I/O操作速度很慢,有了数据库高速缓存,数据库就可以优化对磁盘的I/O操作。当数据块修改块(比如说更新块的上的一行)时,这些修改会在内存中完成,它会修改高速 缓存内的数据库,于此同时,数据库也会把重做这些修改所需的信息保存在重做日志缓冲区中,这是另一个SGA的数据结构。当用户提交时(commit),Oracle不会将SGA中修改的所有块写到磁盘上,它只是把重做 日志缓冲区的内容写到在线重做日志中。 在用户提交后,如果这时数据库主机突然断电,数据库高速缓存就会被彻底清空。如果发生这种情况,那么我们只能从在线重做日志文件中找到用户所做的修改。总之,修改的数据块再被保存到磁盘上的数据文件 之前,记录这个修改动作的在线重做日志文件是不能够被重用(覆盖)的。 这时数据库块写入器(DBWn)就开始登场了,DBWWn是Oracle的后台进程,它会在数据库高速缓存将满时与其他进程一起腾出一部分空间,它的另外一个作用就是检查点。检查点这个动作就是把脏块(已修改的块) 从数据库高速缓存中写至磁盘。 重做日志文件的大小和数目需要考虑问题: 1.高峰负载:你可能希望系统在业务高峰期是时候不会发生检查点的等待事件。这时你的重做日志的大小不应该以支撑“平均”的每小时业务吞吐量为目的,而应该以业务高峰吞吐量为基准。 2.大量用户修改相同的块:如果大量用户都要修改相同的块,你可能需要将重做日志文件设置的大些。因为每个人都在修改相同的块,最好尽可能多的更新之后才将其写入到磁盘。每个日志切换都会导致一个 检查点,索引你可能需要避免频繁的切换日志。不过这样一来又会影响恢复时间。 3.平均恢复时间:如果必须确保实例恢复要尽快的完成,即便是大量用户修改相同的块,我们也可能倾向于使用较小的重做日志文件。 归档重做日志 Oracle数据库可以采用2种模式运行:归档模式和非归档模式。这2种模式的区别在于Oracle重用重做日志文件时会发生什么动作,“会保留redo的一个副本吗?还是Oracle会将其重写,而永远失去原来的日志?” 如果没有以归档模式运行,磁盘出现问题,有2个选择: 1.删除与故障磁盘相关的表空间。只要表空间由一个改磁盘上的文件,就要删除这个表空间(包括表空间的内容)。如果影响到system表空间(Oracle的数字字典)或其他像UNDO这种重要的数据库相关的 表空间,那就不能用这个办法了。 2.恢复到数据备份的时间点,这个时间到故障的时间段的工作白做了。 以归档模式运行: 1.再找一个磁盘 2.从上一个数据备份的时间点把那些受影响的数据文件复原到这个磁盘上 3.对这些数据文件应用备份之后的归档重做日志,以及在线重做日志 密码文件: 不是数据库运行时必须要有的文件,它用于远程sysdba或管理员来访问数据库。 安装Oracle数据库的过程中我们需要指定“管理员组”。在UNIX/Linux上,这个组一般默认为DBA,在Windows上则默认为ORA_DBA,实际上这个组可以是操作系统上任意有效的组。这个组很“特殊”,因为这个组 中的任何用户都可以作为sysdba连接Oracle,并且无需指定用户或密码。 密码文件保存了一些用户名和密码,通过这些用户名和密码,sysdba用户可以通过网络远程进行登录。Oracle必须使用这个文件来认证sysdba用户,而不能通过数据库中存储的那些普通的用户名/密码。 修改跟踪文件: 也不是数据库运行时必须要有的文件,它是Oracle Database 10g 企业版中新增的一种文件,Oracle会在这个文件中记录自上一个增量备份以来哪些块已被修改。恢复管理器会使用这个文件来备份那些有变化的数据块, 而不必读取整个数据库。 闪回日志: 闪回日志是 Oracle Database 10g 中为支持 FLASHBACK DATABASE 命令而引入的,也是 Oracle Database 10g 企业版的一个新特性。闪回日志包含数据块“被修改之前的映象”,它用于将数据库返回(恢复)到之前 某个时间点的状态。 闪回数据库: 在以前的版本中,如果想把数据库恢复到以前的某个时间点去需要一个很耗时并且复杂的流程,引入 FLASHBACK DATABASE 这个命令之后,我们可以大大简化并加快这个流程。 这个命令没有之前: 1.DBA要关闭数据库 2.DBA(一般)要从磁盘机复原最近的一个完整的数据库备份,这通常比较耗时。通常我们得用RMAN命令:RESTORE DATABASE UNTIL <某个时间点> 3.如果归档日志不是放在数据库的服务器上,那么DBA可能还要复原最近的一个备份之后所生成的全部归档重做日志 4.接下来DBA要前滚数据库,这个过程通常要对复原出来的备份应用归档以及在线重做日志,并要在DROP USER 这个动作之前停止应用。 5.要以 RESETLOGS 方式打开数据库 这个过程很麻烦,步骤很多,通常需要花费很多时间(期间无法访问数据库) Oracle Database 10g 企业版之后: 1.DBA要关闭数据库 2.DBA启动并装载数据库,接下来使用 FLASHBACK DATABASE 命令,命令中指定的时间点可以为SCN(这算是Oracle内部的一个时钟)、复原点(SCN的一个指针)或者时间戳,这些时间应当精确到秒。 快速恢复区: Oracle数据库使用快速恢复区来管理与数据库备份和恢复相关的文件。在这块区域中,你可以找到: 1.RMAN的备份片(全量或增量备份) 2.RMAN所做的镜像备份(数据文件及控制文件的镜像备份) 3.在线重做日志 4.归档重做日志 5.多路的控制文件 6.闪回日志 DMP文件(EXP/IMP文件): 这个文件中会包含所有导出对象相关的元数据(CREATE 和 ALTER 语句形式)以及数据本身,由此我们可以重建表、模式甚至整个数据块。 DMP具有向前兼容性,新版本可以兼容旧版本。 DMP文件是平台独立的,无论导出数据的库在哪个平台上,你都可以放心将其传达到另外一个平台上导入。 DMP是二进制文件,也就是说你不能编辑修改这些文件。 数据泵文件: 这些文件由oracle数据泵expdp导出进程生成,并由数据泵impdp导入进程使用。外部表也可以创建和使用这种文件格式。 是跨平台的二进制文件,数据泵中的元数据以XML方式来存储。 平面文件: 平面文件就是一个每一“行”都是一条 “记录”的简单文件,在每行之间都有一些定界的符号,通常用逗号或管道符号(竖线)分隔。通过使用数据加载工具如SQLLDR或外部表,Oracle可以很容易的读取平台文件。

 

第四章 - 内存结构

内存结构: 系统全局区(SGA):这个是一个大的共享内存段,几乎所有Oracle进程都要访问这个区域 进程全局区(PGA):这是一个进程或线程专用的内存,其他进程
/线程不能访问 用户全局区(UGA):这个内存区与特定的会话相关联。它可能在SGA中分配,也可能在PGA中分配,这取决于是用共享服务器还是专用服务器来连接数据库。如果使用共享服务器,UGA就在SGA中分配(因为任何一个共享 服务器进程都能读写你的会话的数据),反之在PGA中分配, Oracle的内存管理有5种方法: 1.自动内存管理:它是针对SGA和PGA的管理,并且只能应用在Oracle Database 11g 或更高的版本上,这种模式下 DBA 只需要设置一个参数 MEMORY_TARGET 来指定数据库使用内存的目标,数据库将自行判断每个 内存区域的大小。 2.自动SGA内存管理:它是针对SGA的管理,这种模式下DBA通过设置参数 SGA_TARGET 来指定整个SGA的目标大小。 3.手动SGA内存管理:它是针对SGA的管理,DBA可以手动设置DB_CACHE_SIZE、SHAARED_POOL_SIZE 等参数来调整SGA中每个区域的大小 4.自动PGA内存管理:它是针对PGA的,DBA可以通过设置参数 PGA_AGGREGATE_TARGET 来指定整个PGA的目标大小 5.手动PGA内存管理:它是针对PGA的,DBA可以手动设置 SORT_AREA_SIZE 、HASH_AREA_SIZE 等参数来调整PGA中每个区域的大小。Oracle强烈建议不用使用该方法。 进程全局区和用户全局区: PGA是特定于进程的一段内存,是操作系统中某个进程或线程专用的内存,不允许系统中的其他进程或线程访问。PGA一般是通过C语言运行时调用malloc() 或 memmap()来分配,而且可以在运行时动态扩大(或收缩)。 PGA绝对不会在Oracle的SGA中分配,而总是由进程或线程为自身分配。PGA中的P代表Process (进程)或Program (程序),是不共享的。 UGA实际上来说,就是你的会话的状态,它是一段你的会话一直能访问到的内存。UGA的分配位置完全取决于你如何连接Oracle。如果使用共享服务器,UGA就在SGA中分配(因为任何一个共享服务器进程都 能读写你的会话的数据),反之在PGA中分配。 所以,PGA包含进程内存,还可能包含UGA。PGA内存中的其他区域通常用于完成内存中的排序、位图合并以及散列。可以肯定的说,除了UGA,这些区域在PGA中占比最大 从Oracle Database 9i Release 1 起,有2种方法来管理PGA中的这些非UGA内存。 1.手动PGA内存管理,采用这种方法时,你要告诉Oracle数据库,如果一个进程中需要排序或散列,允许使用多少内存来完成这些排序或散列 2.自动PGA内存管理,这要求你告诉Oracle数据库,PGA在系统范围内可以尝试使用多少内存。 从Oracle Database 11 Release 1 起,自动PGA内存管理可以使用以下2种技术实现 1.通过设置 PGA_AGGREGATE_TARGET 初始化参数,告诉Oracle数据库在实例范围内PGA可以尝试使用多少内存 2.通过使用 MEMORY_TARGET 初始化参数,告诉Oracle数据库实例应当允许SGA和PGA总共使用多大内存。数据库自己将根据这个参数确定合适的PGA大小。 PGA内存管理方法受数据库初始化参数 WORKAREA_SIZE_POLICY 的控制,而且可以在会话级别进行修改。Oracle Database 9i Release 2 及更高版本中,这个初始化参数默认为AUTO,表示自动PGA内存管理, 而在 Oracle Database 9i Release 1 中,这个参数的默认设置为 MANUAL。 手动PGA内存管理: 如果采用手动PGA内存管理,除了你的会话为PL/SQl中的表和其他变量分配的内存之外,以下参数对PGA大小的影响最大 1.SORT_AREA_SIZE:在排序信息被交换到磁盘之前,所使用的内存总量(磁盘是指用户指定的磁盘上的临时表空间) 2.SORT_AREA_RETAINED_SIZE:排序完成后用于保存已排序数据的内存总量。也就是说,如果SORT_AREA_SIZE是512KB且SORT_AREA_RETAINED_SIZE是256KB,那么服务器进程最初处理查询时会用512KB 的内存对数据进行排序。等到排完序时,排序区会“收缩”为256KB,这256KB内存中放不下的已排序数据会写出到临时表空间 3.HASH_AREA_SIZE:服务器进程在内存中存储散列列表所用的内存量。通常在做一个大数据集与另一个数据集的连接时,会进行散列连接并使用到这部分内存。2个数据集中较小的一个会被散列到内存中, 内存中的散列区中放不下的部分会通过连接键存储在临时表空间中。 在使用*_AREA_SIZE 参数时,需要记住以下重点的几点: 1.这些参数控制这一个排序、散列或位图合并操作所使用的最大内存量 2.一个查询可能包含多个操作,这些操作可能都要使用这部分内存,并且会创建多个排序/散列区。同时还要记住,你也可以同时打开多个游标,每个游标都有自己的 SORT_AREA_RETAINED 需求。所以,如果把 排序区大小设置为10MB,在会话中实际上可能会用到10MB,100MB、1000MB或更多内存。设置这些参数并非是对整个会话的限制,它们只是对会话中的一个操作进行限制。而在你的会话中,一个查询可以有 多个排序,或者多个查询需要用到一个排序。 3.这些内存区都是根据需要来分配的。如果像我们之前做的一样,将排序区大小设置为1GB,这并不是说你实际分配了1GB的内存排序区,而是说,你允许Oracle进程为一个排序/散列操作最多分配1GB的内存 自动PGA内存管理: 引入自动PGA内存管理是为了解决以下问题: 1.易用性:很多人并不清楚如何设置适当的 *_AREA_SIZE 参数 2.手动分配是一种“以一概全”的方法:一般来说,随着在一个数据库上运行类似应用的用户数的增加,排序和散列所用的内存量也会呈线性增长。如果有10个并发用户,每个用户排序区大小为1MB,这就会使用 10MB的内存。除非DBA能一直坐在数据库控制台前不断的为每个用户调整排序/散列区的大小,否则每个用户会一直使用同样的设置值。随着允许使用的内存量的增加,对磁盘上的临时表空间执行的物理 I/O在减少。查询的响应时间也会缩短。手动分配方式会把排序所用的内存量固定为一个常量,而不考虑实际上有多少内存是可用的。利用自动内存管理的话,我们可以使用所用可用的内存,数据库会 根据工作负载动态的调整实际我们可以使用的内存量 3.内存控制:根据上一条,手动分配很难(或许说不可能)控制Oracle实例在合理范围内使用内存。你不能控制实例要用的内存总量,因为你根本无法控制会发生多少并发的排序和散列。所以很有可能导致 数据库尝试使用的内存量超过了服务器所拥有的物理空闲内存 PGA内存管理: 首先简单的建立SGA并确定其大小。SGA是一段固定大小的内存,所以可以看到它的准确大小,这也将是SGA的总大小(除非你做了调整)。确定了SGA的大小后,再告诉Oracle数据库PGA的大小:“你总共有这些 内存来分配所有的工作区(排序区和散列区的一个新总称)”。理论上如果你有一台2G物理内存的服务器,可以分配768MB内存给SGA,768MB给PGA,余下的512MB内存留给操作系统和其他进程。 建立自动PGA内存管理时,需要为2个数据库实例初始化参数确定适当的值 1.WORKAREA_SIZE_POLICY:这个参数可以设置为 MANUAL 或者 AUTO 。如果是 MANUAL ,会使用排序区和散列区大小参数来控制分配的内存量。如果是 AUTO ,分配的内存量会在PGA内存中自动的变化。 默认值为AUTO,这也是推荐的设置。 2.PGA_AGGREGATE_TARGET:这个参数会控制在数据库实例上为所有工作区(即所有的排序区和散列区)分配的内存总量。在不同的数据库版本中,这个参数的默认值有所不同,还可以用多种工具来进行设置。 一般来说,如果使用自动PGA内存管理,就应该明确的设置这个参数 如何自动分配内存: 文档中没有说明采用自动模式时分配内存的算法,而且在不同版本中这个算法还可能(而且将会)不断的改变。所以只要技术以A开头(表示自动,Automatic),那就代表你会丧失一定的控制权,而由底层 算法自行确定做什么以及如何进行控制。 可以根据一些例子来分析: 1.PGA_AGGREGATE_TARGET是一个上限目标,而不是启动数据库时预分配的内存大小。你可以把 PGA_AGGREGATE_TARGET 设置为一个超大的值(远远大于服务器上实际可用的物理内存量),你会看到, 在数据库启动时并没有真的分配很大内存给PGA 2.某个具体的会话所能使用的PGA内存量也依赖与 PGA_AGGREGATE_TARGET 的设置。相关算法所能确定的一个进程可用内存的最大值,这会随数据库版本而变化。通常情况下,一个进程分配到的PGA可用内存 量是根据可用内存总量和正在争用内存的进程数来确定的 3.随着服务器上工作负载的增加(越来越多的并发查询和并发用户),分配给各个工作区的PGA内存量会减少,数据库会努力保证所有PGA分配的总和不超过 PGA_AGGREGATE_TARGET 设置的阈值。 使用 PGA_AGGREGATE_TARGET 控制内存分配: 之前我曾说过,“理论上”可以使用 PGA_AGGREGATE_TARGET 来控制实例使用的PGA内存的总量。不过,从上一个例子中可以看到,这并不是一个硬性限制,数据库实例会尽力尝试将PGA的内存总用量 保持在 PGA_AGGREGATE_TARGET 限制以内,但是如果实在无法限制住,它就会被迫超量使用来保证数据库的运行(数据库实例无法预估一个语句所要占的内存,如果超过了也要保证运行,除非超过系统 内存总量,会抛出错误)。 说它是“理论上”的限制还有另外一个原因:尽管工作区在PGA内存中所占的比重很大,但PGA内存中并非只有工作区。PGA内存分配涉及到很多方面,其中只要工作区在数据库实例的控制之下。 如何选择手动和自动内存管理: 默认情况下,倾向于自动PGA内存管理。 PGA和UGA小结: PGA是进程专用的内存区,这是Oracle专用或共享服务器需要的一组不依赖于会话的变量。PGA是一个内存“堆”,其中还可以分配其他结构。UGA也是一个内存堆,其中定义了属于会话的结构。如果使用专用服务器连接 数据库,UGA会从PGA中分配,如果使用共享服务器连接,UGA则从SGA中分配。这表明,使用共享服务器时,必须适当的设置SGA中大池的大小,以便有足够的内存空间来应对每一个数据库并发用户。所以,相比于使用 专用服务器连接方式,如果使用类似配置的共享服务器连接方式,需要设置更大的SGA。 系统全局区: 每个Oracle数据库实例都有一个很大的内存结构,称为系统全局区(SGA)。这是一个大型的共享内存结构,每个Oracle进程都会访问它。SGA的大小不一,在小型测试系统上可能只有几十MB,在中大型系统上可能呢有 几个GB,对于巨型系统,则可能多大几百GB。 SGA内分为多个不同的池。 1.java池:java池是为数据库中运行的java虚拟机(JVM)所分配的一段固定大小的内存。在 Oracle Database 10g 及更高版本中,java池可以在数据库启动和运行时动态调整大小。 2.大池:在使用共享服务器连接时存放会话内存(UGA)、在使用并行执行功能时作为消息缓冲区、在RMAN备份时作为磁盘O/I缓冲区。大池可以动态调整 3.共享池:共享池包含共享游标、存储过程、状态对象、字典缓存和诸如此类的大量其他数据。在 Oracle Database 9i 及更高版本中,共享池可以动态调整大小 4.流池:这是一个专门针对数据传输/共享工具,(例如Oracle GoldenGate和Oracle Streams)的内存池。它是 在 Oracle Database 10g 中新增的,可以动态调整大小。如果未配置流池,但是使用流功能, Oracle会使用共享池中最多10%的空间作为流内存 5.空池:这个池其实没有名字。它是缓冲区(用来缓存数据库块)、重做日志缓冲区和“固定SGA”区专用的内存 对SGA整体大小影响最大的参数: 1.JAVA_POOL_SIZE:控制java池的大小 2.SHARED_POOL_SIZE:在某种程度上控制共享池的大小 3.LARGE_POOL_SIZE:控制大池的大小 4.STREAMS_POOL_SIZE:控制流池的大小 5.DB_*_CACHE_SIZE:共有8个 CACHE_SIZE 参数,控制各个可用的缓冲区缓存的大小 6.LOG_BYFFER:在某种程度上控制重做日志缓冲区的大小 7.SGA_TARGET:在 Oracle Database 10g 及更高版本中用于SGA自动内存管理,可用动态调整 8.SGA_MAX_SIZE:用于控制SGA大小 9.MEMORY_TARGET:在 Oracle Database 11g 及更高版本中用于自动内存管理(包括PGA和SGA的自动内存管理) 10.MEMORY_MAX_TARGET:在 Oracle Database 11g 及更高版本中对于采用自动内存管理的数据库,使用PGA和SGA的内存总用量尽可能达到(并且不超出)MEMORY_MAX_TARGET所设定的值,这实际上只是一个目标值。 如果用户数超过了某个水平,或者会话分配了超大且不能调整的内存,PGA还是可能会超出这个最优值的。 固定SGA区: 固定 SGA 区是SGA的一个组件,其大小因平台和版本而异。安装时,固定SGA区会“编译到” Oracle可执行文件本身当中(所以说它是“固定”的)。在固定SGA区中,有一组指向其他组件的变量,还有一些包含了各个 参数值的变量,我们无法控制固定SGA区的大小,不过通常情况下它很小。可以把它理解为SGA的“启动”区,Oracle在内部要使用这个区来找到SGA内的其他一些东西 重做缓冲区: 数据需要写到在线重做日志中时,在它们被写至磁盘之前,需要在重做缓冲区中临时缓存这些数据,由于内存到内存之间的传输比内存到磁盘的传输要快很多,因此使用重做日志缓冲区可以加快数据库的操作。 数据在重做缓冲区里停留的时间不会太长。实际上,LGWR 进程会在以下任何一种情况发生时把缓冲区数据刷新输出到磁盘: 1.每3秒一次 2.发生提交(commit)或回滚(rollback)请求时 3.要求LGWR切换日志文件时 4.重做缓冲区用满1/3,或者缓存重做日志数据达到1MB时 块缓冲区缓存: 很大的组件:块缓冲区缓存。oracle将数据块写至磁盘之前,或从磁盘上读取数据块之后,就会把这些数据块块存储在块缓冲区缓存中。对我们来说,这是SGA中一个很重要的区域。如果块缓冲区缓存太小, 我们的的查询就会永远也执行不玩,如果太大,又会抢占其他进程的资源(例如,因为没有为专用服务器连接留下足够的空间来创建其PGA,导致无法连接使用数据库) 在Oracle数据库较早版本中,只有一个块缓冲区缓存,从任何段上读来的块都放在这个区中。从Oracle 8.0 开始,可以把各个段上的已缓存块放在SGA中的3个位置上 1.默认池:所有来自各个段中的块一般都在这个池中缓存,这就是上面提到 的较早版本那个唯一的块缓冲区缓存 2.保留池:当一些被频繁访问的段放在默认池中后,它们会因为其他段对块缓冲区缓存的需求而老化退出默认池,如果你希望能将这些被频繁访问的段也尽量保留在块缓冲区缓存中,按惯例应该使用保留池 3.回收池:与保留池相反,当你很随机的访问一些大型的段时,如果这些段放在默认池(保留池)中,会导致其他常用且频繁访问块被刷出缓冲区缓存。另外缓存这样大型段也没有什么意义,因为等你想要 再次访问这个块时,它可能已经老化退出了缓存。所以要把这种段与其他段分开,这样就不会导致真正需要留在默认池和保留池中的块老化退出缓存,这种情况按惯例应该使用回收池 分成3个池的目的是让DBA能把段分成“热”区、“温”区和“不适合缓存”区。理论上讲,默认池中的对象应该足够热(被访问的足够频繁),而能否留在缓存中也完全取决与此。还有一些段比较热门,但是还算不上 很“热”的话,它们就称作“温”块,这些段可能会从缓存中被刷出去,为一些不常用的块(“不适合缓存”的块)腾出空间。为了保持这些“温”块能留在缓存中,可以采取下面的某种做法: 1.将这些段分配到保留池,力图让温块在缓冲区缓存中停留的更久 2.将“不适合缓存”段分配到回收池,并将回收池设置的很小,以便能快速的进入缓存和离开缓存(减少内存管理的开销) 无论哪种做法都会增加DBA的工作量,因为有3个缓存池,要考虑它们的大小,还要考虑将那些对象分配到这些缓存池中。另外请记住这次池之间是没有共享的。总之,这些池一般被视为一种非常精细调优级别很低 的设备,只要大部分的其他调优手段都被用过之后才应考虑对这些池进行调优 在缓冲区缓存中管理块: 为简单起见,我们这里假设只有一个默认池(其他池都是以相同的方式管理) 缓冲区缓存中的块实质上在一个区域上管理,但有2个不同的列表指向这些块 1.脏块列表,其中的块需要由数据库块写入器(DBWn)写入磁盘 2.非脏块列表 在 Oracle 8.0 及以前的版本中,非脏块列表用的是一种最近最少使用的列表。所有的块按被使用的顺序列出。在Oracle 8i 及以后的版本中,这个算法有所修改。Oracle 不再按物理顺序方式来维护块列表, 而是采用了一种叫作接触计数的算法,如果你的数据库操作命中缓存中的某个块,则会增加与之关联的计数器的值。但这不是说每次命中这个块都会增加计数,而是大约每3秒一次(如果连续命中的话)。 Oracle会有意的使块“冷却”,过段时间会让计数递减。以上算法都是为了让频繁使用的块被缓存而不常使用的块不会缓存太久。 多种块的大小: 从 Oracle Database 9i 开始,同一个数据库中可以有多种不同的块大小,你可以有一个“默认的”块大小,以及最多4种其他的块大小。每种不同的块大小都必须有其自己的缓冲区缓存。默认池、保留池和 回收池只能缓存具有默认块大小的块。为了在数据库中使用非默认的块大小,需要配置相应的缓冲区池来保存这些块。 默认池、保留池和回收池对于缓冲区缓存的精细调优来说应该以及足够了,多种块大小主要用于从一个数据库向另一个数据库传输数据。 共享池: 是Oracle缓存一些“程序”数据的地方。在解析一个查询时,解析得到的结果就缓存在那里。在完成解析整个查询任务之前,Oracle会搜索共享池,看看这个工作是否以及完成。你运行的PL/SQl代码也在共享池中 缓存,还会在这里共享。 共享池的特点是有大量的内存块,一般为4KB或更小。共享池的内存根据最近最少使用(LRU)原则来管理。在这方面,它类似于缓冲区缓存,如果你不用某个对象,它就会被共享池踢出。 共享池如果太小,会严重影响性能,甚至导致系统看上去像中止一样,如果共享池太大,也会有同样的问题(与管理一个较小的满的共享池相比,管理一个更大的满的共享池需要做的工作更多) 大池: 它被用于大块的分配(如共享服务器中的UGA),而共享池无法处理这么大的内存块。 大块内存分配是得到一块内存后加以使用,然后就到此为止,没有必要缓存这个内存,是一个回收型的内存空间,而共享池则是更像是保留缓冲区池(如果对象可能会被频繁的使用,就将其缓存起来) java池: 目的是支持在数据库中运行java,如果用java编写一个存储过程,Oracle会在处理代码时使用java池的内存。 java池有多种用法,这取决于服务器运行的模式。如果采用专用服务器模式,java池包含每个java类的共享部分,由每个会话使用。这实际上是只读部分(执行向量、方法等),每个类的共享部分大约为4-8KB。 因此,采用专用服务器模式时,java池所需的总内存相当少,可以根据要用的java类的个数来确定。 如果使用共享服务器模式的话,java池中包含以下部分: 1.每个java类的共享部分 2.UGA中用于各会话状态的部分,这是从SGA中的java池中分配的。UGA中余下的部分会照常在共享池中分配,或者如果配置了大池,就会在大池中分配 流池: 如果未配置流池,但是使用流功能,Oracle会使用共享池中最多10%的空间作为流内存 Oracle的产品用流池来缓冲队列消息,这些功能原来是使用基于磁盘的持久的队列,而且附带一些自身的开销,现在它们改用内存中的队列了。 SGA内存管理: SGA内存管理的相关配置分为2类: 1.可以自动调优的SGA参数:目前这些参数有 DB_CACHE_SIZE、SHARED_POOL_SIZE、LARGE_POOL_SIZE、JAVA_POOL_SIZE和STREAMS_POOL_SIZE 2.需要手动调整的SGA参数:这些参数包括 LOG_BUFFER、DB_NK_CACHE_SIZE、DB_KEEP_CACHE_SIZE和DB_RECYCLE_CACHE_SIZE 对于SGA内存组件中那些可以自动调优的部分,有以下3种方式对它们进行管理 1.手动SGA内存管理:即手动设置必要的池和缓存参数 2.在Oracle 10g 及更高的版本中,使用自动SGA内存管理:即设置 SGA_TARGET 参数,通过设置改参数,数据库实例便能自行设置和调整SGA中的这些组件的内存大小 3.在Oracle 11g 及更高的版本中,使用自动内存管理:即设置 MEMORY_TARGET 参数,通过设置该参数,数据库实例便能自行管理SGA和PGA的内存区域

 

 

第五章 - Oracle进程

Oracle进程: Oracle中的每个进程都要执行一个特定的任务(或一组任务),每个进程都会为自己分配内存(PGA)来完成它的任务。一个Oracle实例主要有以下3类进程:
1.服务器进程:这些进程根据客户端的请求来完成工作。之前我们已经对专用服务器和共享服务器有了一定的了解,它们就是服务器进程 2.后台进程:这些进程随数据库启动而启动,用于完成各种维护任务,如将数据块写至磁盘,维护在线重做日志、清理异常中止的进程以及维护自动工作负载存储库(AWR)等 3.从属进程:这些进程类似于后台进程,它们代表后台进程或服务器进程执行一些额外的工作 服务器进程: 服务器进程就是执行客户端会话指令的进程,它们负责接收由应用发送给数据库的SQL语句,并在数据库中执行 Oracle的2种连接方式: 1.专用服务器连接:采用专用服务器连接时,会在服务器上得到一个针对这个连接的专用进程,这时,数据库连接与服务器进程之间是一一对应的 2.共享服务器连接:采用共享服务器连接时,多个会话可以共享一个服务器进程池,其中的进程由Oracle实例生成和管理。你所连接的是一个数据库调度器,而不是特意为该连接创建的专用服务器进程 专用服务器和共享服务器进程的任务是一样的:处理你提交的所有SQL。当你向数据库提交一个查询语句时,就会有一个Oracle专用/共享服务器进程来解析这个查询,把它放到共享池中(或者最好能发现这个查询已经 在共享池中)。这个进程要自行制定查询计划(如果有必要的话),然后执行它,并尽可能的在缓冲区缓存中找到必要的数据,或者将数据从磁盘读入缓冲区缓存。 这些服务器进程是“干重活”的进程,因为这些进程在执行排序、汇总、连接等工作,总之几乎所有工作都是由这些进程来完成 专用服务器连接: 你的客户端应用链接着Oracle文件,这些库文件提供了与数据库通信所需的API。这些API指定如何向数据库提交查询,并处理返回的游标。它们知道如何把你的请求打包为网络调用,而专用服务器则知道如何解包。 这部分软件被称为Oracle Net。这是一种网络软件/协议,Oracle利用这个软件来支持客户端/服务器处理(即使在一个N层体系结构中也会“潜伏”着客户端/服务器程序)。有时候,即使从技术上讲没有涉及Oracle net ,Oracle实际也采用了同样的体系来处理。也就是说,即使客户端和服务器在同一台机器上,也会采用这种双进程(也称为双任务)体系结构。这种结构有2个好处 1.远程执行:很显然,客户端应用和数据库可以在不同的机器上运行 2.地址空间隔离:服务器进程是可以读写SGA的。如果客户端进程和服务器进程物理链接在一起,客户端进程中一个错误的指针就会轻易破坏SGA中的数据结构 共享服务器连接: 共享服务器连接要求必须使用Oracle net,即使客户端和服务器都在同一台机器上也不例外。 客户端应用(其中链接了Oracle库文件)会与一个调度器程序物理的连接在一起。对于一个实例,我们可以配置多个调度器,而一个调度器对于数百个用户的情况也很常见。调度器程序只负责从客户端应用接收入站 请求,并把它们放入SGA中的一个请求队列。共享服务器进程池中第一个可用的进程会从队列中依次获取请求,并附加相关会话的UGA。共享服务器进程将会处理这个请求,并把得到的输出放在响应队列中。调度器程序 持续监视着响应队列中的结果,并把结果传回给客户端应用。 数据库常驻连接池: 数据库常驻连接池(DRCP)是连接数据库并建立会话的一种可选方法。有些应用接口自身不支持高效连接池(如PHP),DRCP就是为这种应用接口设计的一种更为高效的连接池方法。 在使用共享服务器连接时,月购买共享服务器进程会被多个会话共享,一个会话可能会使用多个共享服务器进程。但当使用DRCP时,情况就不一样了,从池中选出的专用服务器进程会在会话的整个生命周期中为 客户端进程所用。在共享服务器连接的情况下,如果要在一个会话中执行3个SQL语句,这个3个语句很可能会由3个不同的共享服务器进程来执行。但是只有DRCP时,这3个语句都将由从池中为该会话分配的专用 服务器进程来执行。这个专用服务器进程一直由这个会话专用,直到会话将它释放回进程池。 连接和会话: 在一个连接上可以建立0个、1个或多个会话。各个会话之间是独立的。一个连接上的各个会话可以属于不同的数据库用户 连接:连接是客户端到Oracle数据库实例的一条物理路径。连接可以通过网络或者IPC机制建立。通常会在客户端进程与一个专用服务器(或一个调度器进程)之间建立连接。 会话:会话是数据库实例中存在的一个逻辑实体,你所看到的会话状态信息,代表了你的会话在实例内存中的数据结构的集合。会话是你在数据库上执行SQL、提交事务和运行存储过程的地方 专用服务器、共享服务器和数据库常驻连接池 1.什么时候使用专用服务器: 因为存在一对一的映射,所以不存在担心那些长时间运行的事务会阻塞其他事务。其他事务只需要通过自己的专用服务器进程来处理。因此,在非OLTP模式下,也就是可能有长时间运行事务的情况下, 应该只考虑使用这种模式。专用服务器也是Oracle的推荐配置,它能很好的扩展,只要服务器有足够硬件资源(CPU和内存)来应对系统所需的专用服务器进程个数,专用服务器甚至可以用于数千条并发连接 2.什么时候使用共享服务器: 共享服务器是一种共享资源,而专用服务器不是。使用共享服务器时,必须当心,不要长时间独占这个资源。共享服务器的首要原则:要确保事务的持续时间尽量短,事务可以频繁的执行,但必须在短时间 内执行完(这正是OLTP系统的特点)。如果事务持续时间很长,你会发现整个系统会慢下来,因为共享资源被少数进程独占着。 共享服务器还会造成人为死锁的情况: 你有5个共享服务器进程,并建立了100个用户会话。现在,在任何一个时间点上最多可以有5个活动用户会话。假设其中一个用户会话更新了某一行,但是没有提交。 在这时,可能又有另外5个用户会话力图锁住这一行,当然,这5个会话会被阻塞,然后持有这一行锁的用户试图提交事务,但是已经没有共享服务器进程可用。 因此,由于这些原因,共享服务器只适用于OLTP系统。这种系统的特点是事务短而且频繁。 3.共享服务器的潜在好吃: 1.减少操作系统进程/线程数 2.人为的限制并发度 3.减少系统所需的内存 4.数据库常驻连接池(DRCP) DRCP可以减少进程(我们使用进程池),安全的节省内存,同时完成避免了人为死锁。但DRCP没有共享服务器的多线程功能:一个客户端进程从池中获得一个专用服务器进程后,它将拥有该进程直到 客户端进程将它释放。DRCP对某些客户端应用最为适合,例如应用需要频繁的连接数据库,进行一些相当较小的处理,再断开连接,如此反复。 5.专用/共享服务器小结: 除非你的系统负载过重,或者需要为某个特定的功能使用共享服务器,否则专用服务器可能是最适合你的。 后台进程: 数据库实例由SGA和后台进程组成。后台进程默默的执行一些维护任务来保障数据库运行。 后台进程可用分成2类: 1.由特定任务的进程 2.能够执行各种其他任务的进程(如工具进程) 特定任务后台进程: 特定任务后台进程的数量、名称和类型都因版本而异 1.PMON:进程监视器 负责在连接出现异常中止后进行清理工作。例如,一个专用服务器“失败”或者出于某种原因被结束调,就要由PMON进程负责善后(恢复或撤销工作),并释放资源。PMON会回滚未提交的工作,释放锁,并 释放之前为失败进程分配的SGA资源 除了在连接异常中断后进行清理之外,PMON还负责监视其他的Oracle进程,并在必要的时重启这些后台进程。 2.LREG:监听注册进程 从Oracle Database 12c 开始,LREG进程负责将数据库实例和服务注册到监听器中。当一个数据库实例启动时,LREG进程会去查看那个大家熟悉的端口(1521端口),看看是否已经有监听器在上面运行了。 3.SMON:系统监视器 SMON进程用来做所有“系统级”的任务,之前说的PMON所对应的是各个进程,而SMON则是从系统级的视角出发,成为了数据库上的垃圾收集器。SMON工作包括以下几种 1.清理临时表空间:由于某种原因会话异常中止了,SMON要负责清理这些临时表空间的区段。 2.合并空闲表空间:如果你在使用字典管理的表空间,SMON会负责取得表空间中互相连续的空间区段,并把它们合并为一个更大的空闲区段。 3.针对原来不可用的文件恢复活动的事务:在实例崩溃/恢复时由于某个文件不可用,可能会跳过一些失败的事务(即无法恢复),这些失败的事务将由SMON来恢复。例如,磁盘上的文件可能不可用或者 未装载,导致部分事务失败,当文件变成可用时,SMON将会恢复这些事务 4.执行RAC中失败节点的实例恢复:在一个Oracle RAC配置中,集群中的一个数据库实例失败时,集群中的另外某个节点会打开该失败实例的重做日志文件,并恢复失败节点上的所有数据 5.清理 OBJ$:OBJ$是一个底层的数据字典表,数据库中几乎每个对象(表、索引、触发器、视图等)都在其中对应一个条目。很多情况下,有些条目表示的可能是已经删除的对象,或者表示 “not there”的对象。要由SMON进程来删除这些不需要的行 6.管理撤销:SMON会负责实施撤销段(undo)的自动上下线,以及收缩撤销段 7.回滚段离线:当使用手动的回滚(rollback)段管理时,DBA可以让一个有活动事务的回滚段离线,或将其置为不可用状态。这时候有可能还有活动事务在使用已离线的回滚段。在这种情况下,回滚 段并没有真正离线,它只是标记为“离线中”,之后SMON会在后台定期尝试将其真正的离线,直至成功为止 4.RECO:分布式数据库恢复 RECO有一个非常核心的任务:由于俩阶段提交(2PC)期间的崩溃或连接丢失等原因,有些事务可能会保持在准备状态,这个进程就是要恢复这些事务。2PC是一种分布式协议,允许一个或多个不同数据库 的修改实现原子提交。它力图在提交之前尽可能的关闭分布式失败窗口,如果在N个数据库之间采用2PC,其中一个数据库(通常是客户最初登录的那个数据库)将成为协调器。这个站点会询问其他N-1个 站点是否已准备好提交。实际上,这个站点会转向到另外这N-1个站点,让它们准备好提交。如果某个站点投票说YES,称其已经准备好提交,但是在得到协调器的指令并真正提交之前,网络失败了,这个 事务就会成为一个可疑的分布式事务。这时候RECO会试图联系协调器获得对该事务的处理结果。在此之前,事务会保持未提交的状态。 5.CKPT:检查点进程 实施检查点主要是DBWn进程的工作,CKPT仅仅是协助实际运行检查点的进程,来更新数据文件的文件头。 6.DBWn:数据库块写入器 数据库块写入器是负责将脏块写入磁盘的后台进程。DBWn会写出缓冲区缓存中的脏块,这通常是为了在缓存中腾出更多的空间(释放缓冲区来读入其他数据),或者是为了推进检查点(将在线重做日志文件 中的位置前移)。块写入器进程会把块分散的写到磁盘中的各个位置,DBWn会做大量的离散写,而日志写入器(LGWR)则不同,它会在重做日志中进行大量的顺序写,这是Oracle在有了DBWn进程之外,还要 有重做日志和LGWR的原因(顺序写比离散写快很多)。 7.LGWR:日志写入器 LGWR进程负责把SGA中重做日志缓冲区的内容刷新到输出到磁盘。如果满足一些条件之一时,就会做这个工作: 1.每过3秒 2.一个提交或回滚发起时 3.LGWR被告知进行日志文件切换时 4.重做日志缓冲区1/3满,或者已经包含1MB的缓冲数据 8.ARCn:归档进程 ARCn进程的任务是:当LGWR将一个在线重做日志文件填满时,就将其复制到另一个位置。此后这些归档的重做日志文件可以用于完成介质恢复。ARCn通常将在线重做日志文件复制到其他至少2个位置(冗余 正是不丢失数据的关键所在) 9.DIAG:诊断进程 在以前的版本中,DIAG进程专用于RAC环境。从Oracle Database 11g 开始,利用ADR(高级诊断库),它会复制监视实例的整体情况,而且会捕捉处理实例失败时所需的信息。这既适用于单个实例配置,也 适用于多实例的RAC配置 10.FBDA:闪回数据归档进程 这个进程是 Oracle Database 11g Release 1 新增的,这是新增闪回数据归档功能的重要组成部分。闪回数据归档功能是指能够闪回查询很久之前的数据。这种长期历史查询功能是通过维护行的历史变化 记录,即记录一个表中的每一行数据的变化来实现的。这个历史就是由后台的FBDA进程维护的。这个进程在事务提交之后就立即工作。FBDA进程会读取该事务生成的undo,并回滚事务作出的改变。然后将 回滚后的这些行(原来的值)记录在闪回数据归档中。 11.DBRM:数据库资源管理器进程 DBRM进程会去实施那些为一个数据库实例配置的资源计划。它会设置指定的资源计划,执行相关的各种操作来实施/实现这些资源计划。资源管理器允许数据库管理员对数据库实例所用的资源、应用访问 数据库所用的资源或者单个用户访问数据库所用的资源进行细粒度的控制 12.GEN0:通用任务执行进程 GEN0为数据库提供一个执行通用任务的进程。这个进程的主要目的是分担进程中某些可能造成进程阻塞的处理进程,并把它放在后台中完成。例如,如果主ASM进程需要完成某个阻塞文件的操作,而且这个 操作是可以在后台被安全的完成的,在这种情况下,ASM进程就可以请求GEN0进程来完成这个操作,并让GEN0在完成时给出通知。本质上讲,这类似于后面的从属进程。 13.其他常见的特定任务进程 根据你所使用的Oracle数据库所拥有的特性,可能还会看到其他一些特定任务进程。这里只是简单的列出这些进程,并提供其功能的简要描述 工具后台进程: 这些后台进程是可选的,可以根据你的需要来选用。 从属进程: 1.I/O从属进程 2.并行查询从属进程 I/O从属进程: I/O从属进程用于在不支持异步I/O的系统上或设备上模拟异步I/O。 并行查询从属进程: 对于select、create table、update等SQL语句,创建一个执行计划,其中包含了可以同时执行的多个执行计划,将每个执行计划的输出合并在一起构成一个更大的结果。

 

 

第六章 - 锁和闩

    开发多用户、数据库驱动的应用时,最大的难点之一是:一方面要力争取得最大限度的并发访问,与此同时还要确保每个用户能在保证一致性的前提下读取和修改数据。为此就有了锁机制。

什么是锁:
    锁用于管理对共享资源的并发访问。数据库中使用锁是为了指出对共享资源进行并发访问,与此同时还要保证数据完整性和一致性。
    有多少种数据库,就可能有多少种实现锁的方法。如Sybase、SQL server 和 Informix,这3个数据库都为并发控制提供了锁机制,但它们的实现方式却有本质上的区别吗。
    在Oracle中你会了解到下面几点:
        1.事务是数据库的核心,它们是“好东西”
        2.应该延迟到适当的时刻才提交。不要太快的提交,以避免对系统带来压力。这是因为,即使事务很长或很大,也一般不会对系统造成压力。相应的原则是:在必要时才提交,不要提前。事务的大小
            只应该根据业务逻辑来定
        3.只要需要,就应该尽可能长时间的保持对数据所加的锁。这些锁是你能利用的工具,而不是让你退避三舍的东西。锁并不是什么稀有资源。恰恰相反,你就应该长期的保持数据上的锁,锁并不稀少,
            而且它们可以防止其他会话修改信息
        4.在Oracle中,行级锁没有相关的开销,一点都没有。不论你是有1个行级锁,还是1000000个行级锁,专用于锁定这个信息的资源数都是一样的。
        5.不要以为锁升级会“对系统更好”(例如,使用表级锁而不是行级锁)。在Oracle中,锁升级对系统没有任何好处,也不会节省任何资源。
        6.可以同时得到并发性和一致性。你可以每次都快速而准确的得到它们。数据读取器不会被数据写入器阻塞。
    
    锁的问题:
        1.丢失更新:
            以下情况按顺序执行:
            1.会话1中的一个事务获取(查询)了一行数据,放入本地内存,并显示给一个最终用户,User1
            2.会话2中的另一个事务也获取了这一行,并将数据显示给另一个最终用户,User2
            3.User1 使用应用修改了这一行,并让应用更新数据库并提交,会话1中的事务现在已经完成
            4.User2 也修改了这一行,并让应用更新数据库并提交,会话2中的事务已经完成(在步骤3之前完成)
        
            2种方式解决:
            悲观锁:
                这种模式在用户修改数值之前就已经开始生效了。例如,用户打算对他选择的且在屏幕上可见的某个特定行执行更新(比如通过点击某个按钮),改行就会被加上一个行锁。这个行锁会一直持续到应用
                程序在数据库中执行用户的修改 并提交的时候
                
                悲观锁仅适用于有状态或有连接环境。也就是说,你的应用于数据库之间有一个持续的连接,而且只有你一个人使用这条连接(至少是在你的事务的生命周期内)。这种有状态的连接代价太大。
                根据屏幕上选择的数据,应用将提供绑定变量的值,然后重新从数据库查询这一行,同时锁住这一行,不允许其他会话去更新它:悲观锁因此得名。我们在试图更新之前就把行锁住,因为我们很
                悲观,对于这一行能不能保持住未改变的状态很是怀疑

            乐观锁:
                把所有锁定的动作都延迟到即将执行更新之前才进行。换句话说,我们会修改屏幕上的信息而不需要先锁定它。我们很乐观,认为数据不会被其他用户修改。
                实现乐观并发控制的方法有很多种:
                    1.使用一种特殊的列,这个列由一个数据库触发器或应用程序代码维护,用以告诉我们行记录的“版本”
                    2.使用一个总和校验或散列,这是使用原来的数据计算得出
                
                使用版本列的乐观锁:
                    这种方法很容易实现,如果你想保护数据库表不出现丢失更新的问题,就在对应的每个表上增加一列。这一列一般是NUMBER或 DATE / TIMESTAMP 类型的列,通常通过表上的一个行触发器来维护。每次
                    修改行时,这个触发器要负责递增NUMBER列中的值,或者更新 DATE / TIMESTAMP 列。(避免使用触发器,推荐使用 DATE / TIMESTAMP)
                    
                    如果应用要实现乐观并发控制,只需要保存这个附加列的值,而不需要保存其他列的所有“前”映象。应用只需要在发起更新请求那一刻,去验证数据库中这一列的值与最初读出数据时的值是否匹配。
                    如果两个值相等,就说明这一行未被更新过。
                    需要一种方法来维护这个值,我们有2种方式:可以由应用维护这一列,更新记录时将这一列的值设置为当时的时间戳;也可以由触发器/存储过程来维护。让应用维护会比使用触发器维护的效率更高,
                    因为触发器会在修改操作之外增加额外的开销。
                    不能总是依赖各个应用来维护这个字段,原因是多方面的。首先,这样会增加应用程序的代码。此外,之后的每个应用也必须遵循这些规则。所以在这种情况下,我的建议是把整个更新逻辑封装到
                    一个存储过程中,而不要让应用直接更新表。

                使用总和校验的乐观锁:
                    这与前面的版本列的方法很类似,不过它是基于数据本身来计算得出一个“虚拟列”版本列。
                    我们可以采用相同的方法使用这些散列值或总和校验。只需要简单的将数据库中读出数据时得到的散列值或总和校验值跟修改数据前得到的散列值或总和校验值进行比较。
                    Oracle提供的内置函数:
                        1.OWA_OPT_LOCK.CHECKSUM
                        2.DBMS_OBFUSCATION_TOOLKIT.MD5
                        3.DBMS_CRYPTO.HASH
                        4.DBMS_SQLHASH.GETHASH
                        5.STANDARD_HASH
                        6.ORA_HASH
                    
                    计算散列或总和校验是一种CPU密集型操作(相当占用CPU),其计算代价很昂贵。如果系统上CPU是稀缺资源,在这种系统上就必须充分考虑到这一点。
            
        乐观锁还是悲观锁:
            悲观锁在Oracle中工作的很好(但是在其他数据库中可能不是这样),而且与乐观锁相比,悲观锁有很多优点。不过,它需要一个到数据库的有状态连接,如客户端/服务器连接,因为无法跨连接持有锁。
            正是因为这一点,在当前的许多情况下,使用悲观锁是不现实的。在过去,客户端/服务器应用上可能只有数十个或上百个用户,悲观锁是我们的不二选择。如今,对大多数应用来说,都建议使用乐观锁。
            
            在乐观并发控制的可以方法中,我更倾向使用版本列的方法,增加一个时间戳列。

        阻塞:
            如果一个会话持有某个资源的锁,而另一个会话在请求这个资源,就会出现阻塞。这样一来,请求的会话会被阻塞,他会“挂起”,直至持有锁的会话释放锁定的资源。
            数据库有5个常用的DML语句可能会引起阻塞,它们是:insert、update、delete、merge和select for update。对于一个阻塞的select for update,解决方案很简单:只需要增加nowait子句(阻塞会报错)

            1.阻塞的 insert
                insert 阻塞的最常见的情况:你有一个带主键的表,或者表上有唯一性约束,但有2个会话试图用同样的值插入一行,如果是这样,其中的一个会话就会阻塞,直到另一个会话提交或回滚为止。
                发生insert 阻塞通常是因为应用允许最终用户自己生成主键/唯一列值。为避免这种情况,最容易的做法是使用一个序列或sys_GUID()内置函数来生成主键/唯一列值。序列/sys_GUID()是被设计用于在多
                用户环境中,以高并发的方式生成唯一键值。如果这2个都没办法用,并且必须允许最终用户生成可能重复的键,那你可以使用手工锁来避免这个问题,这里的手工锁通过内置的DBMS_LOCK 包来实现

                使用一个预分配的空间来记录表中主键的值,每个主键一一对应预分配空间值,如果需要insert一个值,则在这个空间使用排他锁来锁定,那么其他会话如果需要insert一个值,则先在这个空间里获取
                锁,如果获取不到则抛出异常,这样就不会出现阻塞。
            
            2.阻塞的update、delete、merge
                这种情况运用上面提到的悲观锁或者乐观锁可以解决。
            
        死锁:
            如果有2个会话,每个会话都持有另一个会话想要的资源,此时就会出现死锁。
            出现死锁的头号原因是外键未加索引(第二号原因是表上的位图索引遭到并发更新),在以下情况下,Oracle在修改父表后会对子表加一个全表锁
                1.如果更新了父表的主键,由于外键上没有索引,索引子表会被锁住
                2.如果删除了父表中的一行,整个子表也会被锁住(由于外键上没有索引)
            
            未加索引的外键还会出现以下情况:
                1.当你使用了 ON DELETE CASCADE ,而且没有对子表加索引。如果从父表中删除多行,父表中没删除一行就要扫描一次子表
                2.当你从父表查询子表。你会发现如果没有索引的话会使查询速度变慢
            
            当满足以下条件时可以不加外键索引
                1.不会从父表删除行
                2.不会去更新父表的唯一键/主键
                3.不会从父表连接到子表
            
        锁升级:
            出现锁升级时,系统会降低锁的粒度。
            Oracle会尽可能的尝试使用低级别的锁,如有必要,再把这个锁转换为一个所限更多的级别。如果用FOR UPDATE 子句从表中选择一行,就会创建2个锁。一个锁放在所选行上。另一个锁是ROW SHARE TABLE锁,
            放在表本身上。
        
    锁的类型:
        在Oracle中主要有3类锁:
            1.DML锁:DML代表数据操纵语言。一般是指select、insert、update、merge和delete语句。DML锁机制允许并发执行数据修改。
            2.DDL锁:DDL代表数据定义语句,如CREATE和ALTER、语句等。DDL锁可以保护对象结构定于
            3.内部锁和闩:Oracle使用这些锁来保护其内部数据结构。例如,Oracle解析一个查询并生成优化的查询计划时,它会把库缓存闩上,将查询计划放在那里,以供其他会话使用。闩是Oracle采用的一种轻量级
            的低级串行化设备,功能上类似于锁。
        
        DML锁:
            DML锁用于确保一次只有一个人能修改某一行,而且这时别人不能删除这个表。
            
            TX锁:
                事务发起第一个修改时会得到TX锁(事务锁)。事务的发起时自动的。TX锁会被一直持有,直至事务执行提交(commit)或回滚(rollback)。Oracle并没有一个传统的锁管理器以及锁管理器为系统
                中锁定的每一行维护的一个长长的列表。Oracle的锁是做为数据的一个属性被保存的,所以锁的开销并不大。

                如果数据库有一个传统的基于内存的锁分离器,在这样的数据库中,对一行锁定的过程一般如下:
                    1.找到想锁定的那一行的地址
                    2.在锁管理器中排队(作为一种常见的内存中的结构,锁管理器必须是串行化的)
                    3.锁定列表
                    4.搜索列表,查看别人是否已经锁定这一行
                    5.在列表中创建一个新的条目,表明你已经锁定了这一行
                    6.对列表解锁
                修改之后:
                    1.再次排队
                    2.锁定列表
                    3.在这个列表中搜索,并释放你的所有锁
                    4.对列表解锁
                
                在Oracle中:
                    1.找到想锁定的那一行地址
                    2.到达那一行
                    3.就地锁住这一行,就是在行的位置上,而非某个大列表。
                
                仅此而已,由于锁是数据的一个属性,所以Oracle不需要传统的锁管理器。事务只是找到数据,如果数据还没被锁定,则对其锁定。
                在Oracle中对数据进行锁定时,会查看对于的行的是否被锁定,就是查看数据行的一个事务ID,每一个行数据都有一个事务ID,由撤销段号、槽和序列号组成。如果此事务ID是在活动的则此行数据被
                锁定,反之则更新事务ID,把自己的事务ID更新到数据行中。
            
            TM锁:
                TM锁用于确保在修改表的内容时,表的结构不会改变。例如,如果你已经更新了一个表中的行,那同时也会得到这个表上的TM锁。如果有其他用户试图修改此表结构,则会抛出错误
            
                尽管每个事务只能得到一个TX锁,但是TM锁则不同,修改了多少个对象,就能得到多少个TM锁。
        DDL锁:
            在DDL操作中会自动为对象加DDL锁,从而保护这些对象不会被其他会话修改。例如,如果我执行一个DDL操作ALTER TABLE T,通常表T上就会加上一个排他DDL锁,以防止其他会话得到这个表的DDL锁和TM锁

            在DDL语句执行期间会一直持有DDL锁,一旦操作执行完毕就立即释放DDL锁。
            每条CREATE、ALTER等语句实际上都如下执行(伪代码)
                BEGIN
                    COMMIT;  -- 会先提交
                    DDL-STATEMENT
                    COMMIT;
                EXCEPTION
                    when others then rollback;
                END;
            
            有3种类型的DDL锁:
                1.排他DDL锁:这会防止其他会话得到它们自己的DDL锁或TM锁。这说明,在DDL操作期间可以查询一个表,但是无法以任何方式修改这个表
                2.共享DDL锁:这些锁会保护所引用的对象的结构,使之不会被其他会话修改,但是允许修改数据
                3.可中断解析锁:这些锁允许一个对象(如共享池中缓存的一个查询计划)向其他对象注册其依赖性。如果在被依赖的对象上执行DDL,Oracle会查看已经注册的依赖对象列表,并使这些依赖对象无效。
                                例如,有一个查询SQL的查询计划已经解析完放到了内存中,但是你又重新编译了,这时可中断解析锁就会把这个缓存里的查询计划无效化
            
    闩:
        闩是轻量级的串行设备,用于协调对共享数据结构、对象和文件的多用户访问
        闩就是一种为保持极短时间而设计的锁(例如,修改一个内存中数据结构所需的时间)。闩用于保护某些内存结构,如数据库块缓存或共享池中的库缓存。
        由于许多请求者可能会同时等待一个闩,你会看到一些进程等待的时间比其他进程要长一些。闩的分配相当随机,这要看运气好坏,闩释放后,紧接着不论哪个会话请求闩都会得到它。等待闩的会话不会排队,
        只是一大堆会话在不断的重试。

        闩自旋:
            闩是一种锁,锁是串行化设备,而串行化设备会妨碍可扩展性。所以尽量减少所需使用闩的数量
            等待闩可能是一个代价很高的操作。如果闩不是立即可以用的,我们就得等待(大多数情况下就是如此)。在一台多CPU机器上,我们的会话就会自旋,也就是说,在循环中反复的尝试得到闩。出现自旋的原因
            是,上下文切换的开销很大(上下文切换时指被“踢出”CPU,然后尝试又必须调度会CPU)。所以,如果我们不能立即得到闩,我们就会继续呆在CPU上,并立即再次尝试,而不是先休眠,放弃CPU,等到必须
            调度回CPU时才再次尝试。
    
    互斥锁:
        是比闩更轻量级的串行设备。功能比闩要少。

 

 

第七章 - 并发和多版本控制

开发基于数据库的多用户应用时,最大的难题之一是:一方面要力争最大的并发访问,另一方面还要确保每个用户能以一致的方式读取和修改数据。

什么是并发控制:
    并发控制是数据库提供的一系列功能,它允许多人同时访问和修改数据。锁是Oracle管理共享数据库资源的并发访问并防止并发数据库事务之间“相互干渉”的核心之一。
    
    Oracle对并发的支持不只是高效的锁定,它还实现了一种多版本控制体系,这种体系结构提供了一种受控但高度并发的数据访问。多版本控制是指,Oracle能同时物化多个版本的数据,这也是Oracle提供数据读一致性的
    基础(读一致性是指相对于某个时间点Oracle会返回一致的结果)
    
    默认情况下,Oracle的读一致性多版本适用于语句级,我们也可以将其调整为事务级。数据库中的事务的基本作用是将数据库从一种一致状态转变为另一种一致的状态。

事务隔离级别:
    ANSI/ISO SQL 标准定义了4种事务的隔离级别,对于相同的事务,采取不同的隔离级别分别有不同的结果。这些级别是根据3种“现象”定义的,以下就是隔离级别可能允许或不允许的3种现象
        1.脏读:你能读取未提交的数据,也就是脏数据。
        2.不可重复读:如果你在T1时间读取某一行,在T2时间重新读取这一行时,这一行可能已经有所修改,也行它已经消失,也可能被更新了,此时等到的结果与之前不一样
        3.幻读:如果你在T1时间执行一个查询,而在T2时间再执行这个查询,,此时可能数据库中新增了一些行而影响你的结果。与不可重复读的区别:在幻读中,已经读取的数据没发生变化,T2比T1有更多的数据

    事务的隔离级别:
        隔离级别            脏读            不可重复读            幻读
        READ UNCOMMITTED    允许            允许                允许
        READ COMMITED        --                允许                允许
        REPEATABLE READ        --                --                    允许
        SERIALIZABLE        --                --                    --
    
    Oracle明确的支持标准中定义的READ COMMITED(读已提交)和SERIALIZABLE(可串行化)隔离级别。在SQL的标准的定义中,READ COMMITED不能提供读一致性,而READ UNCOMMITTED(读未提交)级别
    用来实现非阻塞读。不过在Oracle中,READ COMMITED则有得到读一致查询所需的所有属性。

    READ UNCOMMITTED:
        READ UNCOMMITTED隔离级别允许脏读,Oracle没有使用脏读这样的机制,它甚至不允许脏读。脏读不是一个特性,而是一个缺点。Oracle根本不需要脏读。
    
    READ COMMITED:
        READ COMMITED隔离级别是指事务只能读取数据块中已经提交的数据。READ COMMITED可能是最常用的隔离级别了,这也是Oracle数据块的默认模式,很少看到使用其他的隔离级别
        存在脏读的现象:
            如果在一条语句中查询了多行,除了Oracle外(使用了多版本和读一致查询),在几乎所有其他的数据库中,READ COMMITED隔离都可能“退化”得像脏读一样。
            一个会话在求和一个表中的某个字段时,另外的一个会话对这个表进行修改,如果第一个会话已经读取了前10行,第二个会话把第一行的数据-100并把它加到第100行中,则第一个会话中的求和出来
            的值是错误的,并且还被第二个会话阻塞、
    
    REPEATABLE READ:
        REPEATABLE READ的目标是提供一个这样的隔离级别:他不仅能给出一致的正确答案,还能避免丢失更新。
        1.得到一致的答案:
            如果隔离级别是REPEATABLE READ,那么同一个查询相对于某个时间点返回的结果是一致的。大多数数据(不包括Oracle)都通过使用低级的共享读锁来实现可重复读,共享读锁会防止其他会话修改我们
            已经读取的数据,但是这种机制会降低并发性(甚至会造成死锁)。Oracle则采用更具并发性的多版本模型来提供一致性读。
            
        2.丢失更新:另一个可移植性问题
            在采用共享读锁的数据库中,REPEATABLE READ的一个常见用途是防止丢失更新。
            当你把应用移植到一个没有使用共享读锁作为底层并发控制机制的数据时,就会发现实际情况和你预想的不一样
    
    SERIALIZABLE:
        一般认为这是最搜限的隔离级别,但是它也提供了最高程度的隔离性。SERIALIZABLE事务在执行时,数据库提供给它的环境就好像是一个当用户环境:貌似没有任何别的用户在修改数据库中的数据。
        Oracle实现SERIALIZABLE事务的方式很简单:将原本在语句级的读一致性扩展到事务级

        Oracle采用一种乐观的方法来实现串行化,它认为你的事务想要更新的数据不会被其他事务所更新,而且把宝压在这上面,一般情况下(尤其是短小事务的OLTP系统中)确实是这样的,所以说通常这个宝
        是押对的。在其他数据库系统中这个隔离级别通常会降低并发性,但是在Oracle中,倘若你的事务在执行期间没有别人更新你的数据,那么我们就能提供与非SERIALIZABLE事务同等程度的并发性。另一方面,
        这也是有缺点的,如果宝押错了,你就会得到一个ORA-08177错误。
        所以你希望高效的使用SERIALIZABLE隔离级别,请确认你的系统符号以下3个条件:
            1.一般没有其他人修改相同的数据
            2.需要事务的一致性
            3.事务都很短(这有助于保证第一点)
    
    READ ONLY:
        READ ONLY事务与SERIALIZABLE很相似,唯一的区别是READ ONLY事务不允许修改,因此不会遇到ORA-08177错误。READ ONLY事务的目的是相对于某个时间点,报告的内容应该是一致的。
        
        Oracle的READ ONLY隔离级别的实现方式与单条语句类型,他也使用了同样的多版本控制机制。Oracle会根据需要从undo段重新创建数据,并提供版本开始数据的原样。如果有人修改了你将要修改的数据,
        就会可能发生这种情况。所有数据的修改都会记录在undo段中,但是undo段以一种循环的方式使用,这与重做日志非常相似。报告允许的时间越长,重建数据所需的undo信息就越有可能被覆盖掉。当你需要
        的那部分undo段已经被另外某个事务占用时,就会得到ORA-01555错误,我们的事务只能从头再来。唯一的解决方案就是为系统适当的确定undo段的大小。
    
多版本读一致的含义:
    多版本控制是一个好东西,它不仅能提供一致(正确)的答案,还有高度的并发性。

写一致性:
    到目前为止,我们已经了解到读一致性:Oracle可以使用undo信息来提供非阻塞的查询和一致(正确)的读。但是出现下面这种情况,Oracle会如何处理呢:
    在一个会话允许这个语句,update t set x = 2 where y = 5 但是在第二个会话在第一个会话途中修改了y的值变为y = 6。
    Oracle会这样处理:
        在第一个会话开始时,y = 5,但是在进行修改时却发现y的值变为6了,y的值更新了,Oracle因为不能修改块的老版本,这样会造成不一致性(违反数据库中的事务的基本作用是将数据库从一种一致状态转变
        为另一种一致的状态),所以会出现开始写修改(重启动)。

    隔离级别                实现                写阻塞读                读阻塞写                读易发生死锁                不正确的查询结果                丢失更新                锁升级或限制
    READ UNCOMMITTED        非Oracle            否                        否                        否                            是                    是                        是
    READ COMMITED           非Oracle            是                        否                        否                            是                    是                        是
    READ COMMITED           Oracle              否                        否                        否                            否                    否                        否
    REPEATABLE READ         非Oracle            是                        是                        是                            否                    否                        是
    SERIALIZABLE            非Oracle            是                        是                        是                            否                    否                        否
    SERIALIZABLE            Oracle              否                        否                        否                            否                    否                        否

 

 

 

第八章  - 事务

事务是数据库区别于文件系统的特性之一。事务会把数据库从一种一致性状态转变为另一种一致状态。
Oracle中的事务体现了ACID特征
    1.原子性:事务中的所有动作要么都发生,要么都不发生
    2.一致性:事务将数据库从一种一致状态转变为下一种一致状态
    3.隔离性:一个事务的影响在该事务提交前对其他事务是不可见的
    4.持久性:事务一旦提交,其结果就是永久性的

事务控制语句:
    在Oracle中不需要用一个BEGIN TRANSACTION 语句来启动事务,事务会在修改数据的第一条语句处隐式开始(也就是得到TX锁的第一条语句)。

    一定要显示的使用COMMIT 或ROLLBACK来终止事务,否则你使用的工具/环境就会选一个来替你结束事务。

    Oracle中的事务是原子性的,仅有2种情况会发生:构成事务的每条语句都会提交(所做的修改持久化),或者所有语句都会回滚。如果一条语句失败,并不会导致先前已经执行的语句自动回滚,已经完成的工作必须由你
    来决定提交还是回滚。
        1.COMMIT:会结束你的事务,并使得已做的所有修改持久性的保存在数据库中。
        2.ROLLBACK:回滚会结束事务,并且撤销这个事务所做的修改。撤销动作需要读取回滚段/undo段中的信息,并把数据恢复到事务开始之前的状态
        3.SAVEPOINT:你可以在事务中通过SAVEPOINT语句来创建一个标记点,一个事务可以有多个SAVEPOINT
        4.ROLLBACK TO <SAVEPOINT>:这个语句与SAVEPOINT命名一起使用,它会把事务回滚到指定的标记点,但是它不回滚在此标记点之前的工作。
        5.SET TRANSACTION:这条语句允许你设置不同的事务属性。

原子性:
    语句级原子性:
        例如:insert into t values (1);
        如果该语句与一个约束发生冲突,那么这一行显然不会被插入到表中。如果在表中定义一个insert和delete会触发一个触发器。它将调整T2表中的CNT列。
        我们执行2条insert语句,第一条会成功插入,第二条违反约束条件则不会插入,触发器会执行2次,但是T2表的CNT列会只增加一次,这是因为Oracle会保证insert(即导致触发器触发的插入语句)的原子性,
        所以insert into T 的任何连带效果都被认为是该语句的一部分而被回滚
    
    过程级原子性:
        Oracle把PL/SQL匿名块也当做是语句。如果是在存储过程中添加ROLLBACK的话,则会回滚到最近的COMMIT那,之前的语句并不会被回滚。
    
    DDL与原子性:
        Oracle中有一类语句与提供语句级原子性。数据定义语言语句采用了一种独特的实现方式。
            1.这些语句在开始时会先执行一个COMMIT,结束当前已有的所有事务
            2.完成DDL操作,如create table
            3.如果DDL操作成功则提交,否则回滚DDL操作
    
持久性:
    通常情况下,一个事务的提交时,它的改变就是永久性的。即使数据库在提交完成之后随即崩溃,你也完成可以改变确实已经永久存储在数据库中。不过,这2种情况例外:
        1.使用COMMIT语句新增的write扩展从句
        2.在非分布式(只访问一个数据库,而不是数据库链接)PL/SQL代码块中执行COMMIT
    
    COMMIT的write扩展:
        在Oracle Database 10g Release 2 及以上版本中,你可以为COMMIT语句增加一个write子句,write有一个默认选项wait,它会等待redo写到磁盘之后才算提交结束。而另外一个选项nowait则不会等待
        redo写完就直接完成提交。

        提交所进行的动作之一就是将redo写到磁盘上去,会涉及到物理的I/O操作,而这通常很耗时,如果我们以后台执行的这种方式处理的话,就会大大缩短系统从收到提交请求到响应提交完成所需的时间
        
    非分布式PL/SQL代码块中的COMMIT
        从某种意义上讲所有PL/SQL块都类似于批处理程序,在PL/SQL过程完成执行完之前,最终用户无法知道过程的结果,异步提交只能用于非分布式的PL/SQL代码块。
        PL/SQL采用异步提交机制可以使其内部的COMMIT语句不必等待物理I/O完成就可以继续处理后面的语句。但是,这并不是说完成PL/SQL把控制权返还给客户端时,其所作的修改是非持久性的---PL/SQL块
        在最终将控制权返回客户端应用之前,必须要等待块内所生成的redo写至磁盘,最后的这次是个同步处理,也是唯一的一次等待。
        
分布式事务:
    Oracle能够透明的处理分布式事务,在一个事务的范围内,我可以更新多个不同数据库中的数据,当我提交时,要么提交所有实例中的更新,要么一个都不提交。
    Oracle中分布式事务的关键组件就是数据库链接。数据库链接是一个数据库对象,它描述了如何从你的实例登录到另一个实例。

    Oracle使用一个2PC协议来做到多个数据库之间的同步。2PC是一个分布式协议,如果一个修改影响到多个不同的数据库,那么2PC就能保证这个修改在提交时的事务原子性。如果一个2PC事务涉及到多个数据库,那么
    其中一个数据库(通常是客户端最初登录的那个数据库)会成为整个分布式事务的协调者,提交时,协调者会询问其他非协调者数据库是否已经准备好提交,然后每个协调者都会报告它的“就绪状态”(YES或NO),此时
    只要一个投票NO,整个事务就会回滚。如果所有站点都投票YES,协调者会广播一条信息,从而使所有涉及到的数据中的提交成为永久性的。

    对于分布式事务中能够做的事情,还存在一些限制:
        1不能在数据库链接上发出COMMIT,只能在发起事务的那个数据库提交
        2不能在数据库链接上完成DDL,这个是上一个问题带来的直接结果:DDL会提交。除了发起事务的数据库外,你不能在任何非协调者数据库提交,所以不能在数据库链接上完成DDL
        3不能在数据库链接上发出SAVEPOINT。

自治事务:
    自治事务允许你创建一个“事务中的事务”,它能独立于其父事务提交或回滚。主要用于记录错误日志或信息型消息。

 

 

第九章 - redo和undo

redo(重做信息,后面我们也会称之为重做日志):是Oracle在线(或归档)重做日志文件中记录的信息,当数据库发生故障时,我们可以利用这些数据来“重放”(重做)事务。
undo(回滚信息):是Oracle在undo段中记录的信息,它主要用于取消或回滚事务。

什么是redo:
    重做日志文件对Oracle数据库来说至关重要,它们是数据库的事务日志。Oracle维护着这俩类重做日志文件:在线重做日志文件和归档重做日志文件。

什么是undo:
    你对数据执行修改时,数据库会生成undo信息,以便将来需要的时候可以把数据变更回修改之前的状态。redo用于在失败时恢复事务,undo则用于取消一条语句或一组语句的作用,与redo不同,undo存储在数据库
    内部一组特殊的段中,称为undo段。

    数据库只是“逻辑的”将数据恢复到原来的样子,某些修改会被“逻辑的”取消,但是数据结构以及数据库块本身在回滚后可能(与事务或语句开始之前的数据块状态)不太一样。对于每个insert,Oracle会做一个delete。
    对于每个delete,Oracle会执行一个insert,对于每个update,Oracle则会执行一个“反update”,或者执行另一个update将数据恢复到修改前的样子。

redo和undo如何协作:
    尽管undo信息存储在undo表空间和undo段中,但它也会受到redo的保护。换句话说,数据库会把undo当成是表数据或索引数据一样处理(因为是存储在表空间的,和其他数据时一样的),对undo的修改会生成一些
    redo,这些redo将记入日志缓冲区进而写到日志文件中。

提交和回滚:
    COMMIT做什么:
        不论事务有多大,COMMIT的响应时间一般都很“稳定”,这个因为COMMIT并没有太多的工作去做。所以COMMIT的越多,耗时越久。

        这是因为在数据库执行COMMIT之前,困难的工作都已经做了。已经发生如下的操作:
            1.已经在SGA中生成了undo块
            2.已经在SGA中生成了已修改数据块
            3.已经在SGA中生成了对应前2项的redo缓存
            4.如果前3项比较大并且耗时比较长,那这三项中的某些数据可能已经被刷新输出到磁盘
            5.已经得到了所需的全部锁

        执行COMMIT时,只剩下如下的工作:
            1.为事务生成一个SCN。SCN是Oracle使用的一种简单的计时机制,用于保证事务的顺序,并支持失败恢复。此外它还用于数据库中的读一致性和检查点。
            2.LGWR将所有未写入磁盘的重做日志条目写至磁盘,并把SCN记录到在线重做日志文件中。这一步是真正的COMMIT。
            3.V$LOCK中会记录着我们的会话持有的锁,这些锁都将被释放,而排队等待这些锁的会话会被唤醒,从而可以继续完成它们的工作
            4.如果事务修改的某些块还在缓冲区缓存中,Oracle就会以一种快速的模式访问并“清理”(COMMIT清理)。块清理就是值清除存储在数据库块首部的与锁相关的信息,它实际是在清理块上的事务信息,
                这样下一个访问这个块的人就不用再这样做了。这个清理过程不会生成重做日志,这样就省去以后的大量工作。
    
    ROLLBACK做什么:
        回滚时间一定是和修改数据量成正比的。
        ROLLBACK时,会做以下工作:
            1.撤销所有修改。Oracle数据库会读取undo段,然后将数据应用到数据库以撤销我们的修改,并将相应的undo条目标记为应用。
            2.会话持有的所有锁都将被释放,如果有会话在排队等待我们持有的锁,就会被唤醒
        
    相比之下,COMMIT只是将重做日志缓冲区中剩余的数据刷新输出到磁盘,它比ROLLBACK的工作量要小很多。所有轻易不要回滚。

分析redo:
    redo管理时数据库中的一个串行点,任何Oracle实例都只有一个LGWR,最终所有事务都会汇集到LGWR,在这些事务提交时它们都会请求这个进程管理redo。LGWR要做的越多,系统就会越慢。

    能关掉重做日志的生成吗:
        不能。因为重做日志对于数据库至关重要,他不是开销,不是浪费。
        
        1.在SQL中设置NOLOGGING:
            有些SQL语句和操作支持使用NOLOGGING子句,这并不意味着这个 对象的所有操作都不生成重做日志,而是说有些特定操作生成的redo会比平常少得多。但是所有操作都会生成一部分redo——不论日志模式是什么,
            所有数据字典的操作都会记入日志,只不过使用NOLOGGING子句后,生成的redo量可能会显着的减少。
            关于NOLOGGING操作,请注意以下几点:
                1.redo还会或多或少的生成一些。这些的主要是保护数据字典,而且是不可避免的。
                2.NOLOGGING不会影响后续操作继续生成redo。如创建表时使用NOLOGGING来创建表,只是这个创建表这个操作没有生成日志,所有后续的正常操作(insert等)都会生成日志。另外如insert /*+ APPEND*/语法
                    的直接路径插入也是不产生日志的。
                3.在一个归档模式的数据库上执行NOLOGGING操作后,你必须尽快为受影响的数据库文件建立一个新的基准备份。
            
        2.在索引上设置NOLOGGING
            在段(索引或表)上设置NOLOGGING属性,从而隐式的采用NOLOGGING模式来执行某些操作。
        
        3.NOLOGGING小结:
            我们可以采用NOLOGGING模式执行以下操作:
                1.索引的创建和重做
                2.表的批量insert(通过/*+ APPEND*/提示使用直接路径insert)。表数据不生成redo,但是所有的索引修改会生成redo
                3.LOG操作(对大对象的更新不必生成日志)
                4.通过CREATE TABLE AS SELECT 创建表
                5.各种ALTER TABLE 操作,如MOVE和SPLIT
    块清除:
        指删除所修改数据库块上与“锁”有关的信息
        
        所实际上是数据的属性,它存储在块首部。这就带来一个副作用。例如,当我们修改了一个数据块,但是因为某些情况我们没有在事务结束时将其上面的锁信息清除掉(redo自动刷新输出到磁盘),那么当我们下一次
        访问这个块时,就必须清理这个块上的锁信息,换句话说,要将这些事务信息删除。这个清理动作会修改数据库,让这个块变脏,并且会生成redo,这说明一个简单的select也可能生成redo,而且可能导致下一个
        检查点操作时有大量的块需要写至磁盘。

        如果Oracle不使用延迟块清除这种机制,那么COMMIT的处理可能就会与事务本身一样长,因为它必须要重新访问每一个块,而且可能还要从磁盘将块再次读入缓冲区中(它们可能已经刷新输出到磁盘)。

    日志竞争:
        出现的原因有几个原因:
            1.redo放在一个慢速设备上。磁盘性能不佳
            2.redo与其他频繁访问的文件放在同一个设备上。
            3.日志放在有缓存的设备上。有可能操作系统会去缓存我们的日志文件。其实数据库已经在缓冲日志数据了。
            4.redo的存储采用了一种慢速技术
        
    临时表和redo/undo
        1.12c之前:
            临时表的数据块不会生成redo,因此,对临时表的操作时不可恢复的。不过临时表会生成undo,而且这个undo会记入日志(提供回滚功能)。
            对于临时表上的DML活动,我们可以总结以下结论:
                1.insert会生成很少甚至不生成undo/redo
                2.临时表的update会生成永久表update大约一半的redo(存储update之前的undo,而undo需要redo来写入日志)
                3.delete在临时表上生成的redo与永久表上生成的redo同样多
        
        2.12c之后:
            我们可以通过设置参数TEMP_UNDO_ENABLED来将临时表的undo放在临时表空间中,由于临时表空间的任何数据变更都不会产生redo,所有当这个参数为TRUE时,任何临时表上的DML都会产生很少甚至不产生。
        

分析undo:
    
    什么操作会生成最多和最少的undo
        索引(或者实际上表就是索引组织表)会显著的影响undo的生成量,因为索引时一种复杂的数据结构,对它的维护可能会生成相当多的undo信息
        一般insert只生成很少量的undo,update生成的undo量等于数据在修改前的映象大小,delete会将整个数据写至undo段
        更新有索引的列会生成好几倍的undo

    ORA-1555:snapshot too old 错误(快照过旧)
        undo是一个固定大小的undo表空间,会循环使用,新的undo会覆盖旧的undo。
        一般以下3个原因:
            1.undo段太小
            2.你的程序跨COMMIT获取数据
            3.块清除
        
        一般的解决办法:
            1.适当的设置参数UNDO_RETENTION(要大于运行时间最长的事务所需的时间)。
            2.使用手动undo管理时加大或者增加更多的undo段。但是建议采用自动undo管理
            3.减少查询的运行时间(调优)
            4.收集相关对象的统计信息
        
        延迟的块清除:
            如果一个块修改,下一个会话访问这个块时,就会其查看最后一个修改这个块的事务是否还在活动的,一旦确定该事务不再活动,就会进行块清除,这样再有别的会话访问这个块时就不必再经历同样的过程。

 

 

第十章 - 数据库表

表类型:
    1,堆组织表:这个就是普通的标准数据库表。数据在其中以堆的方式管理、增加数据时,会找到并使用段中第一个能放下此数据的自由空间。从表中删除数据时,则允许以后的insert和update重用这部分空间。
    2.索引组织表:这些表按索引结构存储。这就强制要求行本身有某种物理顺序。数据要根据主键有序的存储在IOT中
    3.索引聚簇表:聚簇是指一个或多个表组成的组,这些表中的数据物理的存储在相同的数据库块中,有相同聚簇键值的所有行会物理存储在相邻的位置。这种结构可以实现2个目标,首先,多个表可以被物理的存储
                    在一起。多个表的数据可以存储在同一个数据块中。其次,包含相同聚簇键值的所有数据会物理的存储在一起。因此数据会按聚簇键值聚簇在一起,而聚簇键是使用B*Tree索引建立的。索引聚簇表
                    的优点是当频繁访问以聚簇键连接的多个表时,能减少磁盘I/O并提高查询性能
    4.散列聚簇表:这些表类似于索引聚簇表,但不是使用B*Tree索引按聚簇键来定位数据。散列聚簇是将键散列到聚簇上,从而直接找到数据应该在哪个数据库块上。在散列聚簇中,数据就是索引(一种隐喻)。如果
                    需要频繁的通过键的相等性比较来读取数据,散列聚簇表就很适用
    5.有序散列聚簇表:这种表的类型是Oracle Database 10g 新增的,它结合了散列聚簇表和IOT的一些特点,其概念如下:如果你的行按某个键值进行散列,而与该键值相关的一系列记录会以某种有序顺序被写入表,
                        并被这种顺序进行从处理。
    6.嵌套表:嵌套表是Oracle对象关系扩展的一部分。它们实际上就是系统生成和维护的父/子关系中的子表。
    7.临时表:这些表存储的是事务期间或会话期间的“草稿”数据。临时表数据需要从当前用户的临时表空间分配临时区段。每个会话只能看到本会话分配的区段,而不会看到其他任何会话创建的任何数据。我们可以使用
                临时表来存储一些临时性数据,这样做的好处:与常规的堆表相比,临时表上redo的生成量会大幅降低。
    8.对象表:对象表是基于某种对象类型创建的。它们拥有非对象表所没有的特殊属性,如系统会为对象表的每一行生成REF(对象标识符)。对象表是堆组织表、索引组织表和临时表的一种特殊类型。Oracle会用这些
                种类的表甚至嵌套表来构建对象表。另外嵌套表也是对象表结构中的一种
    9.外部表:这些表中的数据并不存储在数据库中,而是放在数据库之外,也就是说它们被存放为普通的操作系统文件。

    不论哪种类型的表,都有以下基本信息
        1.一个表最多可以有1000列,
        2.一个表的行数理论上是无限的,不过因为存在着另外某些约束,使得它实际上无法达到“无限”。
        3.表中的列有多种排列,表就有多少个索引。
        4.即使在一个数据库中也可以有无限多个表。
    
段:
    Oracle中的段是占用磁盘上存储空间的一个对象。段有很多种类型
        1.聚簇:这种段类型能存储多个表。聚簇分为2种:B*Tree聚簇和散列聚簇。聚簇通常用于存储多个表上的相关数据,将其预连接存储到同一个数据块上。还可以用于存储一个表的相关信息。
        2.表:一个表保存一个数据库表的数据,这可能是最常用的段类型,通常与索引段联合使用
        3.表分区或子分区:这种段类型用于分区,与表段很相似。一个表分区或子分区存储了一个表中的一部分数据。一个分区表由一个或多个表分区段组成,组合分区表则由一个或多个表分区段组成
        4.索引:这种段类型可以保存索引结构
        5.索引分区:类似于表分区,这种段类型包含一个索引的一部分。分区索引由一个或多个索引分区段组成
        6.LOB分区、LOB子分区、LOB索引和LOB段:LOB索引和LOB段类型可以保存大对象的结构。对包含LOB的表分区时,LOB段也会被分区,LOB分区段正是用于此。有意思的是,并不存在LOB索引分区段类型,不论是
            是出于什么原因,Oracle将分区的LOB索引标记为一个普通的索引分区
        7.嵌套表:这是为嵌套表指定的段类型,它是父/子关系中一种特殊类型的子表
        8.回滚段和“Type2 undo”段:被撤销的数据就存储在这里,回滚段是DBA手动创建的段。Type2 undo段则是由Oracle自动创建和管理的

段管理空间:
    从Oracle Database 9i 开始,管理段空间有下面2种方法
        1.手动段空间管理(MSSM):由你设置FREELISTS、FREELISTGROUPS、PCTUSED和其他参数来控制如何分配、使用和重用段中的空间。
        2.自动段空间管理(ASSM):你只需控制与空间使用相关的一个参数PCTFREE
    
    高水位线(HWM):
        就是曾经包含过数据的最右边的块。HWM很重要,因为Oracle在全面扫描段时会扫描HWM之下的所有块,即使其他不包含任何数据。它会影响全面扫描的性能,特别是当HWM之下的大多数块都为空时。
        Truncate会把表HWM重置回“0”,还会截取表上的相关索引。(Truncate不能被回滚)

        在MSSM表空间中,每个段只有一个HWM,而在ASSM表空间中,除了HWM外,还有一个低的HWM。在MSSM中,当向前推进时,HWM之下的所有块都会被格式化并立即有效,Oracle可以安全的读取这些块。而
        对于ASSM,HWM推进时,Oracle并不会立即格式化所有块,而是在第一次真正使用这些块时才会完成格式化,以便安全的读取。第一次使用会发生在数据库向定块插入记录时。对于ASSM,数据会被插入到位于低
        HWM和HWM之间的任意块中,而在这个区域中的许多块可能没有格式化。低HWM代表了一个位置点,在这个点以下的所有块都被格式化的。
        所以,当全面扫描一个段时,必须知道被读取的块是否“安全”,或是否未格式化,为了避免对表中的每个块都进行这种“安全/不安全”检查,Oracle同时维护了一个低HWM和一个HWM。
    
    FREELIST
        使用MSSM表空间时,针对那些有自由空间的对象,Oracle会使用自由列表来维护它们的HWM之下的块

        每一个对象都至少有一个相关的FREELIST,使用块时,可能会根据需要把块放在FREELIST上或者从FREELIST上去除,需要说明一点的是,只要位于HWM以下的对象块才会出现FREELIST中。仅当FREELIST为空时才会
        使用HWM之上的块,此时Oracle会推进HWM,并把这些块增加到FREELIST中。

        一个对象可以有多个FREELIST,如果预料到会很多并发用户在一个对象上执行大量的insert或update,就可以配置多个FREELIST,这对性能提升很有好处。
    
    PCTFREE和PCTSED
        PCTFREE参数用来告诉Oracle应该在块上保留多少空间来应对将来的更新。默认情况下是10%。如果一个块上的自由空间的比例高于PCTFREE中指定的值,那么这个块就被认为是“自由的”。
        PCTUSED则是用来告诉Oracle,如果块上不自由的空间达到或小于PCTUSED参数指定的百分比时,这个块将重新变为自由的块。

        行迁移:
            指由于某一行变得太大,无法再与块中其余的行放在创建这一行的块中,这就会要求这一行离开原来的块。行迁移会影响性能。

        设置PCTFREE和PCTSED
            高PCTFREE,低PCTUSED:如果你插入大量将来要被更新的数据,而且这些更新会频繁的增加行的大小,此时就适合采用这种设置
            低PCTFREE,高PCTUSED:如果你只打算在表上进行insert或delete,或者如果你的update只会缩小行的大小,此时就适合采用这种设置
        
        LOGGING和NOLOGGING:
            通常对象采用LOGGING方式创建,这说明只要在该对象上执行能够生成redo的操作时,就都会生成redo,而NOLOGGING则按允许在该对象上执行某些操作时可以不生成redo。
        
        INITRANS和MAXTRANS:
            段中每一个块都有一个块首部。这个块首部中有一个事务表。事务表会建立一些条目来描述哪些事务将块上的哪些行/元素锁定。事务表会根据需要动态扩展,最大达到MAXTRANS个条目。
        
堆组织表:
    应用中99%的情况下使用的可能都是堆组织表。执行CREATE table 语句时,默认得到的表类型就是堆组织表。堆实际上就是一个很大的空间、磁盘或内存区,并被以一种很随机的方式管理,数据会放在合适放下它的
    地方,而不是以某种特定顺序来放置。

    全表扫描时,会按命中的顺序来获取数据,而不是以插入的顺序。

索引组织表(IOT):
    就是存储在一个索引结构中的表。IOT中的数据是按主键顺序来存储和排序的。索引就是数据,数据就是索引。但是索引是一种复杂的数据结构,需要大量的工作来管理和维护,而且随着存储行的宽度的增加,维护的需求
    也会增加。

    IOT的好处:
        1.提高缓冲区缓存访问,因为给定查询在缓存中需要的块更少
        2.减少缓冲区缓存访问,这会改善可扩展性
        3.获取数据的工作总量更少,因为获取数据更快
        4.每个查询完成的物理I/O更少,因为对于任何给定的查询,需要的块更少,而且针对地址记录表的一次物理I/O很可能可以获取所有相关地址
    
    建立IOT时,最关键的是要适当的分配数据,即哪些数据存储在索引块上,哪些数据存储在溢出段上。可以选择某几列放在索引段上,其他列放在溢出段上,索引段上有指向溢出段的指针。

索引聚簇表:
    如果一组表有一些共同的列,则将这样一组存储在相同的数据库块中。聚簇还会把相关的数据存储在同一个块上。聚簇并不是有序的存储在数据(这是IOT的工作),它是按某个键以聚簇方式存储数据,但是数据还是
    存储在堆中。

    不应该使用聚簇的情况:
        1.如果预料到聚簇中的表会有大量修改:必须知道,索引聚簇会对DML的性能产生一些负面的影响,特别是insert。
        2.如果需要对聚簇中表执行全表扫描:不仅只是对你需要的表中的数据进行全面的扫描,同时还不得不对其他(聚簇中可能存在的表)多个表中的数据进行全面扫描
        3.如果你要对表进行分区:聚簇中的表不能进行分区,聚簇也不能分区
        4.如果你认为需要频繁的Truncate和加载表:聚簇中的表不能截除。这是显然的,因为聚簇在一个块上存储了多个表,所以必须从聚簇中删除行
    
    利用聚簇表,可以物理的“预连接”数据,使用使用聚簇可以把多个表上的相关数据存储在同一个数据库块上。可以减少Oracle必须缓存的块数,从而提高缓冲区缓存的效率。

散列聚簇表:
    在概念上与前面介绍的索引聚簇表非常相似,只有一个主要区别:聚簇键索引被一个散列函数取代。表中的数据就是索引,这里没有物理索引。Oracle会取得一行键值,使用某个内部函数或者你提供的方法计算
    其散列值,然后使用这个散列值得出数据应该在磁盘上哪个位置。但是无法对表进行区间扫描(散列的特性)。

有序散列聚簇表:
    不仅有前面提到的散列聚簇的有关性质,还结合IOT的一些性质。可以按所选的字段有序的存储

临时表:
    临时表中保存的数据只对当前会话可见,所有会话都看不到其他会话的临时表中的数据,即使当前会话提交了事务,别的会话也看不到临时表中的数据。
    
    临时表会从当前登录用户的临时表空间分配存储空间。当我们创建临时表时,数据库并不会为这个表分配任何的存储空间,只有当会话向临时表中存入数据时,数据库才会给其创建一个临时段,而且这个临时段仅供
    这个会话使用。

    Oracle的临时表时静态定义的,你在数据库中只需要创建一次就行了,无需在存储过程中,让它每次运行的时候都创建一次临时表。

    临时表时基于会话的(临时表中的数据在提交之后不会被清空,但是断开连接后再连接时这部分数据就没有了),也可以是基于事务的(提交之后就没有了,基本没有开销)

    如果应用中需要临时的存储一批数据(可能对应一个会话,也可能对应一个事务),那我们就能使用临时表。我们不要使用临时表来分解查询。

 

 

第十一章 - 索引

Oracle提供了多种不同类型的索引
    1.B*Tree索引:我愿意把这种索引为“传统”索引。到目前为止,这是Oracle和大多数其他数据库中最常见的索引。B*Tree的结构类似于二叉树,我们通过键值就能快速的访问一行数据,或通过键值的某个范围内来定位
                    多行数据。通过这种索引访问数据一般只需几个I/O就能完成。这里需要提醒的是,B*Tree中的B不代表二叉,而代表平衡。以下几种类型也算是B*Tree索引
        1.索引组织表:这是一种表,但是它的存储也是B*Tree结构,
        2.B*Tree聚簇索引:这是传统B*Tree索引的一个近似变体。B*Tree是对聚簇建立的索引。在传统的B*Tree索引中,键会指向行这一级别,而B*Tree聚簇不同,一个聚簇键会指向一个块,这个块中包含与这个聚簇
                            键相关的数据
        3.降序索引:在降序索引中,数据是按“从大到小”的顺序来排序的,而不是“从小到大”(升序)
        4.反向键索引:这也是B*Tree索引,只不过键中的字节会“反转”。如果向一个索引中不断插入递增的数据,那么这些数据通过反向键索引会得到更均匀的分布。例如,如果用一个序列来生成主键,这个序列将
                        生成类似于987500、987501、987502等的值,这是个单调递增序列,倘若我们使用一个传统的B*Tree索引,这些值就可能会放在同一个右手块上,这就加剧了对这个数据块的竞争。如果我们
                        使用反向键索引,Oracle就会对205789、105789005789(顺序反转)等建立索引。Oracle将数据放入索引之前,先把待存储数据的字节反转,这样原本可能在索引中相邻放置的数据,在字节
                        反转之后就会相距很远。通过反转字节,我们就能将索引上的插入分布到多个块上
    
    2.位图索引:一个索引条目会通过一个位图同时指向多行数据。位图索引适用于高度重复的数据(高度重复指相对于表中的总行数,数据只有很少的几个不同的值),而且数据通常是只读的。
    
    3.位图联结索引:数据的逆规范化通常是通过表来实现的,而这种索引也能对数据进行逆规范化。

    4.基于函数的索引:这些索引本身就是B*Tree索引或位图索引,但是存储的是某列或某几列的一个计算结果,而不是原始的数据。
    
    5.应用域索引:应用域索引是你自己构建和存储的索引,它可能存储在Oracle中,也可能在Oracle之外。


B*Tree索引:
    B*Tree类似于二叉树,这个树最底层的块称为叶节点或叶块,其中包含各个索引键以及一个rowid(指向所索引的行)。叶节点之上的内部块称为分支块,数据的搜索会通过这种块最终抵达叶节点。有意思的是,索引叶节点
    这一层的结构实际上是一个双向链表。这样我们就可以实现区间的范围查询。

    B*Tree索引中不存在非唯一的条目,也就是说,在B*Tree索引中没有2个索引条目完成一样。在非唯一性的索引中,Oracle会把rowid作为一个额外的列(有一个长度字节)追加到键上。使得键是唯一的。例如,
    CREATE unique index i on T(X,Y,rowid)。

    B*Tree的特点之一是:所有叶块都应该在树的同一层上。这一层也称为索引的高度,所有从索引的根块到叶块的遍历都会访问同样数目的块。换句话说,,索引是高度平衡的,大多数B*Tree索引的高度都是2或者3,
    即使索引中有数百万行记录也是如此。

    索引键压缩:
        我们可以对B*Tree索引进行压缩,但它与压缩ZIP文件的方式不同,是从多列索引(即所有的定义中含有多列)中去除冗余数据。

        如果某个前缀值有N个副本,那么索引压缩之后就可以把那些重复的副本都去掉,从而达到节省空间的目的,但是作为压缩机制的一部分,每一个被移除的重复前缀会在块内有一个4字节的成本开销。所以确定
        哪几个列作为前缀是最为关键的,要选择重复度高的列作为前缀。

        压缩索引不是毫无代价的,压缩索引的结构比原来未压缩的更复杂,Oracle会花更多的时间来处理这个索引中的数据。
    
    反向键索引:
        B*Tree索引的另一个特点是能够将索引键反转。其目的是减少“右手”索引中索引叶块的竞争。

        反向键索引的缺点之一是:能用常规索引的地方不一定能用反向键索引。例如,where x > 5,数据在存储到索引之前不是按X来排序的,而是按反转X的值来排序的。

        反向键索引有助于缓解缓冲区忙等待问题,但是如果系统的性能还受其他一些因素的影响,那么反向键索引的作用也会大打折扣。

    降序索引:
        在降序索引中,列中的数据是以降序的方式存储的,而不是升序。
        在你用到一组列,其中一些列按升序排序,一些列按降序排序,传统的B*Tree就派不是用场了。
        create index i on t(X desc,y asc)
    
    什么情况下一个使用B*Tree索引
        1.如果你要访问表中非常少的一部分数据。(如果要查大部分的行,优化器可能会选择全表查询)
        2.如果你要处理表中大量的数据,但这些数据可以通过索引(而不是表)直接拿到。
    
        1.物理组织:
            如何在磁盘上物理的组织数据,对上述计算有显著影响。

            如果表上有主键的话,那么数据会自然的按主键的顺序聚簇(因为数据大致是以主键值的顺序放到表中),当然,他不一定严格安装键聚簇(要想做到这一点,是以IOT)。一般来讲,主键值彼此接近的数据,
            其物理位置也会靠在一起。例如查询一个区间SQL,这种情况下,即使访问的数据占的百分比很高,索引分区扫描可能还是高效的,原因在于:我们需要读取和重新读取的数据库块很可能已被缓存,因为数据
            都是放在一起的
        
        2.聚簇因子
            指出表中数据按照索引顺序的有序程度
            1.如果这个值与块数接近,则说明表中数据相当有序,同一个叶块中的索引条目可能指向同一个数据块
            2.如果这个值与行数接近,表中的数据的次序可能就是非常随机。同一个叶块上的索引条目不太可能指向同一个数据块

            所以,索引不一定是最适合的数据访问方式,优化器能做出正确的选择,但是这个选择可能不使用索引,影响优化器是否使用索引的因素有很多,包括数据的物理布局。
    
    小结:
        是否该使用索引取决于2个因素:
            1.如果仅用索引就能拿到答案,那么就算是要访问的数据比重很大,也该使用索引,因为这样可以不要读表并且节省了大量分散的I/O
            2.如果我们要通过索引来取得表中的数据,那就必须确保要访问的数据只占全表的很小一部分
    
位图索引
    位图索引特别不适用于OLPT系统,如果系统中的数据会由多个并发会话频繁的更新,那么也不应该用位图索引

    位图索引的结构跟B*Tree索引不同,它的一个索引键条目会指向多行数据,而在B*Tree索引中,索引键和表中的行存在一对一的关系。

    Oracle在索引中存储的内容类似如下
    值/行    1    2    3    4    5    6    
    ANALYST 0    0    0    0    1    0
    CLERK    1    0    0    0    0    0
    MANAGER    0    1    0    0    0    0

    什么情况下应该使用位图索引
        位图索引对于相异基数低的数据最为合适(也就是说,与整个数据集的总数相比,这个数据只有很少几个不同的值)。我们很难定义低相异基数到底多低才算合适。对于几千条数据来说,2就是一个低相异基数,
        但是对于一个只有2行的数据来说,就不能算低了。一般来说,数据中不同值的个数除以总行数如果是一个很小的数据(接近于0)才算低相异基数。

        位图索引最适合用于读密集的环境,而非写密集的环境,原因在于,一个位图索引键条目会指向很多行的数据,如果要修改索引列的数据的话会把大多数行的锁定。

    位图联结索引:
        索引都是建立在一个表上的,而且只会用到这个表上的列,而位图联结索引时在一个表上建立的索引基于另外一个表的某个列。

        使用位图联结索引有一个先决条件:联结条件必须是联结到另一个表中的主键或唯一键
    
基于函数的索引:
    我们可以通过基于函数的索引,对某些列经过计算之后的结果进行索引,并在查询中直接使用索引中的计算结果。使用的原因:
        1.这种索引很容易实现,而且其中的值都是计算好的,在需要的时候能够立即用上
        2.不要修改任何逻辑或查询,就可以加快现有远的速度
    
    优点:
        1.有索引时,插入9999条记录需要大约2倍多的时间(与无索引相比)
        2.尽管函数索引表上的插入运行速度慢了2倍多,但对它查询的运行速度却块了几倍。表越大无索引的全表扫描耗时就会越长,而基于索引的查询则不同,随着表的增大基于索引的查询性能几乎是保持不变
    
    可以建立一个虚拟列,并对其建立索引:
        alter table emp add index_nam as (substr(emname,1,6))
    
    只对部分行建立索引
        可以编写一个函数:如果不想对某行数据加索引,那就让这个函数给这行返回NULL,而对想加索引的行则返回一个非NULL值
    
    这个函数必须是确定的值,否则会编译报错。

应用域索引:
    利用应用域索引,你可以创建自己的索引结构,使之像Oracle提供的索引一样的工作,如果有人使用create index 语句来创建这个你的这个类型的索引,Oracle就会运行你的代码来生成这个索引

同列上的多个索引:
    在Oracle Database 12c 之前,我们不能在完全相同的列(或列组合)上创建多个索引
    自12c起,你可以在同样的列(或组合)上创建多个索引,但这么做到的前提条件:这些索引必须是物理上完全不同的(B*Tree、位图索引)


关于索引的常见问题和各种传言
    1.使用能使用索引吗
        视图实际上就是一个存放在数据库中的查询SQL,当我们访问视图时,Oracle后把视图转化其所定义的查询。如果你想对一个视图加索引的话,就在基表上加就行了
    
    2.NULL和索引能协作吗
        B*Tree(除了聚簇B*Tree索引这个特例之外)不会存储键值完成为空值的条目,而位图和聚簇索引则会存储。
    
    3.为什么没有使用我的索引
        1.你有一个B*Tree索引,但是谓语没有使用索引的最前列。如果在T表上有(X,Y)有一个索引,查询是select * FROM T WHERE Y=5。优化器就可能不会用到索引,因为谓语没有用到X列。
          如果查询的是select X,Y FROM T WHERE Y=5,优化器就不会对表进行全表扫描,只需对索引做一个快速的全面扫描就能拿到需要的全部数据,因为X,Y的数据都在索引中。
        
        2.索引列值可以全部都是null,表上没做任何约束,由于某行数据的索引列全部都为null时,索引中就不会为这行数据建立索引条目,所以索引可能跟表中的行数并不一致。

        3.在列上使用了函数,解决方法是建立一个基于函数的索引

        4.我们对一个字符列建立了索引,但是这个列中只存放了一些数值数据。select * from t where index_column = 5,这个查询实际是select * from t where TO_NUMBER(index_column) = 5(隐式转化)
        
        5.如果使用索引,数据库的处理速度反而会变慢,Oracle的优化器就会不选择索引。

    传言:索引中从不重用空间
        索引会重用空间
    
    传言:最有差别的元素应该在最前面
        一个表T有一个索引(c1,c2),你查询的谓词同时使用这2个列,那么索引中哪列放在前面并不重要。

 

posted @ 2022-10-06 15:13  小xxxx程序员  阅读(146)  评论(0编辑  收藏  举报