1.1 理解Oracle数据库
1.2 Oracle高可用特性(High Availability)
1.3 搭建高可用的周边辅助环境
1.4 高可用应用设计
1.5 高可用数据库设计
1.6 高可用性案例
1.7 总结
引言
近几年来,随着IT技术的不断进步,以及业务需求的不断提高,搭建一个数据库高可用环境已经成为很多企业迫切的需求。本书从Oracle及Oracle周边环境分析Oracle高可用环境的特性,为用户搭建一个良好的Oracle高可用环境打下一定的理论基础。
本章是本书的第1章,仅仅提供一些Oracle的基础理论知识与高可用性构架的思想,也希望能起到一个引导作用,为顺利阅读以后的章节打下一定的基础。通过本章,希望能了解如下内容:
● 理解Oracle的大致体系结构
● 理解Oracle内存结构与后台进程
● 理解Oracle物理与逻辑结构
● 理解Oracle MAA最高可用性结构与计划
● 理解Oracle的典型高可用特性
u Oracle并行服务器(OPS/RAC)
u Oracle 数据保护(Standby/Data guard)
u Oracle 数据复制(Advanced Replication/Streams)
u Oracle主机上的HA
● 理解如何搭建一个高可用环境
u 辅助环境的高可用设计
u 应用的高可用设计
u 数据库的高可用设计
● 理解一些典型的高可用设计的案例
1.1 理解Oracle数据库
1.1.1 Oracle数据库体系结构
Oracle是一个可移植的数据库——它在相关的平台上都可以使用,即具有跨平台特性,也正由于具有这个特性,加上Oracle优越的性能与开放性,才使得Oracle能取得今天这样的成绩。不过,在不同的操作系统上,Oracle除了内核是完全一样的以外,其他地方也略有差别,如在Linux/Unix上,Oracle是多个进程实现的,每一个主要函数都是一个进程;而在Windows上,则是一个单一进程,但是在该进程中包含多个线程。
从其内核,也就是内部的整体构架上来看,Oracle在不同的平台上是一样的,如内存结构(Memory structure)、后台进程(Background process)、物理与逻辑结构(Physical&Logical structure)等等。所以,作为Oracle爱好者,或者是Oracle DBA,了解了Oracle的体系结构,就基本了解了Oracle的运行原理。
一般来说,Oracle把一系列物理文件,如数据文件(Data file)、控制文件(Control file)、联机日志(Redo log buffer)、参数文件(Spfile or pfile)等物理结构及与之对应的逻辑结构,如表空间(Tablespace)、段(Segment)、块(Block)等组成的集合,称为数据库(Database)。
与此对应,Oracle内存结构和后台进程被做成数据库的实例(Instance),一个实例最多只能安装(Mount)或打开(Open)在一个数据库上,负责数据库的相应操作并与用户交互。一般情况下,一个数据库对应一个实例,但是在特定的情况下,如OPS/RAC的情况下,一个数据库可以对应到多个实例。
作为现在使用最广泛的关系型数据库,Oracle到底有什么特性能让它保持如此良好的运行状态呢?除了Oracle的跨平台特性与本身技术一直在发展之外,Oracle的体系构架与运行原理起到了不可忽视的作用,如Oracle的并发机制与锁机制,从设计上就与其他数据库有着本质的区别,保证了让Oracle比其他数据库更适合于高并发访问的OLTP环境。
在Oracle版本不断变更,新功能不断添加,系统不断完善的过程中,Oracle最基本的体系结构却是保持不变的,下面我们就先介绍一下Oracle的体系结构,从最基本的体系结构上来理解Oracle。
1.1.2 Oracle实例(Instance)
Oracle内存结构
Oracle内存结构主要可以分共享内存区与非共享内存区,共享内存区主要由SGA(System Global Area)组成,非共享内存区主要由PGA(Program Global Area)组成(见图1-1)。

图1-1 Oracle内存结构
从图1-1中可以看到,Oracle共享内存区主要包括数据缓冲区(Data buffer)、共享池(Shared pool)及一些其他的结构。而PGA则主要包括会话的一些信息及排序区,Hash join区域等,下面分别介绍这些内容。
SGA
系统全局区(SGA,System Global Area)其实是一块巨大的共享内存区域,包含了Oracle的数据缓冲及众多的控制结构。这里的数据可以被Oracle的各个进程共用,如果有互斥的操作,如锁定一个内存对象,则需要通过Latch与Enqueue来控制。
每个Oracle实例(instance)只能启动一个SGA,除非通过RAC等一些特殊的全局管理方式,否则不同的实例只能访问自己的SGA区域。通过如下的方式就可以查看SGA大小:
10gR2 Piner>show sga
Total System Global Area 8877225568 bytes
Fixed Size 755296 bytes
Variable Size 486539264 bytes
Database Buffers 8388608000 bytes
Redo Buffers 1323008 bytes
以上结果是一个典型的OLTP环境中的SGA的分配情况,我们可以看到四个大的部分。
Fixed Size,包括了一些数据库与实例的控制信息、状态信息、字典信息等,启动的时候就固定在SGA中,而且不会改变。
Variable Size,包含了shared pool、large pool、java pool、streams pool、游标区和其他结构等,合计450MB左右。
Database buffers,有时候也叫Data buffer,它是数据库中数据块缓冲的地方,数据块在内存中就缓存在这里。所以,在OLTP环境中,Data buffer是SGA中最大的缓冲区,是数据库性能高低的关键所在。
Redo buffers,它是为了加快日志写进程的速度而设立的缓冲区,在一般OLTP环境中,因为提交很频繁,所以一般不会很大。
SGA的大小信息也可以从v$sga中获得,与show sga的结果一样。另外,v$sgastat记录了SGA的一些统计信息,v$sga_dynamic_components则保存了SGA中可以动态调整的区域的一些动态或者手工调整记录。
下面将介绍SGA中最重要的两个对象,共享池(Shared pool)与数据缓冲区(Database buffer),其他的池,如Streams pool,在介绍Streams的章节时再另外介绍。
共享池(Shared pool)
共享池是SGA中非常关键的内存片段,特别是在性能和可伸缩性上。由初始化参数shared_pool_size决定其大小,在Oracle 10g以后,Oracle可以自动管理大小。
在典型的OLTP高可用环境中,一个太小的共享池会扼杀性能,导致出现Ora-04031错误,使系统停止运行。但是太大的共享池也将消耗大量的CPU来管理。在数据仓库环境中,因为并发进程的需要,可能会分配比较大的共享池。
提醒:在实际的高可用环境中,有很大的一部分故障就是因为共享池的原因而导致系统停顿甚至宕机的,如Latch争用、Ora-0431错误、Library cache争用与等待。
不正确地使用共享池只会带来灾难性的后果,所以,必须先要了解共享池的作用。共享池又可以分为SQL语句缓冲区(Library Cache)、数据字典缓冲区(Data Dictionary Cache)及一些控制结构。而数据字典缓冲区与控制结构是用户无法直接控制的,所以,真正与用户有关的其实就是SQL语句缓冲区。
当一个用户第一次提交一个SQL语句,Oracle会将这句SQL进行硬分析(Hard parse),这个过程类似于程序编译,会耗费相对较多的时间,因为它要分析语句的语法与语义,获得最佳的执行计划(Sql plan),并在内存中分配一定的空间来保存该语句与对应的执行计划等信息。
因为Oracle总是保存语句的执行信息,当数据库第二次或者多次执行该SQL时,Oracle自动跳过这个硬分析过程,变为软分析(Soft parse)或快速软分析(Fast soft parse),从而减少了系统分析的时间,减少CPU与Latch的消耗。
注意:Oracle中只有完全相同的语句,包括大小写、空格、换行都要求一样,才能重复使用以前的分析结果与执行计划。
图1-2是一个完整的SQL语句的分析过程:

图1-2 SQL语句的分析过程
所以,如果大量的,频繁访问的SQL语句都不采用绑定(Bind)变量,Oracle为了做SQL的硬分析,Shared pool latch将变得严重争用与等待,同样也会耗费大量的CPU,直到机器的资源耗尽。另外,因为Oracle会从共享池中分配空间来保存刚做完硬分析的SQL语句,也将耗费大量的内存空间,而且,这些被浪费的空间无法被重用。
对于编程者来说,要尽量提高语句的重用率,减少语句的分析时间,一个设计很差的应用程序可以毁掉整个数据库。为了避免出现这样的问题,Oracle从9i开始,还引进了一个新的参数cursor_sharing,不过,因为该参数总是带来很多其他问题,如bug,所以,在现有的数据库版本上,还是不建议在高可用的生产环境中使用。
共享池采用LRU算法来决定共享池中的对象是否继续保存,因为其空间毕竟有限,不可能无限保存所有的信息。所以,假设一个语句已经被执行过了,如果长时间没有被使用,重新执行的时候,也可能面临重新分析,如果真的执行次数如此之少,就不是高可用环境需要关心的了。
对于间歇性访问的比较大的对象,如自定义的过程与包,如果不想在运行过程中被系统自动交换出去,可以调用DBMS_SHARED_POOL.KEEP存储过程将该过程或包pin在共享池中,以减少重新载入的巨大代价。
我们可以通过如下命令手工清除共享池的内容,除了有指导的特殊情况下外,该命令不建议在高可用的生产环境上运行。
SQL> alter system flush shared_pool;
与共享池相关的视图很多,这里介绍几个可能在高可用环境中经常需要用到的视图。
v$sqlarea,每条记录都显示了一个SQL语句的详细统计信息,包括历史以来的执行次数、物理读、逻辑读、耗费时间等非常多的重要信息。
v$sqltext_with_newlines,因为v$sqlarea只是记录了一个语句或者是一个游标的前1000个字符,如果是比较大的SQL语句,则不能在v$sqlarea中完全显示。如果通过这个视图,可以获得一个SQL语句的详细信息。在这个视图中,一个SQL语句分为多行保存,通过hash_value来标示语句,通过piece来排序。
v$sql_plan视图保存了被执行的SQL语句的执行计划,可以通过特定的脚本获得以前执行过的语句的执行计划,在本书的后面章节有这样的讨论。不过,该视图在9i的早些版本,如9206以前,存在一些bug,查询该视图,可能会出现600错误,甚至导致数据库崩溃。
v$shared_pool_advice,这个视图会对Oracle的共享池做一些预测,范围可能在当前值的50%~200%之间,优化者可以根据视图显示的信息做优化判断,如重新调整共享池大小。其中的字段SHARED_POOL_SIZE_FACTOR说明了预测的共享池大小与现在大小的比例。
对于并发很多,而且访问频繁的高可用环境,需要避免如下的一些情况,在本书的后面将有详细的案例分析来说明其原因与避免方法:
l 共享池不够或bug导致的0431错误,该错误可能导致系统无法访问。
l 分析数据改变导致执行计划改变,错误的执行计划可能导致系统无法运行。
l 增加了新的对象,如索引,引起执行计划改变而导致系统无法运行。
l 修改表导致依赖的存储过程或者是包失效,而无法自动编译成功,导致系统崩溃。
l 错误的操作方法导致严重的Latch争用与等待。
数据缓冲区(Data buffer)
u 数据缓冲区(Data buffer)
数据缓冲区(Data buffer)是Oracle中用于数据块缓冲的区域,数据库常规情况下读写(非直接读写)数据块,Undo块等,都会经过这个缓冲区,并适当地保存在缓冲区。如果下一次请求操作同样的块,则不需要从磁盘获得,大大提高了系统的响应速度。
数据缓冲区虽然不像共享池那样容易导致系统故障,但是,它却是影响OLTP系统性能的关键,因为它的Cache技术可以很大程度地避免磁盘寻道,直接从Data buffer中获得。所以,Oracle把从Data buffer获得数据块叫Cache hit,把从磁盘获得数据块叫Cache miss,它们的比率就是我们常说的Data buffer命中率。
在一个典型的OLTP的环境中,或者对事务型以及小查询型的数据库来说,更高的命中率意味着更快的响应速度,所以命中率一般要求在95%以上。大的Data buffer对提高系统的性能有巨大的好处,因为Data buffer比较大,缓冲的数据块也就比较多,命中率也就更高。
但是在典型的OLAP环境中,大的Data buffer则不一定是必要的,因为OLAP的查询基本是要求从磁盘返回,而且以直接读写居多,直接读写是不经过数据缓冲区的,使得命中率失去意义。所以在OLAP环境中,需要考虑用更多的磁盘驱动器,OLAP的速度取决于硬盘的多少与系统的带宽。
数据缓冲区中的块基本上在两个不同的列表中管理。一个是块的“脏”表(Dirty List),表示被用户修改过的数据块,采用检查点队列(checkpoint queue)来管理这些脏的数据块,必要的时候通过数据库写进程(DBWR)来写入这些脏块;另外一个队列是不脏的块的列表(LRU List),比如通过Select从磁盘获得的数据块。一般的情况下,Oracle使用最近最少使用(Least Recently Used,LRU)算法来管理这些队列,但是,从8i开始,另外还增加了Touch的概念,不仅仅是纯粹的LRU算法。
块缓冲区高速缓存又可以细分为以下三个部分Default pool、Keep pool、Recycle pool,在9i以前,它们对应的是db_block_buffers、buffer_pool_keep、buffer_pool_recycle三个参数,分别表示每个缓冲区块的个数。从9i开始,又重新引入了三个新的参数:db_cache_size、db_keep_cache_ size、db_recycle_cache_size,分别表示该缓冲区的字节大小。
从9i开始,Oracle支持创建不同块尺寸的表空间,这个新的特性同时也解决了在不同块大小的数据库之间传输表空间的问题,并且可以为不同块尺寸的数据块指定不同大小的数据缓冲区。不同块尺寸的数据缓冲区的大小就由相应参数db_nk_cache_szie来指定,其中n可以是2,4,8,16或32。如创建了一个大小为2K的非标准尺寸的表空间,就可以指定db_2k_cache_size为这个表空间指定缓存区的大小。
注意:db_block_buffers与db_cache_size这两种不同类型的参数,不能同时设置。另外, db_nk_cache_size不能设置默认标准块大小的缓冲区,如默认块大小为8K,则不能设置参数db_8k_cache_size。
正确使用Default pool、Keep pool、Recycle pool也可以提高系统的性能,如把一个访问很频繁的表或索引放置在适当大小的Keep pool中,可以减少物理读,提升IO性能。是否决定使用这个功能,要看系统的具体情况,如参考Statspack中的Segment统计信息,关注其中的物理读部分,分析Top物理读的对象,如果有些对象的确不大,但是物理读又很大,就可以考虑缓冲分离。
视图v$db_cache_advice与共享池的v$shared_pool_advice一样,由Oracle自动根据一些数学模型算法,收集信息后产生的一系列建议值,可以作为调整Data buffer大小的参考。
u v$bh与x$bh
v$bh在研究与查询Data buffer使用上,是一个非常不错的视图,非常详细地记录了数据块在数据缓冲区内的使用情况,一条记录对应一个Block的详细信息。
如果通过如下语句来查询v$bh的来源,可以看到这个视图来源于基表x$bh与x$le,但是,主要数据与字段都是来源于x$bh的。
SQL>select * from v$fixed_view_definition t where t.view_name='GV$BH'
不同的是,x$bh包含了更多的信息,如touch count信息,在Oracle 8i以上作为LRU算法的一个重要的参考信息,表示了一个块的热点程度。touch count信息对应到x$bh的tch字段,而段的data_object_id信息对应到x$bh的obj,或者是v$bh的objd。有了这些基本信息,其实就很好确认热点块在哪里了。
下面简单地介绍几个关于这个视图与基表的实用方法。
(1)对象有多少个数据块缓冲在Data buffer中
为了详细说明具体的情况,先创建一个测试表,并且插入一定的记录进去。
Piner@10gR2>create table test(a int);
Table created.
Piner@10gR2>begin
2 for i in 1..5000 loop
3 insert into test values(i);
4 end loop;
5 end;
6 /
PL/SQL procedure successfully completed.
Piner@10gR2>commit;
Commit complete
如以上,先创建了一个叫test的表,并插入了5000条记录,插入记录后,用showspace存储过程分析一下表的空间使用情况。关于这个存储过程的具体代码,在本书的附录B中可以获得,因为这个存储过程本身很通用,这里就不多介绍这个存储过程本身了。
Piner@10gR2>set serveroutput on
Piner@10gR2>exec show_space('TEST');
Total Blocks............................16
Total Bytes.............................131072
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................4
Last Used Ext BlockId...................17
Last Used Block.........................8
PL/SQL procedure successfully completed.
可以看到的是,这张TEST的表一共使用了16个块,数据文件id为4,我们再分析一下数据所在的Rowid。
Piner@10gR2>select f,b from (
2 select dbms_rowid.rowid_relative_fno(rowid) f,
3 dbms_rowid.rowid_block_number(rowid) b
4 from test) group by f,b;
F B
---------- ----------
4 12
4 20
4 13
4 21
4 14
4 16
4 22
4 15
8 rows selected.
可以看到,数据块其实只占用了8个块,但是表合计占用了16个数据块,另外8个块是什么呢,它们是段头,位图块等,是表中的额外开销。至于这些块的详细信息,在本书的表空间与数据分布一章会有详细介绍,现在只需要知道,该表有16个block,数据块占了8个。
注意:以上通过Rowid查询使用了哪些数据块,只适合没有发生行迁移与行链接的情况下,如新创建的表,插入的小记录是适合的。如果发生了行迁移与行链接,因为Rowid本身不发生变化,而数据块的使用却会发生变化。
那么,现在分别查询一下x$bh与v$bh,看看它们中间保存了几条记录。
Piner@10gR2>select file#,dbablk,tch from x$bh where obj=
2 (select data_object_id from dba_objects
3 where owner='PINER' and object_name='TEST')
4 order by dbablk;
FILE# DBABLK TCH
---------- ---------- ----------
4 9 2
4 10 2
4 11 5
4 12 2
4 13 2
4 14 2
4 15 2
4 16 2
4 17 2
4 18 2
4 19 2
4 20 2
4 21 2
4 22 2
4 23 2
4 24 2
16 rows selected.
Piner@10gR2>select file#,block#,status from v$bh where objd=
2 (select data_object_id from dba_objects
3 where owner='PINER' and object_name='TEST')
4 order by block#;
FILE# BLOCK# STATUS
---------- ---------- -------
4 9 xcur
4 10 xcur
4 11 xcur
4 12 xcur
4 13 xcur
4 14 xcur
4 15 xcur
4 16 xcur
4 17 xcur
4 18 xcur
4 19 xcur
4 20 xcur
4 21 xcur
4 22 xcur
4 23 xcur
4 24 xcur
16 rows selected.
可以看到,这两个查询都返回了16条记录,从块9到块24,中间包含了数据块所在的8个块。至于为什么从块9开始,在本书的后面也会有解释,因为本地管理的数据文件头部,有8个保留块。
看x$bh,返回了块的触摸(touch count)信息,表示了一个块的热点程度,现在最热的是数据块11,它并不是数据块,而是段头,通过如下查询也可以证明:
Piner@10gR2>select header_file,header_block from dba_segments
2 where owner='PINER' and segment_name='TEST';
HEADER_FILE HEADER_BLOCK
----------- ------------
4 11
看v$bh,返回了块的状态信息,这里是xcur,表示排斥状态,被当前instance独占,也就是该块正在被使用。常见的状态还有scur,表示被instance共享;cr,表示一致性读;free表示空闲状态;read,表示正在从磁盘上读取;write,表示正在被写入。至于v$bh的这些状态,也是从x$bh中通过decode函数根据字段state解释过来的,所以能看得更明白一些。
如果这个时候,手工清除一次Data buffer,会发生什么情况呢?
Piner@10gR2>alter system flush buffer_cache;
System altered.
Piner@10gR2>select file#,dbablk,tch from x$bh where obj=
2 (select data_object_id from dba_objects
3 where owner='PINER' and object_name='TEST')
4 order by dbablk;
FILE# DBABLK TCH
---------- ---------- ----------
4 9 0
4 10 0
4 11 0
4 12 0
4 13 0
4 14 0
4 15 0
4 16 0
4 17 0
4 18 0
4 19 0
4 20 0
4 21 0
4 22 0
4 23 0
4 24 0
16 rows selected.
Piner@10gR2>select file#,block#,status from v$bh where objd=
2 (select data_object_id from dba_objects
3 where owner='PINER' and object_name='TEST')
4 order by block#;
FILE# BLOCK# STATUS
---------- ---------- -------
4 9 free
4 10 free
4 11 free
4 12 free
4 13 free
4 14 free
4 15 free
4 16 free
4 17 free
4 18 free
4 19 free
4 20 free
4 21 free
4 22 free
4 23 free
4 24 free
16 rows selected.
可以看到,x$bh中的块的tch都恢复到0了,而v$bh中的状态也都变成free了,但是记录数并没有发生变化,也就是说,数据块虽然被刷到磁盘上去了,数据块的记录指针只不过是简单地被清空而已。
明白了这些内容,如果统计一个对象的非free状态的v$bh的记录数,基本就反映了一个对象在Data buffer中的被Cache的块数。如:
select count(*) from v$bh where objd=
(select data_object_id from dba_objects
where owner='PINER' and object_name='TEST')
and status !='free'
(2)热点块问题
刚才上面其实已经就讨论到了x$bh的touch count,这个数字将作为LRU算法的一个重要参考信息。如果一个块被多次访问,每次访问都会导致该块的记录加一。
还是以上创建的测试表,这里重复选择第一条记录,看看对比情况。
Piner@10gR2>select data_object_id from dba_objects
2 where owner='PINER' and object_name='TEST';
DATA_OBJECT_ID
--------------
11835
Piner@10gR2>select tch from x$bh
2 where obj= 11835 and dbablk=12 and file#=4 and tch>0;
TCH
----------
5
下面采用了dbms_rowid.rowid_create函数来创建Rowid,表示新创建的Rowid类型为扩展Rowid,类型为1,data_object_id为11835,也就是上面的对象TEST,数据文件ID为4,块的编号为12,行数为第一行。
Piner@10gR2> select count(*) from piner.test
2 where rowid = dbms_rowid.rowid_create(1,11835,4,12,0);
COUNT(*)
----------
1
Piner@10gR2>select tch from x$bh
2 where obj= 11835 and dbablk=12 and file#=4 and tch>0;
TCH
----------
6
Piner@10gR2> select count(*) from piner.test
2 where rowid = dbms_rowid.rowid_create(1,11835,4,12,0);
COUNT(*)
----------
1
Piner@10gR2>select tch from x$bh
2 where obj= 11835 and dbablk=12 and file#=4 and tch>0;
TCH
----------
7
从以上的例子可以看到,这里选择的特定的一个数据块,初始化的touch count值是5,经过连续2次查询以后,该值就变成7。根据这一变化,就可以基本判断x$bh表中tch大的块,一般都是热点块。
有了这点认识,就可以根据x$bh来寻找系统中的热点块了。如通过如下语句来查询top 10热点块所在的热点对象。
select /*+ rule */ owner,object_name from dba_objects
where data_object_id in
(select obj from
(select obj from x$bh order by tch desc)
where rownum < 11) ;
注意:这里的hint提示是避免查询字典表时,Oracle采用复杂的执行计划,反而更慢,在以后的例子里面也会经常看到这样的提示存在。
(3)一致性块问题
上面演示了数据块如果发生select,会导致touch count增加,由此可以判断热点块。除了这个特性,x$bh还可以看到另外一个Data buffer中的特性,那就是块的一致性读。
在Data buffer中,默认最多可以保存6个一致性块的数据块,也就是说,一个块最多可以在x$bh中出现6行, 该特性由隐含参数_db_block_max_cr_dba决定。这个也就是我上面提到的,如果简单统计有效的v$bh或者是x$bh的count(*)数目,只能大致描述一个对象在Data buffer的数目。
还是用上面的例子表,选择一直在测试的块12来做实验。
SQL> select tch, flag,
2 decode(bitand(flag,1), 0, 'N', 'Y') dirty,
3 decode(bitand(flag,16), 0, 'N', 'Y') temp,
4 decode(bitand(flag,1536), 0, 'N', 'Y') ping,
5 decode(bitand(flag,16384), 0, 'N', 'Y') stale,
6 decode(bitand(flag,65536), 0, 'N', 'Y') direct,
7 decode(bitand(flag,1048576), 0, 'N', 'Y') new
8 from x$bh
9 where dbablk = 12
10 and obj=11835
11 and tch>0;
TCH FLAG DIRTY TEMP PING STALE DIRECT NEW
---------- ---------- ----- ---- ---- ----- ------ ---
16 524288 N N N N N N
这里可以看到该块当前的信息只有一行,表示只有一个一致性读的块存在,也就是当前块。但是,如果在别的会话中,反复修改该块的记录,或者删除该块的记录。如:
会话1,删除但不提交
SQL>delete from test where rowid = dbms_rowid.rowid_create(1,11835,4,12,0)
会话2,查询该记录
SQL>select * from test where rowid = dbms_rowid.rowid_create(1,11835,4,12,0)
马上,就可以发现,块12在x$bh中增加了一行新的记录,如果反复这么操作,则最多可以出现6个一致性块。
SQL> select tch, flag,
2 decode(bitand(flag,1), 0, 'N', 'Y') dirty,
3 decode(bitand(flag,16), 0, 'N', 'Y') temp,
4 decode(bitand(flag,1536), 0, 'N', 'Y') ping,
5 decode(bitand(flag,16384), 0, 'N', 'Y') stale,
6 decode(bitand(flag,65536), 0, 'N', 'Y') direct,
7 decode(bitand(flag,1048576), 0, 'N', 'Y') new
8 from x$bh
9 where dbablk = 12
10 and obj=11835
11 and tch>0;
TCH FLAG DIRTY TEMP PING STALE DIRECT NEW
---------- ---------- ----- ---- ---- ----- ------ ---
3 35659777 Y N N N N N
2 35659776 N N N N N N
2 35659776 N N N N N N
1 524416 N N N N N N
1 524416 N N N N N N
1 524416 N N N N N N
6 rows selected
可以看到,一个块出现了6条记录,而且,不管怎么样修改,最多也就出现6条记录,对应了6个不同时间的一致性块。如果再有SQL语句需要访问这些一致性块,只要其一致性块存在,就不用重新构建一致性块了。
而且,上面第一行用灰度标记出来的Y,表示dirty为Y,也就是表示这个最新的块仍是脏数据块,还没有写入数据文件中。
u 数据缓冲区与读打断
在OLTP环境中,小的查询语句,因为高缓冲区的命中率能取得良好的性能,是因为这些语句一般逻辑读在100以下,所以才能取得如此良好的效果。但是,如果要在一个典型的OLTP的环境中,做一个全表扫描,逻辑度与物理读都非常大,但是因为有大量的断续的数据块在内存中,而导致一次性读取的块根本达不到参数db_file_multiblock_read_count指定的值,所以性能会更差。
提醒:如果在一个离散读很典型的OLTP环境上做一个全表扫描,可能会因为有大量的块已经在内存中而导致性能与没有任何数据块在内存中的性能相差很远。根据使用经验,这个时间消耗的差别可以达到5倍以上。
为什么会出现这样的情况呢?假定一个表有100 000个数据块,现在全部在硬盘中,参数db_file_multiblock_read_count为16,则全表扫描只需要发生100 000/16=6250个db file scattered read即可;但是,如果已经有40 000个块已经缓冲在Data buffer中,但是分布很均匀,则可能需要发生几万次的db file sequential read + db file scattered read。看看图1-3可能就更清楚了。

图1-3 buffer与物理读
可以简单地认为db file sequential read就是单块读,db file scattered read就是多块读,关于这两个概念,在本书后面还有介绍。因为这里的sequential与scattered,不是说被读取的表或者索引上的块是连续的还是分散的,而是指读到内存中的时候,是连续的还是分散的。
图1-3用16个块模拟了一个极端情况,假定db_file_multiblock_read_count=16,在情况1中,16个块都在物理磁盘,那么只发生一个db file scattered read即可。但是,如果有一半的块在内存中,则可以分成两种情况。
l 集中式缓存:比如上面的16个块,前8个在缓存,那么这8个cache命中,另外8个发生一次db file scattered read即可,速度与全部读效率差不多。
l 分散式缓存:比如上面的16个块,很不幸的是,正好每隔一个块,缓存一个块,因为已经在缓存中的数据不用读,所以要发生8次db file sequential read读,时间可能是一个db file scattered read的好几倍。
具体论证这里就不详述了,有兴趣的读者可以自己想办法去证明。
SGA中的HASH算法
Hash算法是一种使用非常广泛的算法,假定要把100个数字放到10个容器中,最简单的做法就是把这100个数划分成10个范围段,不同的范围段放到不同的桶中即可。Hash算法采用了类似的方法,可以把一个复杂的结构Hash成一个简单的数字,然后,根据数字的比较就可以找到对应的目标(见图1-4)。
因为主机的内存越来越大,共享池与数据缓冲区也越来越大,在高达几十个GB甚至几百个GB的SGA时,如果没有一个合理的快速的管理方法,而是遍历所有的内存区域肯定是不现实的做法。
共享池使用的Hash算法,这个可能更好理解一些,因为从数据库中也可以看到,每个SQL语句都有hash value,如果是一个新的SQL语句,在进入共享池之前,要先计算hash value,然后,根据hash value去对应的桶(buckets)中查找是否有相同的SQL语句存在。这也就是为什么一定要SQL语句完全相同才能重用的原因,因为稍有差异,包括大小写不一样,计算出来的hash value就不一样。

图1-4 SQL Hash算法
除了共享池,数据缓冲区也使用Hash算法,至于数据缓冲区中的Block bucket的个数则由隐含参数_db_block_hash_buckets决定。一个块应当放到哪个Buckets里面,则是由Block的文件编号、块号等做Hash 算法决定的,Bucket里面存放了这些Buffers的地址。这样的话,只需要根据块的信息,计算出一个Hash值,就能很快地访问到这个数据块了。
PGA
Program Global Area(PGA)用来保存与用户进程相关的内存段,PGA总是由进程或线程在本地分配,在Oracle 9i以前,它们完全私有,其他进程与线程无法访问。另外,User Global Area(UGA)实际上是会话的状态,它是会话必须始终能够得到的内存。对于专用服务器进程,UGA在PGA中分配。对于共享服务器(有时候也叫多线程服务器,MTS),UGA在Large pool中分配。PGA/UGA一般保存了用户的变量、权限、堆栈、排序(Sort)空间、Hash jion空间等信息。
在Oracle 9i以前的版本中,我们可以通过手工修改sort_area_size、hash_area_size等值控制PGA的使用率。使用这种分配方法存在一个弊端,因为数据库中,特别是大并发的OLTP高可用数据库上,一般活跃着几千个并发进程,它们都有私有信息,SQL信息,排序或者Join区域,所以不恰当的设置,可能导致用户空间消耗太大,而且因为进程之间私有而不能共享,导致大量内存的消耗而严重影响主机的性能。
从Oracle 9i起,开始使用PGA自动管理,用pga_aggregate_target参数来指定所有session一共使用最大PGA内存的上限。这个参数可以被动态地更改,赋值范围从10M~(4096G-1) Bytes。另外,9i里还提供了workarea_size_policy参数用于开关PGA内存自动管理功能:自动管理(AUTO)或者手工管理(MANUAL)。在手工管理模式下或多线程服务器模式下,还是跟8i一样手工修改sort_area_size、hash_area_size等值控制PGA的使用率。
Oracle 10g之前,pga_aggregate_target只在专用服务模式下生效。而10g以后,PGA内存自动管理在专有服务模式(Dedicated Server)和MTS下都有效。在自动PGA管理模式下,有一套非常复杂的管理机制,要描述清楚该管理机制对于本书来说并不重要,在高可用的OLTP环境中,自动PGA的管理只要设置到一定的值,如2G左右,就一般能满足系统的需求,而不会影响系统的性能。而在OLAP环境,因为需要大量的join空间,理解PGA的自动管理机制还是很重要的,这里就简单介绍一下。
首先,我们把Oracle用于排序,join的内存空间叫工作区域(workarea),把能进行完全内存操作的workarea大小叫做最佳大小optimal size。与之对应的还有onepass,使用最小写磁盘操作,大部分在内存中进行。multipass,表示将会发生大量磁盘操作,性能会急剧下降(见图1-5)。

图1-5 PGA与sort、jion
在一个高可用的OLTP环境中,要保证99%以上的排序与join操作都是在最佳大小optimal(cache)size下完成的。要保证没有这样的操作在multipass环境下完成。当一个语句从用户进程提交运行时,Oracle会运行一套复杂的机制来决定需要用多大的内存,假定这个需要的内存大小称为expected size,在Oracle 9i中,可以参考如下几个规则。
规则1:expected size 不能小于minimum memory 需求。
规则2:expected size 不能大于optimal memory 需求。
规则3:如果bound介于minimum和optimal之间,那么将会使用bound值作为expect size。
sort操作除外,因为sort操作并不会从多余内存中得到利益,上面也已经提到,所以当sort操作时将会取onepass作为expect size。
规则4:如果这个workarea是并行执行的,那么它的expect size和它的并行度成正比。
规则5:最后,expect size不能超过pga_aggregate_target大小的5%或100MB,并行操作下不能超过pga_aggregate_target大小的30%。
在Oracle 10g R2以后,expect size不能超过100MB这个条件已经放宽了,不再是固定的值,而是取决于一个内部参数_pga_max_size,可使用值为该参数值的一半,并另外取决于系统设置的pga_aggregate_target参数的大小,在一定值以后,基本是该值的10%左右。
v$pgastat显示了详细的PGA的统计信息,该视图可以显示从instance以来的PGA的详细使用信息,包括PGA大小是否足够。
v$pga_target_advice是PGA的一个预测统计信息,Oracle会模拟不同PGA大小情况下的性能数据,为DBA调整PGA大小做一定的参考。
自动内存管理
Oracle的一个重要发展方向就是自动管理,当然,SGA与内存也不例外。从Oracle 9i开始,就出现了一个新的参数sga_max_size,可以保证在此数值之内的内存可以自由地修改与调配。如指定了sga_max_size,就可以在这个范围内自由地设置Shared pool、Data buffer等的大小。
sga_max_size的参数设置的内存大小,在instance启动的时候就分配完成,并且不可以动态修改。所以,在系统启动之前,需要规划好这个参数。如果不特别指定该参数大小,该参数大小就默认等于所有SGA大小之和。
从Oracle 10g开始,又出现了另外一个新的参数,sga_target,只要设置了这个参数,所有的SGA的组件,如Shared pool、Data buffer、Large pool等,都不需要手工指定了,Oracle会自动管理。这一特性被称为自动共享内存管理(Automatic Shared Memory Management,ASMM),也就是说,Oracle会根据需要随时改变各个内存组件的大小,以达到最佳使用状态。
当然,如果在自动管理模式下,sga_target的大小不能超过参数sga_max_size的大小值。因为sga_max_size指定的是SGA的最大值,而sga_target指定的是SGA现在的值,sga_target在sga_max_size的大小范围内可以动态修改。自动SGA管理模式下,也支持手工修改各个内存组件的大小,如把Shared pool指定一个确定的值,也是可以的。另外,如果设置sga_target=0,则是自动关闭自动共享内存管理功能。
SQL> alter system set sga_target = 2000M;
SQL> alter system set shared_pool = 200M;
SQL> alter system set db_cahe_size = 1000M;
SQL> alter system set sga_target = 0; --关闭ASMM
从Oracle 11g以后,这个自动化管理的范围进一步扩大,从SGA扩展到整个Oracle内存,包括SGA+PGA,所以这个特性被改称为自动内存管理(Automatic Memory Management,AMM)。
与SGA自动管理相对应,重新引入两个新的参数,那就是memory_max_target与memory_target。作为DBA或者管理员,设置好memory_max_target与memory_target之后,就不用关心SGA与PGA的大小了。
与sga_max_size对应,memory_max_target不能被动态修改,需要在系统启动的时候规划好。同样,与sga_target相对应,memory_target可以在memory_max_target大小范围内动态修改,但是不能突破它的范围。
当然,如果指定了memory_target,还想手动修改SGA大小与PGA大小,也可以手动修改这些参数。另外,如果指定memory_target=0,则关闭内存自动管理,采用手工分配策略。
SQL>alter system set memory_target= 3000M;
SQL>alter system set sga_target = 2000M;
SQL>alter system set pga_aggregate_target = 1000M;
SQL>alter system set memory_target = 0; --关闭AMM
在自动管理模式下,可以减少管理成本,让DBA把时间与精力投入到其他更重要的工作中去。另外,作为DBA,也必须规划好数据库的SGA大小和PGA大小,如果是Oracle 11g,则只用规划好总体内存大小。
不过,由于操作系统寻址能力有限制,不通过特殊设置,在32位的系统上SGA最大也只能达到1.7GB,通过特殊设置,可以达到3GB或者更大。在64位的系统上已经没有这个限制,SGA可以达到几十GB甚至几百GB。
作为SGA+PGA或者是memory_max_target,如果在内存比较小的时候,如1GB的总内存,可以考虑给40%~50%;在4G~8G的时候,可以考虑给50%~60%;在更大的内存情况下,一般可以考虑给70%,而且最好不要超过70%。
单独作为PGA,则在不同的应用环境上可能有一些差异,如OLTP环境中,对PGA的大小要求并不高,可能1~2GB的PGA内存大小就可以满足需求,而在OLAP或者是DSS环境中,SGA的Data buffer对系统的影响不大,而PGA因为Sort、并行与Hash jion的需要,可以适当的设置到总内存的40%~50%。
注意:有的操作系统上,如Aix 5.3上,Oracle SGA默认不要超过70%,否则,可能会导致系统故障甚至系统宕机。
1.1.3 后台进程(Background process)
后台进程就是与用户无关,Oracle自动运行的守护进程,用来管理数据库的读写、恢复和监视等工作。而用户进程(User process)是当一用户运行一应用程序时,如PRO*C程序或一个Oracle工具(如SQL*PLUS),为用户运行的应用建立一个用户进程,与用户进程相对应,Oracle会生成很多服务进程(Server Process)。
可以通过如下命令查询到一个Linux/Unix下的Oracle 9i系统后台进程:
piner@Oracle$ps -ef|grep ora_|grep -v grep
oracle 303146 1 4 Aug 31 - 4631:08 ora_lgwr_test
oracle 475148 1 9 Aug 31 - 2046:32 ora_dbw0_test
oracle 483402 1 0 Aug 31 - 258:41 ora_arc0_test
oracle 585780 1 0 Aug 31 - 74:17 ora_smon_test
oracle 594042 1 0 Aug 31 - 257:08 ora_arc1_test
oracle 622800 1 0 Aug 31 - 26:19 ora_cjq0_test
oracle 729262 1 0 Aug 31 - 94:24 ora_pmon_test
oracle 778476 1 0 Aug 31 - 0:06 ora_reco_test
oracle 831612 1 0 Aug 31 - 236:34 ora_ckpt_test
但是,如果在Windows机器上,Oracle后台进程相对于操作系统线程,打开任务管理器,我们只能看到一个ORACLE.EXE的进程,但是通过另外的工具,就可以看到包含在这个进程中的线程。后台进程与其他结构的关系如图1-6所示:

图1-6 后台进程
图1-6只列出了一些最基本的后台进程,也就是能保证Oracle数据库运行的最基本的几个后台进程。虽然在经过Oracle 9i/10g/11g的不断发展后,后台进程也越来越多,并且越来越复杂,不过这几个基本的后台进程基本一直没有什么变化。
另外,上图还列出了专用服务器与共享服务器的差别,在专用(dedicated)模式下,一个用户进程会对应一个服务进程。在共享服务器(也叫多线程服务器,MTS)模式下,多个用户进程可能只对应一个服务进程。
DBWR
DBWR 其实就是DataBase Writer n,如果只有一个,那么n就是0,即进程为DBW0。它是Oracle数据库中一个极其重要的后台进程,主要负责将数据缓冲区内的数据写入数据文件。其功能很简单,仅仅就是写数据缓冲区内的脏数据,也就是将脏列表(Dirty List)上的数据定期写入数据文件,和任何前台用户的进程几乎没有什么关系,也不受它们的控制。DBWn工作的主要条件如下:
l DBWR 超时,大约3秒
l 系统中没有多的空缓冲区来存放数据
l CKPT 进程触发DBWn 等
如图1-7所示,Data buffer可能存放有数据块、Undo数据块、Undo头等,而DBWn只负责写脏列表的数据,当一个服务器进程将一缓冲区移入“弄脏”表,该弄脏表达到临界长时,该服务进程将通知DBWn进行写操作。

图1-7 DBWn写进程
另外的情况,当一个服务器进程在LRU表中查找可用的缓冲区时,到了一定程度,还没有查到未用的缓冲区,它将停止查找并通知DBWn进行写操作。
在CKPT的时候,需要根据检查点队列,通知DBWn把检查点以前SCN的块写入数据文件,以减少实例的恢复时间。
LGWR
LGWR也是一个非常重要的后台进程,主要负责将重做日志缓冲区的数据写入重做日志文件,LGWR是一个必须和前台用户进程通信的进程。当数据被修改的时候,系统会产生一个重做日志并记录在重做日志缓冲区内。这个Redo记录的条目大致可以认为是:
事务标示Transaction identifier
列信息Column address(File Block Row Column)
列的值Value of the column that changed
而日志缓冲区是一个循环缓冲区。在LGWR将日志缓冲区的日志项写入日志文件后,服务器进程可将新的日志项写入到该日志缓冲区。LGWR 通常写得很快,可确保日志缓冲区总有空间可写入新的日志项。
LGWR工作的主要条件如下:
l 用户提交
l 有1/3 重做日志缓冲区未被写入磁盘
l 有大于1MB重做日志缓冲区未被写入磁盘
l 超时
l DBWR需要写入数据的SCN号大于LGWR记录的SCN号,DBWR触发LGWR写入
所以,有时候当需要更多的日志缓冲区时,LWGR在一个事务提交前就将日志项写出,而这些日志项仅在事务提交后才永久化。 Oracle使用快速提交机制,当用户发出COMMIT语句时,一个COMMIT记录立即放入联机日志文件,但对应的数据缓冲区的数据块的改变,也就是上面说的“脏”数据,一直要等到满足条件才被DBWn写入数据文件。这样做的主要目的就是可以快速提交事务并返回给用户提交信息,但是又能确保事务的完整性。
当事务提交时,它被赋给一个系统修改号(SCN),同事务日志项一起记录在日志中。在Oracle 中,SCN是一个很重要的概念,贯穿整个Oracle体系结构。下面还会单独介绍SCN。由于SCN也记录在日志中,所以,系统故障需要系统恢复的时候,就可以很容易地根据SCN来恢复。
如图1-8所示,Log Buffer默认大致可以分为三个部分,当写满其中一个部分,或者是遇到提交的时候,都会导致LGWR写数据。另外,LGWR也会有3s的超时限制,超过这个时间还没有写日志将会强制写,或者是大于1MB的限制也会强制写。因此基于以上的限制,在频繁提交的OLTP系统中,根本不需要有太大的Log buffer。

图1-8 LGWR写进程
9i其他的后台进程
CKPT:检查点进程,同步数据文件、日志文件和控制文件。DBWR/LGWR的工作原理,造成了数据文件、日志文件、控制文件的不一致,这就需要CKPT进程来同步。CKPT会通知DBWn进程写入脏块,并更新数据文件/控制文件的头信息。
PMON:进程监控进程,主要用于清除失效的用户进程,释放用户进程所用的资源。如PMON将回滚未提交的工作,释放锁,释放分配给失败进程的SGA资源。
SMON:系统监控进程,主要负责系统启动时候的实例恢复,管理回滚段的在线与离线工作,管理并清除临时空间,以及合并空闲空间等。
ARCH:归档进程。当数据库以归档方式运行的时候,Oracle会启动ARCH进程;当重做日志文件被写满时,日志文件进行切换,旧的重做日志文件就被ARCH进程复制到一个/多个特定的目录/远程机器。这些被复制的重做日志文件被叫做归档日志文件。
CJQn作业进程的管理进程,一般是CJQ0,可以自动产生j000-j999类似的作业进程来运行作业。
Jnnn作业进程,负责运行具体的作业,如j000,j001。
Pnnn并行进程,一般在并行环境中自动产生的并行进程,如p000,p001。
Snnn用于共享服务器(或者叫多线程服务器,MTS),共享服务器进程。
Dnnn用户共享服务器(MTS),调度后台进程。
Oracle 10g中的一些后台进程
QMNn是Oracle10g以后供 Oracle Streams Advanced Queuing使用的可选进程,用于监控消息队列。
MMON与诊断功能有关系,如为AWR(Automatic Workload Repository)收集性能数据的快照,并维护这些数据。
MMNL与诊断功能有关系,也为AWR服务,其全拼为Memory Monitor Light,会根据调度从SGA将统计结果采集给AWR。
MMAN与Oracle 10g 的新特性,自动SGA管理有关系,全拼为Memory Manager,在10g负责自动管理SGA,11g则负责管理整个内存(SGA+PGA)。
RBAL负责协调磁盘组间的负载平衡工作,在使用了ASM的数据库实例中运行。当向ASM磁盘组增加或删除磁盘时,RBAL进行负责处理重新平衡的请求,它还支持多个实例同时访问一个 ASM 磁盘(global open),并由 ORBn 进程实际执行数据迁移的负载均衡。实例中可以运行多个 ORBn 进程,分别为 ORB0,ORB1,以此类推。
ASMB在使用ASM磁盘组的时候负责与ASM实例的通信,向ASM实例提供更新统计信息。
Oracle11g中的一些后台进程
DBRM数据库资源管理进程,负责设置资源计划和其他的资源管理工作。
DIAG数据库诊断进程,负责维护管理各种用于诊断的转储文件,并执行Oradebug命令。
DIAn另一个数据库诊断进程,负责检测Oracle数据库中的挂起(hang)和死锁的处理。
PSPn用于产生Oracle进程。
SMCO负责空间协调管理工作,负责执行空间的分配和回收。
Wnnn命名为W000,W001,W002……,由SMCO动态产生执行上述相关任务。
VKTM用于提供wall-clock time,频率为每秒钟更新一次。它还提供每20毫秒更新一次的reference-time counter,有点类似计时器的功能。
SCN
上面介绍LGWR的时候, 我们曾经简单介绍过SCN,SCN(System Change Number)由LGWR在系统提交、超时或者检查点的时候发生,表明了一个块以至于整个数据库的更新时间戳。
Oracle 9i以上版本可以通过如下语句获得系统当前的SCN:
SQL> select DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER SCN from dual;
SCN
-------------
69383617692
SCN可能同时存在于数据文件头、控制文件头、联机日志、归档日志中,甚至是块的内部,是数据库备份与恢复、表的一致性读的基础。通过控制文件与数据文件中的SCN,可以知道当前的数据文件与控制文件是否同步,是否需要做介质恢复;联机日志,归档日志中的SCN,可以用于数据库的介质恢复,如恢复的统一的SCN点;而数据块中的SCN,则可以用于数据的一致性读。
SCN有如下特点:
l 查询语句不会使SCN增加,就算是同时发生的更新,数据库内部对应的SCN也是不同的。这样一来就保证了数据恢复时的顺序。
l 维持数据的一致性读,当一个查询执行的时候,它会先从系统中得到一个当前的SCN号,在查找数据的同时,它会检查每个数据行及其对应的SCN号,只有那些小于或等于它的SCN号的行才能从对应用户数据文件的缓冲区内取出,而那些大于它的SCN号的行,就应该从回滚段数据文件的缓冲区内取出。
1.1.4 物理结构与逻辑结构
物理结构指物理文件的集合,如数据文件、联机日志、控制文件、参数文件等,而逻辑结构,则是对用户可视的逻辑对象,如表、索引,也都是逻辑对象的一种。图1-9说明了典型的逻辑结构与数据文件之间的关系。

图1-9 逻辑结构与数据文件
从图1-9中可以看到,逻辑结构的关系是:
l 数据库可以包含多个表空间。
l 一个表空间(Tablespace)可以有多个数据文件(data file),可以存在多个段,但是一个段只能存在于一个单独的表空间内。
l 一个段(Segment)可以分布在多个数据文件中,一个数据文件也可以存在多个段。
l 区间(Extent)不能跨越在多个数据文件上,一个数据文件可以包含多个区间。
l 一个段可以划分为多个区间。
l 任何一个区间都是由一系列连续的块(Block)组成的,所以一个区间包含多个数据块。
表空间(Tablespace)
表空间是数据库中最大的逻辑存储结构,为数据库提供使用空间,其对应物理结构是数据文件,一个表空间可以包含多个数据文件,但是一个数据文件只能属于一个表空间。表空间所包含的数据文件的大小,也就决定了表空间的大小,所以,表空间也是逻辑结构连接到物理结构的一条纽带。
作为Oracle一个非常重要的逻辑对象与空间管理对象,表空间所对应的高可用管理特性也非常多。本书有很多地方都会介绍表空间的高可用特性,如第10章的表空间迁移,第14章的表空间特性与数据文件规划,都会详细介绍表空间的很多高可用特性。
这里先简单地介绍一些日常工作中遇到的简单的表空间管理特性,更多更详细的信息请参考本书后面的内容。
u 默认表空间
在Oracle 9i以前,system表空间是用户默认的表空间,当用户创建一个对象没有指定特定的表空间的时候,对象将创建在system表空间中。因此,经常发生这样的情况,system表空间因为这些可能的垃圾对象而变得非常之大,而又没有一个好的办法让它变小。
从Oracle 9i起,这个情况有了一定的变化,系统可以为数据库指定一个默认的永久表空间,如果没有指定特定的表空间,将使用这个默认的表空间。
SQL>alter database default tablespace <tbs_names>;
关于这个值可以从视图DATABASE_PROPERTIES中获得,如通过如下语句可以获知系统当前的默认表空间是什么。如果以下查询发现没有指定任何默认表空间,则数据库还是会使用system作为用户默认的表空间。
SQL>select property_value from database_roperttes
where property_name = 'DEFAULT_PERMANENT_TABLESPACE';
除了数据库可以统一指定以外,用户也可以在创建的时候,或者后期通过如下的命令指定默认的用户默认表空间,如
SQL>alter user taobao default tablespace <tbs_name>;
u 不同块大小支持
为了支持在不同块大小的数据库上传输表空间,Oracle 从9i开始,可以支持在一个数据库中存在不同块大小的表空间。
除了传输表空间以外,也可以手工创建不同块大小的表空间,需要在创建表空间的时候,指定blocksize <块大小>参数。如
SQL>create tablespace tbs_name
datafile 'data file name' size 1024M reuse blocksize 2048;
如果存在不同块大小的表空间,则需要指定特定的Data buffer,如在本章介绍Data buffer时曾经介绍过的db_nk_cache_szie参数。
u 重命名表空间
在Oracle 10g以前,数据库并不提供重命名表空间的操作,但是,随着传输表空间使用的越来越频繁,如果源数据库与目标数据库的表空间名称存在重名冲突,将使传输表空间变得困难。
其实,主要是为了解决这个问题,从Oracle 10g起可以重命名表空间,这个特性使得重名冲突下传输表空间变得很简单了。该操作可以针对任何永久表空间及临时表空间,重命名的语法为:
SQL>alter tablespace <old_tbs_name> rename to <new_tbs_name>;
另外,Oracle还会智能地帮助DBA或维护者完成配套的修改工作,例如,这个表空间如果是数据库或用户默认表空间,则在对应的数据字典中也会发生改变。甚至,如果重命名默认的Undo表空间,spfile都会发生对应的变化。
u 删除表空间中的文件
在Oracle 10gR2以前,如果在一个表空间中意外地创建了一个数据文件,虽然这个数据文件还是空的,上面没有任何东西,但是想删除它非常困难,而且基本上不可能实现。
一般的做法是Offline该数据文件,并把它修改为非常小的一个空间,但是即使这样,这个数据文件还是存在的,只不过是数据库以后不再访问这个数据文件,并且不再更新其SCN,但是这个数据文件不能丢失,也不能手工删除。
在Oracle 10gR2以后,可以通过如下的命令来删除一个多余的数据文件,这个命令对于很多误操作,对于高可用系统的管理,都是一个好的消息。具体命令为:
SQL>alter tablespace tbs_name drop datafile 'data file name'
当然,如果想删除该数据文件,必须保证数据文件为空,而且,如果该表空间只有一个数据文件,则无法删除这个数据文件,只能删除表空间。在把数据文件真正从表空间中删除以前,还是建议先offline该数据文件。
SQL>alter database datafile 'data file name' offlien [drop];
段(segment)
段是Oracle另外一个很重要的逻辑对象,一个段可以跨越在多个数据文件中,但是,一个段只能在一个表空间中。在一个段中,至少存在一个Extent,对于单独的Extent,必须是连续的空间,而且只能存在一个数据文件中。常见的段类型有以下几种。
l 表段:表示未分区的表对应的段,一个未分区的表一般就是一个段。
l 索引段:表示一个未分区的索引,就是一个索引段。
l 表分区段:如果一个表分成多个区,那么,每个区对应一个表分区段。
l 索引分区段:包括本地索引及分区的全局索引,每个区对应一个索引分区段。
l 临时段:存在于临时表空间,或者永久表空间的临时用途,用完后能马上释放。
l 回滚段:用于存放Undo数据,也叫Undo段,9i以上开始自动管理。
l LOB段:存放LOB数据的段,如果一个表有LOB字段,每个LOB字段可能还会分离出两个段,LOB段与LOB index段。
如在某个含一个LOB字段的分区表内创建一个本地索引,假定分区个数为N,则该表与本地索引对应的段有:
l N个表的分区段。
l N个索引分区段,如果本地索引个数为M,则索引段的个数为N*M个。
l N个LOB index段与N个LOB data段,如果LOB字段个数为M,那么,LOB索引与LOB数据段分别为N*M个。如创建一个有3个分区的表,包含一个本地索引,包含一个LOB字典,则合计应当有12个段。
SQL> create table test(id number,create_time date,testlob blob)
2 partition by range (id)
3 (partition TEST_PART1 values less than (100),
4 partition TEST_PART2 values less than (200),
5 partition TEST_PART3 values less than (maxvalue));
Table created.
SQL> create index ind_test on test (id) local;
Index created.
SQL> select segment_name,partition_name,segment_type from user_segments;
SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE
------------------------- ------------------ ------------------
TEST TEST_PART1 TABLE PARTITION
TEST TEST_PART2 TABLE PARTITION
TEST TEST_PART3 TABLE PARTITION
SYS_LOB0000012033C00003$$ SYS_LOB_P21 LOB PARTITION
SYS_LOB0000012033C00003$$ SYS_LOB_P22 LOB PARTITION
SYS_LOB0000012033C00003$$ SYS_LOB_P23 LOB PARTITION
SYS_IL0000012033C00003$$ SYS_IL_P24 INDEX PARTITION
SYS_IL0000012033C00003$$ SYS_IL_P25 INDEX PARTITION
SYS_IL0000012033C00003$$ SYS_IL_P26 INDEX PARTITION
IND_TEST TEST_PART1 INDEX PARTITION
IND_TEST TEST_PART2 INDEX PARTITION
IND_TEST TEST_PART3 INDEX PARTITION
12 rows selected.
另外,每一个段在数据字典中,都会有一个唯一的标示,即data_object_id,它与object_id的差别是,object_id针对的是对象,而data_object_id针对的是段。如一些对象,如存储过程、函数等,它们不是段,则只有object_id而没有data_object_id。
注意:不是每个对象都会有object_id,如db link,但是,每个段肯定都有data_object_id,因为一个段肯定属于一个表空间,所以根据data_object_id就能唯一确定该段所在的表空间,这个就是Rowid组成的基础知识。
如果段的物理存储属性发生变化,如在有数据情况下被truncate、move、rebuild等操作,data_ object_id是会发生变化的,而object_id一旦产生,则不会发生改变,除非是删除该对象。如:
SQL> select object_name,subobject_name,object_type,object_id,data_object_id
2 from user_objects;
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE OBJECT_ID DATA_OBJECT_ID
------------------------- --------------- --------------- ---------- --------------
TEST TEST_PART2 TABLE PARTITION 12035 12035
TEST TEST_PART3 TABLE PARTITION 12036 12036
TEST TEST_PART1 TABLE PARTITION 12034 12034
TEST TABLE 12033
SYS_IL0000012033C00003$$ SYS_IL_P26 INDEX PARTITION 12044 12044
SYS_IL0000012033C00003$$ SYS_IL_P25 INDEX PARTITION 12043 12043
SYS_IL0000012033C00003$$ SYS_IL_P24 INDEX PARTITION 12042 12042
SYS_LOB0000012033C00003$$ SYS_LOB_P23 LOB PARTITION 12040 12040
SYS_LOB0000012033C00003$$ SYS_LOB_P22 LOB PARTITION 12039 12039
SYS_LOB0000012033C00003$$ SYS_LOB_P21 LOB PARTITION 12038 12038
SYS_LOB0000012033C00003$$ LOB 12037 12037
IND_TEST TEST_PART3 INDEX PARTITION 12048 12048
IND_TEST TEST_PART2 INDEX PARTITION 12047 12047
IND_TEST TEST_PART1 INDEX PARTITION 12046 12046
IND_TEST INDEX 12045
现在,插入几条记录,并truncate该表,可以看到:
SQL> insert into test values(1,sysdate,'1');
1 row created.
SQL> insert into test values(1,sysdate,'110');
1 row created.
SQL> commit;
Commit complete.
SQL> truncate table test;
Table truncated.
SQL> select object_name,subobject_name,object_type,object_id,data_object_id
2 from user_objects;
OBJECT_NAME SUBOBJECT_NAME OBJECT_TYPE OBJECT_ID DATA_OBJECT_ID
------------------------- --------------- --------------- ---------- --------------
TEST TEST_PART2 TABLE PARTITION 12035 12052
TEST TEST_PART3 TABLE PARTITION 12036 12036
TEST TEST_PART1 TABLE PARTITION 12034 12051
TEST TABLE 12033
SYS_IL0000012033C00003$$ SYS_IL_P26 INDEX PARTITION 12044 12044
SYS_IL0000012033C00003$$ SYS_IL_P25 INDEX PARTITION 12043 12043
SYS_IL0000012033C00003$$ SYS_IL_P24 INDEX PARTITION 12042 12042
SYS_LOB0000012033C00003$$ SYS_LOB_P23 LOB PARTITION 12040 12040
SYS_LOB0000012033C00003$$ SYS_LOB_P22 LOB PARTITION 12039 12039
SYS_LOB0000012033C00003$$ SYS_LOB_P21 LOB PARTITION 12038 12038
SYS_LOB0000012033C00003$$ LOB 12037 12037
IND_TEST TEST_PART3 INDEX PARTITION 12048 12054
IND_TEST TEST_PART2 INDEX PARTITION 12047 12047
IND_TEST TEST_PART1 INDEX PARTITION 12046 12053
IND_TEST INDEX 12045
可以看到,在truncate之前,这些段data_object_id与object_id还是相等的,但是truncate之后,表与索引的data_object_id都发生了变化。因为data_object_id 是与段的存储属性有关的,这里表示Oracle可能重新分配过一次空间,虽然空间分配可能就是在原地,段头的位置并没有发生变化,但是data_object_id还是会变。
另外,还可以发现,如果这个段中没有任何数据,如上面的TEST_PART3,就算发生了truncate,data_object_id也没有变化。只有存在数据的段,如上面的TEST_PART1与TEST_PART2,发生truncate的时候,data_object_id才会发生改变。
块(Block)
Block概述
上面介绍了Oracle两个非常重要的逻辑对象——表空间与段,现在介绍Oracle最基本的逻辑对象,也是Oracle最小的存储单位——数据块。数据块大小在建立数据库的时候指定,虽然在初始化文件中可见,但是不能修改。为了保证存取的速度,它是OS数据块的整数倍,Oracle的所有存储IO操作都是以块为基本单位的。
在Oracle 9iR2之前,一个Oracle数据库中只能存在一个类型的块大小,从Oracle 9iR2 开始,才取消了这个限制。
块的内部结构与数据的存取方法都是比较复杂的,以表段的块为例,从简单的结构上划分,可以把块的内部划分成如下几个部分:块头(Block Header)、表目录(Table Directory)、行目录(Row Directory)、可存取空间等。
图1-10是一个表块的大致结构:

图1-10 表块的大致结构
块头(Block Header)包含着关于块类型(表块、索引块等等)的信息、块上活动和过时事务信息、磁盘上块地址信息,其中的transaction Layer决定了该块可以并发操作的事务数,其大小由init trans存储参数决定,而Variable大小由Max trans决定。如果设置了不恰当的init trans与max trans,可能导致该块在进行大批量并发操作出现严重ITL等待,甚至爆发ITL死锁。关于ITL与ITL死锁,在本书的第14章将有详细的介绍。
表目录(Table directory),包含着此块中存储各行的表的信息(多个表的数据可能保存在同一个块中,如cluster table)。行目录(Row directory)包含在块中发现的描述行的信息。以上三部分为块的开销(Block Overhead),其余部分为可用存储空间,可以用如下查询获得可用空间大小。
SQL>Select kvisval,kvistag,kvisdsc from sys.x$kvis;
可以看到,一般的8K(8192)的块可用空间为8096字节。
在手动段管理模式(MSSM)下,PCTFREE与PCTUSED是表的两个存取参数,其实是作用在表中的块上面的,PCTFREE与PCTUSED表示两个百分比,默认分别是10与40。PCTFREE表示保留该百分比的可用空间用于以后的行更新,避免行迁移。如果行数据达到PCTFREE保留的空间,该块从FREE LIST上撤消,不再接收数据。PCTUSED表示当行的空闲空间降低(如删除数据)到该参数指定的百分比时,该块重新进入FREE LIST,开始接收新的数据。
而在自动段管理模式(ASSM)下,PCTUSED参数被取消,空间的使用率与块的重用将采用另外的算法来进行。
Block的行限制
一个8K的块的可用空间虽然有8096字节,那如果插入1字节一行的数据,是否可以插入8000多行呢?答案是否定的,因为每行的其他开销导致每行的最小长度在11字节左右,所以一个8K块的行理论上最多可以存储8096/11=736行。具体可以看如下的实验过程:
这里先创建一个8K字节大小的MSSM管理模式的表空间,并且设置PCTFREE为0,这样的话,可以最大限度地插入记录,而不保留任何空间。
Piner@Ora9iR2:8K>create table test(a varchar2(1)) pctfree 0 TABLESPACE TBS_MSSM;
Table created.
先可以看看这个块大小,理论上可以插入的最大记录数。
Piner@Ora9iR2:8K>select object_id from dba_objects where object_name='TEST';
OBJECT_ID
----------
66921

