构建Oracle高可用环境HA rac:企业级高可用数据库架构、实战与经验总结

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 ;
关于这个值可以从视图DATABASE_PROPERTIES中获得,如通过如下语句可以获知系统当前的默认表空间是什么。如果以下查询发现没有指定任何默认表空间,则数据库还是会使用system作为用户默认的表空间。
SQL>select property_value from database_roperttes
     where property_name = 'DEFAULT_PERMANENT_TABLESPACE';
除了数据库可以统一指定以外,用户也可以在创建的时候,或者后期通过如下的命令指定默认的用户默认表空间,如
SQL>alter user taobao default tablespace ;
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 rename to ;
另外,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
Piner@Ora9iR2:8K>SELECT SPARE1 FROM sys.TAB$ WHERE OBJ#=66921;
     SPARE1
----------
         736
以下的sys_op_rpb函数是一个未公布的函数,表示查询指定的表中数据块最大已经达到的记录数,如果没有任何记录,则返回NULL。
Piner@Ora9iR2:8K>select max(sys_op_rpb(rowid)) from test;
MAX(SYS_OP_RPB(ROWID))
----------------------
注意:对于未公开的函数,作者不保证该函数不发生任何意外的问题,需要各位读者小心使用。
现在,在刚才创建的表中,插入6000条记录,值都是1。因为是varchar2(1),所以,每行其实只有1个字节的行长度。
Piner@Ora9iR2:8K>begin
  2      for i in 1..6000 loop
  3      insert into test values('1');
  4      end loop;
  5      commit;
  6  end;
  7  /
PL/SQL procedure successfully completed.
再通过sys_op_rpb函数查询,可以看到数据块最大保存到的记录数为733条。
Piner@Ora9iR2:8K>select max(sys_op_rpb(rowid)) from test;
MAX(SYS_OP_RPB(ROWID))
----------------------
                       733
其实,通过分析Rowid,完全可以证明这一点:
Piner@Ora9iR2:8K>select f,b,count(*) 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    COUNT(*)
---------- ---------- ----------
        11       39945          734
        11       39946          128
        11       50434          734
        11       50435          734
11       50436          734
11       50437          734
        11       50438          734
        11       50439          734
        11       50440          734
9 rows selected.
Rowid与Rdba
在上面的例子中,用到Rowid来分析数据分布。关于Rowid,在本书的很多章节都会使用。Rowid表示一个行的物理 地址,一行一旦插入数据库的块中,Rowid就已经唯一确定,只要不发生行的物理移动,这行的Rowid是不会发生任何变化的。Rowid不真正存在于表 数据块中,但是会存在于索引中,方便根据索引中的Rowid找到表数据。
Rwoid只有在行的物理位置发生改变的情况下才变化,如表的Move、Flashback table、分区表的行分区间移动等等。而行链接与行迁移等块的内部变化,变化的仅仅是指针,而不会使行的Rowid变化。
Oracle 8以下Rowid组成(也叫受限Rowid)为:FFFF.BBBBBBBB.RRRR,占用6个字节(10bit file#+22bit+16bit),但是,为了扩充的需要(如数据文件的扩充),现在的Rowid改为:OOOOOOFFFBBBBBBRRR,占用 10个字节(32bit+10bit rfile#+22bit+16bit)。其中,O是对象ID,F是文件ID,B是块ID,R是行ID。由于Rowid组成从file#变成了 rfile#,所以数据文件数的限制也从整个库不能超过1023个变成了每个表空间不能超过1023个数据文件。
注 意:这里的O代表上面提到的data_object_id,是与段物理存储位置相关的一个信息,因为一个段对象只可能在一个表空间 上,data_object_id能唯一确认ts#,而data_object_id + rfile#就能最终定位该Rowid到那个确定的物理数据文件。剩下的块地址与块内的行地址,就可以定位到具体的记录了。
Oracle 8以下的版本,FFFF表示物理文件号file#,因此,Rowid就限制了每个数据库不能超过1023个数据文件,因为超过了该限制Rowid就无法表示了。
在Oracle 8以上,因为引入了OOOOOO代表data_object_id,而data_object_id对应特定的物理表空间,所以,OOOOOO+FFF就 决定了每个表空间只要数据文件不超过1023个即可。为了向下兼容,引入了一个新的编号,rfile#,也叫相对文件号。就是说,这个rfile#是跟 Oracle 8以前的file#一样,值为1~1023。而Oracle 8以后的file#,则可以一直往下增长。
关于file#与rfile#的关系,可以从如下的视图中获得
select file#,rfile# from v$datafile
......
      FILE#      RFILE#
---------- ----------
       1020         1020
       1021         1021
       1022         1022
       1023         1023
       1024             1
       1025             2
       1026             3
......
可以看到,如果file#,也就是数据文件编号file_id超过了1023,相对文件编号rfile#则重新归1,又从1开始计算,一直循环下去。
注 意:因为Rowid中采用的是rfile#,所以,从Rowid中获得的file id不一定代表真实的file id,只能说是代表了rfile#。从上面的视图也可以看到,如果数据文件个数在1023以内,则file#=rfile#,只要整个数据库的数据文件没 有超过1023,可以认为它们是等同的。
查询一个表的Rowid,就可以获得object的信息、相对文件编号信息、块信息与行信息等等。
用例子说明一下Rowid的组成:
SQL> select rowid from test where rownum = 1; 
     AAAAeNAADAAAAWZAAA
分解一下,可以看到:
Data Object number = AAAAeN
File                  = AAD
Block                 = AAAAWZ
ROW                   = AAA
另外,我们需要注意的是,ROWID是64进制的,分布关系如下:
A-Z <==> 0  - 25 (26)
a-z <==> 26 - 51 (26)
0-9 <==> 52 - 61 (10)
+/  <==>  62 - 63 (2)
拿其中的Data Object number= AAAAeN为例子,
N是64进制中的13,位置为0
13 * (64 ^ 0) = 13
E是64进制中的30,位置为1
30 * (64 ^ 1) = 1920
A是64进制中的 0
所以
A * (64 ^ 2) = 0
A * (64 ^ 3) = 0
A * (64 ^ 4) = 0
A * (64 ^ 5) = 0
则有AAAAeN = 0 + 0 + 0 + 0 + 1920 + 13 = 1933,表示该行存在的对象,对应的对象号为1933。
而且,也可以利用Oracle提供的包——dbms_rowid来做到这一点:
select dbms_rowid.rowid_object('AAAAeNAADAAAAWZAAA') data_object_id#,
        dbms_rowid.rowid_relative_fno('AAAAeNAADAAAAWZAAA') rfile#,
        dbms_rowid.rowid_block_number('AAAAeNAADAAAAWZAAA') block#,
        dbms_rowid.rowid_row_number('AAAAeNAADAAAAWZAAA') row# from dual;
         DATA_OBJECT_ID#      RFILE#      BLOCK#        ROW#
         --------------- ---------- ---------- ----------
                      1933            3         1433            0
Oracle 8以后的普通索引或本地索引,因为每一个索引段可以对应到一个具体的表段或表分区段,然后根据这个对应关系定位到索引所在的表空间,所以,这些索引中其实 保存的还是原始的6个字节的Rowid。而全局索引因为不能对应到具体的表段,所以全局索引中,必须保证10个字节的Rowid。
正是因为索引要另外保存一个Rowid,这也就是有的时候索引刚创建出来就比表大的原因。
如果明白了以上Rowid的含义,那么就很容易理解块的地址——rdba了,其实,块的地址就是Rowid中的FFFBBBBBB部分,10bit rfile#+22bit,如分析一个块地址:
DBA: 0x2fc0100a
那么,把0x2fc0100a转换成二进制为
2     f     c     0     1    0    0     a  
0010 1111 1100 0000 0001 0000 0000 1010
再次转换
0010 1111 11                00 0000 0001 0000 0000 1010
------------                ---------------------------
数据文件id                    块ID
191(十进制)                 4106(十进制)
或者是通过如下的方法,0x2fc0100a = 十进制的801116170
SQL> select dbms_utility.data_block_address_file(801116170) "file",
  2           dbms_utility.data_block_address_block(801116170) "block" 
  3           from dual;
       file       block
---------- ----------
         191        4106
因为这里得到的191是rfile#,相对文件号,而相对文件号不能超过1023,所以,如果想根据这个地址来dump数据文件块的话,最好还是核对一下v$datafile:
SQL>select file# from v$datafile where rfile# = 191 and ts# = <:dbfile_in_ts>
如通过以下语句核对
SQL>select file# from v$datafile where rfile# = 191 and ts# = 9;
      FILE#
----------
       1214
这里的9代表该数据文件所在的表空间编号,你可能会惊奇地发现,这个块地址真正的数据文件编号应当是1214,而不是上面转换得到的191。
不过一般的情况下,数据库的数据文件都没有1023个,所以这个时候的数据文件编号file#与rfile#基本是对应的。
这个时候,如果得到了完整的数据文件与块编号,就可以采用如下方式来dump该数据块了。
SQL>alter system dump datafile 1214 block 4106;
1.2.1  什么是高可用
高可用(HA)有两种不同的含义,在广义环境中,是指整个系统的高可用(High Availability)性,在狭义方面,一般指主机的冗余接管,如主机HA,如果不特殊说明,本书中的HA都指广义的高可用性。在高可用的解释方面,可以分为如下一些方面:
(1)系统失败或崩溃(System faults and crashes)
(2)应用层或者中间层错误(Application and middleware failures)
(3)网络失败(Network failures)
(4)介质失败,一般指存放数据的媒体介质故障(Media failures)
(5)人为失误(Human Error)
(6)分级与容灾(Disasters and extended outages)
(7)计划宕机与维护(Planned downtime, maintenance and management tasks)
可见,高可用不仅仅包含了系 统本身故障、应用层的错误、网络错误、人为错误等,还应当包括数据冗余、容灾及计划的维护时间,也就是说,一个真正的高可用环境,不仅仅能避免系统本身的 问题,还应当能防止天灾人祸,并且有一个简单可靠的系统维护方法如微码升级、软件升级等计划停机维护。
本书定位在Oracle数据库层面上的高可用性,同时也会介绍一些如主机、存储、操作系统、分级存储、容灾方面的高可用性与业务连续性保证。除了应用层及中间层的高可用性仅仅是在本章后面有一定的描述以外,其他部分在本书其他章节都有一定的介绍。
高可用的计算方法一般以年在 线率来计算,如规定整个系统一年之中的可用环境要达到99.95%,那么24*365*(1-99.95%)=4.38小时(包括计划内维护时间)。另 外,子系统的可用性一定会高于整个系统的可用性,如承接前面规定整个系统的可用率为99.95%,那么对于数据库子系统,可用性很可能就是要求达到 99.99%。高可用性的在线率(可用级别)与停机时间可以参考如图1-11所示的对照表:
图1-11  高可用级别对照表
基于以上的规定,假定一个系统一年之中故障时间是1小时(差不多99.99%),但是计划内维护时间却花了20小时,那么这个系统也不能算是一个满足设计要求的高可用环境。
现阶段使用环境中,基本没有真正的100%的在线环境,或者说,如果达到100%的在线能力,要付出非常大的代价,所以一般能达到99.9%以上的可用性的环境,一般都可以认为是比较高的可用环境了。
对于高可用性在线效率的计算,可以参考如图1-12所示的方法:
图1-12  收益与成本
在公司收益与投入成本计算方面取得一个平衡,则是最终所希望的在线效率,但是收益与成本的计算方法则是决策者与实施者需要着重考虑的问题了。本书的很多地方,其实也希望能提供一种思想,那就是怎样搭建最适合自己的高可用环境,而不是盲目地去追逐最高可用性。
1.2.2  Oracle最高可用性体系结构(MAA)
随着Oracle 9i/10g/11g的更多高可用特性的出现,Oracle也推出了它自己的高可用概念,那就是Oracle 最高可用性体系结构(Oracle Maximum Availability Architecture,MAA)。它是Oracle提供的全套的高可用解决方案,由Oracle已经在使用的高可用特性组成,目标是消除设计最优高可 用性体系结构时的复杂性。
Oracle 的MAA从非计划宕机到计划内的停机维护说明了高可用的保证,在MAA体系结构中,可以分为如下4个部分。
u         非计划宕机
l           系统失败:RAC
l           数据异常:Data guard、ASM、Flashback、Rman、Streams
u         计划内停机
l           系统改变:在线修改配置,在线滚动补丁升级
l           数据变化:在线重定义
以上内容在本书都会有详细的描述,如第4章将专门介绍RAC与ASM;第5章专门介绍Data guard;第6章专门介绍Streams;第8章介绍了Flashback;第九章介绍了RMAN。
至于计划内停机的一些可用性,可以从如下几个方面考虑:
l           在线修改配置的特性,如ASM动态增加移动硬盘,Oracle内存或SGA的在线调整,RAC动态增加与删除节点。
l           在线滚动补丁升级的特性,如RAC环境的滚动升级,Data guard环境的滚动升级。
l           在线重定义特性,如在线重定义表,在线rebuild索引等等。
以上的这些特性,在本书不同的地方也有详细的描述。
因为本书不仅仅是基于Oracle本身的高可用特性,也包括Oracle数据库的辅助环境的高可用性,所以介绍的范围会更广泛,将包含存储、主机等很多辅助环境。
不过,Oracle推出MAA计划,也表示了它对高可用性方面的重视,特别是从Oracle 9i/10g/11g看来,很多特性都是为高可用性准备的。可以这么说,Oracle 8i/9i开始出现很多高可用的特性,而在Oracle 10g/11g中,它们更完善、更可靠了。图1-13是一个典型的Oracle MAA体系结构。
图1-13  典型的Oracle MAA结构
在该体系结构中,数据库采用 了RAC+ASM+STANDBY的结构体系,应用层采用Oracle自己的Application Server。用户通过负载均衡设备访问不同的Oracle应用服务器,而应用服务器通过自动负载均衡及Failover特性访问当前的主数据库。
当主站点出现故障的时候,Data guard可以手工或者是自动切换到备用端,应用服务器的访问也将自动被切换到备用站点,以保证系统的最大可用性与业务连续性。
1.2.3  Oracle高可用相关功能的产品概述
Oracle提供的高可用相关产品主要有下面几种:
(1)Oracle Parallel Server(8i)/ Real Application Cluster(9i/10g/11g)
(2)Oracle Standby Database(8i)/Oracle Data Guard(9i/10g/11g)
(3)Oralce Advanced Replication(8i)/Oracle Stream(9i/10g/11g)
(4)Oracle Server HA
(5)Other: Mv/RMAN/Oracle Log Miner/Oracle Flashback Query(9i/10g/11g)
等等。本章先对前4个主要的高可用特性进行简单介绍,为本书以后的章节做一个铺垫。
Oracle并行数据库OPS /RAC
OPS从Oracle 8开始提供,从Oracle 9i起开始称为RAC,作为本地环境高可用的典型代表,OPS/RAC设计初衷就是系统与应用的高可用(System/Application high availability)。与其他产品相比,OPS/RAC是多个服务器的Cluster,组成具有更大计算处理能力与故障处理能力的集群。 Cluster里面不同的节点(node) 使用一个(一般是一个)或多个Oracle实例(instances)与一个数据库(database)连接,该数据库存放于多个节点的公用存储 (Shared Storage)上。
提醒:因为OPS的技术不够完善,如果不作特殊指定,本书介绍的都是Oracle 9i以后的RAC环境。
主要的技术特点:
(1)Database 所有的Data files 是建立在共享存储(Shared Storage)上面的,一般可以采用RAW设备、共享文件系统或ASM(Oracle 10g以后开始提供)。在早期的版本中,对Cluster软件依赖性比较高,不过从9i/10g开始,Oracle也不断地开发了自己的Cluster软 件与存储管理,如OCFS、CRS、ASM都是为RAC而准备的,并且不断地完善,也开始被广泛使用。
(2)RAC在共享存储方面 并没有冗余保护,在共享存储阵列损坏的情况下不具备切换的能力,因此媒体介质损坏(Media failure)方面,要依靠RAID(redundant array of inexpensive disk) Subsystem、ASM、LV镜像(LV Mirror)、卷复制(Volume Replication)或Standby/Data guard来实现数据的冗余保护。
(3)该技术是Oracle 近来主推的技术,特别是10g以后的网格计算与线型扩展能力,在电信、移动、银行等行业使用广泛。10g以后RAC的线形扩展能力,可以把负载均衡分布到 多个节点实现共同计算,使RAC在数据仓库环境(OLAP)上也开始大量使用。如果还是老的OPS,则不建议使用,Oracle 9i的RAC也或多或少有一些问题,但是Oracle 10g以后的RAC技术逐渐成熟,基本可以大范围使用了。
不过,RAC在高可用环境下的管理成本与技术的复杂性,也是需要考虑的。
如图1-14所示,最理想的RAC环境,其实就是10g以后提出来的网格计算的概念,在存储层采用ASM实现可扩充性的存储网格计算。ASM在这里可以实现数据的分布、镜像及自动负载均衡等。
图1-14  RAC网格计算
在中间层实现了数据库的可伸缩性,例如可以根据负载情况增加或减少节点个数,可以根据业务情况划分业务的资源分配,可以根据每台机器的负载实现负载均衡,可以充分利用每个节点的资源实现分布计算。
最上层就是应用的分布计算 了,应用分布对比数据库分布要容易得多,一般采用廉价的PC服务器就可以实现线形扩展。不过,在高可用的RAC及网格计算模式下,多个应用在一个RAC环 境中,更有利于资源的合理分配。比如,在特定时期,可以让财务系统使用更多的机器实现财务对账计算,在另外一个时期,可能让其他业务系统使用更多的资源计 算。
所以,理想的RAC以及网格计算,不需要关心数据库是放在什么位置,数据库是怎么运行的,只需要发出一个需求,让网格返回一个结果而已,就像我们用电一样,插上插头,电灯就可以亮了。
Oracle备用数据库Standby/Data Guard
Standby database/Data guard是Oracle推出的一种高可用性数据库方案,在主节点与备用节点间通过日志同步来保证数据同步,备用节点作为主节点的备份,可以实现快速切换与灾难性恢复。
Oracle从7.3才开始 支持Standby database。从9i开始,发展为Data guard,并支持Maximize protection、Maximize availability、Maximize performance三种保护模式,可以实现自由的手工主备切换,实现高可用的条件。如果配置合理,可以部分实现自动切换。
从Oracle 9iR2开始,除了原来的物理备用数据库(Physical Standby),也开始支持逻辑备用数据库(Logical Standby),逻辑备用数据库能够实现在数据同步的时候可读写。另外,从Oracle 11g开始,Oracle也开始推出物理备用数据库可以在Open read only模式下实时(real time)同步日志。
提醒:如果不做特殊说明,本书中介绍的Standby都是Oracle 9i以后的Data guard环境,即使名称叫Standby,其实也是指Data guard。
主要的技术特点:
(1)可以实现数据库主机以及共享存储的完全冗余保护,该冗余甚 至可以跨地域,做成容灾保护,是Oracle主推的容灾产品。在Standby中,主节点必须运行在归档模式下,并且可能要强制归档(Force Logging),以保证备用节点的数据正确性,因此,该特性在一定方面并不适合数据仓库。
(2)Standby主备用节点对OS的环境要求比较高,一般要求是相同或者是相近的OS环境(Oracle 11g以后这个条件有所放松),而且数据库版本也有特定的要求。如不能做Oracle 9i到Oracle 10g的Standby。
(3)除了最大保护模式外,其他模式下如果主站点的存储损坏而导致备用站点进行失败切换的时候,需要注意数据的丢失问题,务必保证当前环境联机日志的多重冗余保护,并且在切换之前,完全同步主站点当前的联机日志。
(4)在Oracle 11g以前,物理备用节点的主机与存储基本不能提供访问,仅仅能提供只读查询,所以该技术也有严重的资源浪费,不过该技术因为成本比较低,管理方便,技术 成熟,所以被广泛使用。在Oracle 11g以后,物理Standby也可以在应用日志的时候就提供只读查询,大大的提高了物理Standby的可用性。
(5)从Oracle 9i开始提供的逻辑Standby,可以在应用SQL同步的时候,提供读写操作。但是,Oracle 9i的逻辑Standby问题比较多,Oracle 10gR2以后的逻辑Standby可以适当使用,也是一个读写分离的好方法。
如图1-15所示,备用Standby如果是逻辑 Standby,或者是Oracle 11g以后的物理Standby,则可以在应用日志(SQL)的时候提供读操作,做到读写分离。当可读的Standby很多时,如一个主数据库能够连接很 多可以提供读操作的备用Standby,读写分离的效果将更明显。
图1-15  Standby读写分离
另外,在主数据库发生故障的时候,可以选择切换到其中一个Standby上,在做设计的时候,为了成本考虑,可以选择一个Standby的硬件环境跟主库相近,提供切换,另外的Standby仅仅是提供读查询,硬件环境也可以相对差一些,不提供切换。
为了保证Standby的更高可用性,可以将RAC与Standby结合使用,就如上面提到过的Oracle MAA体系结构,主库与备用库都是RAC。另外,如果需要,选择一些单节点的机器,提供非接管性的查询服务。
Oracle高级复制与流(Advanced Replication/Streams)
Advanced Replication/Streams 的设计目的是更灵活地实现数据分布,这种技术可以将一个数据库中的表,用户(Schema),表空间(Tablespace)或者整个数据库复制到另一数 据库中,甚至是双向同步。 Advanced Replication是基于内部触发器的技术,而Streams采用挖掘日志的方式,对主系统的压力将更小。
从Oracle 9iR2开始,Oracle更倾向使用Streams的技术,但是也不是说Oracle不再发展高级复制了,只不过是Streams将更适合于高可用环境 的复制。而且本书将不介绍高级复制,只介绍Streams或与其原理相同的Quest share plex/dsg realsync。
在Oracle 10g/11g中,Streams技术又得到了很大的强化和扩展,如10g以后开始DownStream、DownStream real time捕获等,对主系统的影响就更小了。甚至可以通过在异地对归档日志/联机日志的挖掘,在对主系统基本没有任何压力的情况下,实现对数据库的表对象、 用户、表空间甚至整个数据库的同步。从11g起,Streams还可以支持实时的数据捕获。
Streams主要的技术特点如下:
(1)Streams最大的特点就是灵活,可以跨平台对单独的表对象、用户、表空间或者整个数据库进行复制。而且复制的压力更小,如在主库之外的机器上分析日志,将对主库没有任何额外压力,著名的复制软件Share plex就是采用类似的技术进行数据复制的。
(2)Streams还能实现双向复制,或者多源复制(见图1-16),可以根据应用的特点,自定义复制方式,这个是Standby或者其他容灾软件所不能做到的。
(3)可以实现数据库主机及共享存储的完全冗余保护,甚至是跨地域容灾保护,在很多比较大型的在线系统中,如eBay购物网站,采用该技术(share plex)实现系统的读写分离,通过该技术把写站点的数据复制到多个读站点,大大提高系统的可用性、扩展性与安全性。
因为Streams在Oracle 10gR2以前,问题还比较多,导致该技术没有被广泛使用,但是其对应软件share plex使用还是很广泛的,不过因为其昂贵的价格,则是需要考虑其搭建成本的。不过,Oracle 10gR2以后,Streams还是值得期待的。
图1-16  Streams复制
如果说Standby主要用在容灾设计上,Streams则主要用于Standby所不能完成的,灵活的数据同步。它可以是:
l           跨平台,跨版本等迁移。
l           可以灵活配置,如只同步一部分数据,甚至可以相互同步。
l           可以跨数据库同步,如从Oracle数据库同步到MS SQL Server数据库。
l           两边的数据库可以同时读写。
以上一些特点,在很大程度上,主要用来弥补Standby的一些不足,当然,特别是Oracle 11g以后,Standby与Streams走得越来越近,但是,Streams的一些优势,如部分数据同步、自定义规则的同步、多源同步等问题,Standby还实现不了。
主机相关HA
Oracle主机HA(Server HA)就是上面说的狭义HA,是基于OS的技术,采用OS支持的Cluster Soft来保证主机的冗余保护,可以在主机错误、网络错误时实现自动的保护切换。但是,跟RAC一样,它使用共享存储,所以不能保证共享存储的高可靠性。
它的基本架构共分两种模式:双机互备援(Dual Active)模式和双机热备份(Hot Standby)模式。对于双机互备模式,双机都是正常工作的,但是工作业务不同,在一个主机故障时,切换到另外一个主机上;双机热备模式则只有一个机器 工作,另外一个机器处于接管状态。不过,它们的最终原理还是active/standby机制。
不管是哪种模式,Cluster软件都可以正确检测到系统异常并自动进行失败切换,如果是双机互备模式,则需要注意当两个业务都切换到一个Server上的时候,该机器是否能承载双份的压力。
主要的技术特点:
(1)与RAC一样,Database 所有的数据文件(Data files)是建立在共享存储上面的,存储的冗余保护则需要依赖其他技术,如RAID、存储复制、卷复制等等。
(2)HA的技术简单成熟,所以在实际使用中,也能被广泛采用,但是,对主机资源的浪费也最为严重,基本上要保证有对等的资源处于等待状态。
HA很类似于RAC,需要两个或两个以上的主机系统。 因此,在主机失败,或者是一个主机的网络失败的情况下,都可以提供某种程度的恢复,保持系统可用。HA与RAC最大的差别是,一个是OS 上的active/standby解决方案,一个是Oracle的提供的active/standby的解决方案:
图1-17  主机HA
如图1-17所示的是一个典 型的主机HA双机解决方案,主机采用双机互备模式,也就是典型的active/standby方式。如果主机发生故障,业务可以自动切换到备用机上。另 外,因为共享存储本身不提供保护功能,这里采用存储镜像技术复制到另外一台存储上面,提供数据,包括在必要的时刻提供数据查询以及存储失败的接管。
主机HA的最大好处就是可以 解决服务器的单点故障问题,如机器故障,与RAC一样,它并不能解决磁盘故障问题或阵列故障问题。所以HA也必须采用附加的备份机制,如存储同步与异步复 制技术、LV镜像与复制技术、数据备份策略等,也可以配套使用Oracle Data guard来实现数据保护。
主机HA的机制起源比较早,发展到现在已经日趋成熟,在实际案例中,使用还比较广泛,但是它必须有一半的资源处于等待状态,所以资源浪费比较严重。
除了数据库环境的高可用设计 外,其他的高可用设计包括Oracle数据库高可用辅助环境设计、应用设计、数据库设计等方面。不过这些都不是本书的重点内容,本书只做一些简单的介绍, 其中,第2章会比较详细地专门介绍高可用存储与主机的选择和设计,本章也会介绍一些简单的高可用网络设计知识、高可用应用设计及高可用数据库设计。
这里先介绍高可用的网络设计,存储与主机的高可用设计还是等到第2章介绍。可靠的网络环境在高可用环境中是必不可少的,网络环境主要可能考虑的是如下几个因素:
l           可靠性,包括链路与硬件冗余
l           高速性,包括响应速度与吞吐量(带宽)
l           健壮性,比如在Dos攻击时的表现
l           安全性,有很强的安全策略,防止别人侵入
如果一个网络能做到以上4点,那么这个网络基本可以被认为是一个高可用的网络了,复杂的网络环境,还可能涉及路由规划、DNS设置、城域或广域的高速互连。当然,在复杂的网络环境中,很多事情需要网络运营商的配合,而不是单个公司可以解决的。
1.3.1  简单网络
一个简单的高可用网络环境,可能就分布在一个公司内部或一个机房内部,图1-18是一个网络运营商(或者是IDC机房)内部的一个高可用局域网环境:
图1-18  简单网络设计
我们可以看到,这个结构可以简单的分为3个层次:
(1)网络出口层,即最上层的出口是网络运营商,如电信运营商或网通运营商。他们负责把内部网络的信息流和外部互联与传递。
(2)核心网络层,就是中间的主要网络设备,如主交换机、主路由器都采用了对称冗余的方式,在路由器与交换机之间,还存在冗余的负载均衡设备。
(3)应 用网络层,网络的最下层就是内部应用层了,这里可能包括所有的应用服务器与数据库,还有一些内部的小的网络交换机与负载均衡设备。为了安全考虑,应用可能 会根据不同的类型被分配到不同的子网里面去,如网络Vlan,不同的Vlan之间有不同的访问策略,这个可以通过网络层的策略与ACL来完成。
1.3.2  复杂网络
如果说简单的网络一般都是一个局域网环境,那复杂的网络一般都是城域网或广域网络了。在这样的网络环境中,设置将变得更复杂,不过,有一点是始终不变的,那就是冗余,没有冗余就没有网络的高可用。
图1-19是一个中等复杂的城域网络。
图1-19  复杂网络设计
在以上网络环境中,通过一系列路由设备,把4个子网互联起来,每个子网可以相当于一个上面的简单网络设计中的环境。
从以上看来,网络基本就是一座桥梁,连接了最终的用户与应用,也连接了应用与数据。网络的速度、带宽、稳定性、安全性也就决定了这座桥梁的高可用性。所以,骨干网络的设备都是多重冗余并且是可接管的。
在更复杂的远程网络环境中,涉及的路由设备可能会更多,可靠性与安全性设计也会更复杂。
1.4.1  高可用应用设计技术
高可用应用设计这几年发展得很快,新的技术也层出不穷,但是,主要包含如下三项技术,或者是这三项技术的组合:它们是分布式技术、Cache技术与Search技术。
u          分布式技术
应用服务器相对数据库服务器来说是更廉价的,所以,应用设计最根本的一点就是分布。应用服务器可以采用几百台,几千台甚至 几万台来做分布式计算,用户行为与应用服务器之间采用负载均衡设备,这样的情况下,即使坏掉几台服务器,对整个应用构架也是没有任何影响的。分布式技术现 在已经从网格(grid)计算技术扩展到云(cloud)计算技术,并进一步走向虚拟化。
u          Cache技术
除了分布,高可用应用设计最核心的技术是Cache,所谓Cache就是高速缓存,而应用层Cache,就是利用服务器内 存,把数据缓存到内存中,利用内存的高访问速度,提高应用的访问速度。当今时代,Cache无处不在,如存储有Cache,主机有Cache,数据库有 Cache,应用也有Cache。这里主要介绍的是应用的Cache,数据库的Cache就是上面所说到的SGA,至于其他的一些Cache方式,如存储 Cache,本书第2章也会有详细介绍。
Cache在内存中的数据,可以采用定时更新或者是实时更新。在定时更新中,数据同步一般有一个延迟;如果是实时(real time)更新,如被Cache的数据发生了变化,就更新Cache服务器,或者是通知Cache服务器来获取最新的数据。
当然,Cache也有很多变化,如分布式Cache,把分布技术与Cache技术有效结合,让Cache数据分布在多个应用服务器上面,而且任何一个数据可能被Cache多份。分布式Cache的最大的好处就是,如果单个机器的损坏,对整个Cache环境没有任何影响。
Cache技术还可以扩展到内存文件系统、内存索引、内存数据库等不同的领域与方式。如分布式的内存文件系统,结合了文件 系统的优点,又实现了分布与快速响应,可以快速返回文件数据,如图片文件的存放与读取。内存数据库则结合了数据库的特点与Cache的快速,如 Oracle公司的timesten内存数据库,既可以实现数据库的事务型应用,又可以实现数据的快速响应。
u          Search技术
除了分布式与Cache技术,现在还有一个发展很迅速,使用很广泛的技术,那就是Search技术。其实Search也是基于Cache的,不过,Search还有自己的很多技术特点,如Build技术,分词技术等等。
一般的Build技术采用定时Build,因为在数据量非常巨大的情况下,很难做到实时(real time)的Build。如Google、Baidu基本上都是每天定时更新一次数据,所以,在更新以前,怎么搜索都是相同的结果。当然,在一些特定的情 况下,也需要采用实时Build,如电子商务网站eBay,希望每个商品的改变都能立即显示出来。实时Build会要求每个改变都能立即通知到Build 服务器,然后同步到Search的特定内存索引上。
1.4.2  典型应用构架结构
因为以上的几项技术并不是单独存在的,而且,不同的应用可能在不同的层次上,我们可以对应用做一个简单的层次模型(如图1-20)来观察这些技术。
图1-20  应用设计
在典型的应用设计体系结构图中,可以分为3个层次,每个层次之间还可分为多个子层。层次之内的模块是独立的,没有业务关联关系,但是,层次之间存在一定的交互关系。
(1)用户层,代表了用户的所有请求,以及活动行为。
(2)应用层,首先是包括各种各样的产品与功能,如交易系统、财务系统等,然后通过下        层的各种核心服务,如一些公共通用性服务接口,直接访问下层数据,为搜索引擎提供数据等。
在核心的服务之下,就是应用服务接口API,这些代用业务性质的API可以被上层的服务直接调用,也可以开放给外部客户,供他们访问。在应用服务接口API之下,就是应用层的分布式Cache,提供被访问数据的缓冲。
(3)数据层,从应用服务接口API访问的数据,可能直接被应用层的Cache返回,直接返回的叫Cache命中,如果不能命中的,则需要从数据库,或者是从文件系统上返回。数据库与文件系统也可能是分布式的,通过数据调用接口API统一控制。
数据调用接口API中,主要包括路由技术与队列技术。
路由技术则是封装了数据库的位置,甚至是数据库的类型,如有两个不同的数据库,Oracle与Mysql,甚至是Oracle与文件系统,它们分布在不同的物理位置,但是路由技术知道从什么位置、什么数据库中获得必要的数据,而不需要应用层来关心。
队列技术则是一种同步技术,如分布式的数据库中,经常有一个事务需要跨数据库来运行,这种情况下,可以先写队列,由队列同步到不同的数据库中。
在上面介绍了很多Oracle数据库的高可用产品,它们也是数据库设计中非常重要的一个环节。虽然它们是本书的重点,也是保证Oracle高可用环境的不可缺少的部分,但是,除了这些,另外也还需要考虑一下数据库的应用设计或者是叫Schema设计。
数据库的Schema设计的时候,也是需要有丰富的设计经验,才可以设计出来一个高效的高可用数据库的。好的数据库设计,可以保证数据库的可扩充性与良好的性能。下面就范式设计、反范式设计与数据库分布设计说一下数据库Schema设计。
1.5.1  数据库范式设计
对于数据库范式设计,一般在设计时,只需要了解到第三范式即可,后面的范式没有必要过于计较,前三种范式在数据库设计中还是非常重要的。
u          第一范式(1NF):在关系模式R中的每一种具体关系r中,如果每个属性值都是不可再分的最小数据单位,则称R是第一范式的关系。
第一范式简单的说,就是要求属性具有原子性,不可再分解。表1-1是一个最简单的例子。
表1-1
编号
姓名
选修科目
001
张三
语文,数学,英语
002
李四
物理,化学
003
王五
历史,地理,生物
从表中可以看到,在课程选修表中,选修科目设计为一个字段,选修的科目内容用逗号分开,看起来是好像没有什么问题,但是,如果想统计哪些人选修了哪门科,将变得非常困难,如想统计哪些人选修了化学,可能需要用到如下的SQL
SQL>select count(*) from course where subject like ‘%化学%’;
除了这个影响,更新的影响也非常大,如,上面的李四现在想在选修科目中增加一门生物,他必须先读出以前的科目,然后在科目后面合并一个生物,这个表就不满足第一范式。
那么,为了满足第一范式,可以做一点简单的修改(见表1-2)。
表1-2
编号
姓名
选修科目
001
张三
语文
001
张三
数学
001
张三
英语
002
李四
物理
002
李四
化学
003
王五
历史
003
王五
地理
003
王五
生物
作了以上修改就可以满足第一范式了,如果想统计哪些人选修了化学,SQL语句可以修改为。
SQL>select count(*) from course where subject = ‘化学’
如果想给李四增加一门生物选修课,只要insert一个新行即可。
u       第二范式(2NF):如果关系模式R(U,F)中的所有非主属性都完全依赖于任意一个候选关键字,则称关系R是属于第二范式的。
第二范式简单地说,就是每个表有个主键,其他字段完全依赖于该主键。
还是以上面的表为例,现在满足了第一范式,但是,不满足第二范式,也就是没有可以唯一确定的主键,如果王五同学想把课程地理修改成天文学,他不得不把所有的字段作为查询条件来更新该记录。那好,我们增加一个主键ID(PK),如表1-3所示:
表1-3
ID(PK)
编号
姓名
选修科目
1
001
张三
语文
2
001
张三
数学
3
001
张三
英语
4
002
李四
物理
5
002
李四
化学
6
003
王五
历史
7
003
王五
地理
8
003
王五
生物
有了主键,如果能确定哪一行需要修改,用主键就可以定位该行。如王五同学想把课程地理修改成天文学,那么更新语句可以是根据主键来更新。
SQL>update course set subject = ‘天文学’ where id=7;
这样,以上的表符合第二范式了吗,其实还没有,我们可以看到,编号依赖于ID,但是姓名是依赖于编号的,没有唯一的主键,还不符合第二范式,那会有什么问题呢?如张三要换一个名字,就要更改这个表中的三条记录,如果涉及1万条记录呢,那就需要更新1万条记录。
这也仅仅是其中一个问题,还有,假如要统计有多少个学生,从课程表中是很难获得的,因为可能有的学生没有选修课程,就是全部选修了,也要distinct,大大影响了性能。
基于以上很多影响,可以修改为如表1-4和表1-5的父子表:
表1-4为父表——学生表,表1-5为子表——课程表。
表1-4
编号(PK)
姓名
来源地
001
张三
北京
002
李四
上海
003
王五
广州
表1-5
ID(PK)
学生编号(FK)
选修科目
1
001
语文
2
001
数学
3
001
英语
4
002
物理
5
002
化学
6
003
历史
7
003
地理
8
003
生物
有了上面的两张表,现在,清晰多了,学生信息直接从学生表中获得,课程信息直接从课程表中获得。
u          第三范式(3NF):如果关系模式R(U,F)中的所有非主属性对任何候选关键字都不存在传递信赖,则称关系R是属于第三范式的。
第三范式是很重要的,表示了不要存在值的依赖关系,比如还是上面的课程表,现在增加了选修课成绩等级(见表1-6)。
表1-6
ID(PK)
编号
选修科目
成绩
等级
1
001
语文
60
及格
2
001
数学
87
良好
3
001
英语
57
不及格
4
002
物理
43
不及格
5
002
化学
99
优秀
6
003
历史
90
优秀
7
003
地理
74
及格
8
003
生物
81
良好
这里,相信大家都能看出来问题在哪里,因为等级是直接依赖于成绩的值的,现在假定某个人的成绩从60修改成50,那么不得不修改对应的等级,如果修改失误,则可能造成数据的异常(不对应),这就不符合第三范式。
除了上述3种范式,还有很多其他范式,这里不再一一列举,这三种范式比较重要,所以,在这里列了出来,供大家在设计数据库的时候参考。
注意:数据库的设计的好坏,将可能影响到应用的性能,数据库设计得好,可以为高可用的环境打下良好的根基。
1.5.2  反范式数据库设计
数据库设计要严格遵守范式,这样设计出来的数据库,虽然思路很清晰,结构也很合理,但是,有的时候,却要在一定程度上打破范式设计。
这里其实并不矛盾,因为范式越高,设计出来的表可能越多,关系可能越复杂,但是性能却不一定会很好,因为表一多,就增加了关联性。特别是在高可用的OLTP数据库中,这一点表现得很明显。
最明显的打破范式的设计方法就是冗余法,以空间换取时间的做法,把数据冗余在多个表中,当查询时可以减少或者是避免表之间的关联。
还是用上面的例子,学生表与课程表,假定课程表要经常被查询,而且在查询中要显示学生的姓名,查询语句则为:
SQL>select code,name,subject from course c,student s where s.id=c.code where code=?
这个语句如果被大范围、高频率执行,可能会因为表关联造成一定程度的影响,现在,假定评估到学生改名的需求是非常少的,那么,就可以把学生姓名冗余到课程表中,又变回了如表1-7所示:
表1-7
ID(PK)
编号
姓名
选修科目
1
001
张三
语文
2
001
张三
数学
3
001
张三
英语
4
002
李四
物理
5
002
李四
化学
6
003
王五
历史
7
003
王五
地理
8
003
王五
生物
注意:我这里并没有省略学生表,不过是把学生姓名冗余在了课程表中,如果万一有很少的改名需求,只要保证在课程表中改名正确即可。
那么,修改以后的语句可以简化为:
SQL>select code,name,subject from course c where code=?
1.5.3  数据库分布技术
除了应用更容易采用分布式技术以外,数据库也是可以采用分布技术的,而且是有必要采用分布式技术的,特别是访问量很高的应用,如果不采用分布技术,再高配置的服务器也可能承载不了业务的需求。
数据库的分布技术,大致可以分为如下几类:
u          主机角度的分布技术
数据库的分布技术,主要是为了解决单个数据库服务器成为瓶颈,而不能承载业务的情况。这时,只要把数据库运行在多个主机上即可,Oracle RAC或者是网格计算就是一种很好的解决方案,它们可以保证在多个主机上运行数据库业务。
另外,读写分离也是一种 有效的解决方案,读写分离就是在一个数据库中写入数据,然后把写入的数据同步到多个站点,在其他的站点上读取数据,这样也可以把应用的负载分布在多个不同 的数据库站点上面。如果写的数据库失败,可以找一个读的数据库来接管,从Oracle技术角度来说,Logical standby、Streams、Oracle 11g的可只读Physical standby都可以实现这样的需求;从非Oracle技术角度来说,复制软件如quest share plex/dsg realsync也可以实现这样的需求。
u          应用角度的分布技术
数据库也可以从应用的角度考虑分布,从应用角度的分布一般有两种解决方案,垂直分割与水平分割技术(见图1-20)。
垂直分割,表示按照应用来分割,如应用1与应用2是可以独立出来的完全不同的应用,则把它独立出来,分割在两个不同的数据库服务器上,这样就实现了垂直分割。这种情况下,如果一个应用故障,就不会影响到其他应用。
水平分割,一般是数据量 的分割,如有一个用户表,可以按照一定规则,把用户表分割成两个表,再分布在两个不同的数据库中,当特定的用户访问数据库的时候,根据规则就可以知道它在 哪个数据库中,然后访问该数据库即可。这种情况下,如果一个库失效,受影响的只是这个库存放的特定的用户。
图1-21  数据库分布技术
如图1-21,拿两个最简单的应用做比较,在垂直分割技术中,论坛与博客是两个不同的数据库,论坛访问论坛的数据库,博客访问博客的数据库。这种情况下,如果论坛坏了,不会影响博客,反之亦然。所以,这种方式,影响的是一个特定的应用。
另外一种分割技术中,把一半用户的论坛与博客数据存放在一个数据库,另外一半存入另外一个数据库,两个库基本一样,都是保 存接近1/2的论坛+博客数据。这种情况下,如果一个库坏了,就有一半的用户不能访问论坛,也不能访问博客,而另外一半用户是正常的。这种分法不一定是分 成2个数据库,也可以水平分割成n个数据库,如果出现问题,影响的是部分的用户群,如1/n的用户。
其实,在很多情况下,也不完全是单纯的垂直分割或者是水平分割,而往往是混合型分割,如先根据业务来垂直分割数据库,如果 单个业务在单个数据库中都不能承载,则可以考虑对这个应用再水平分割,把这个应用分割成多个数据库。然后,再结合RAC技术以及读写分离技术实现数据库的 可扩展性与高可用性。
1.5.4  区别OLTP还是OLAP
在数据库的设计中,就算完全掌握了以上的方法,但是,在不同的数据库类型上,使用起来也是大有差别的,这就需要设计者弄清 楚自己的业务类型。如果没有正确地识别自己的业务类型,就盲目地开始设计数据库,或者是盲目地寻求优化方法,则往往是把OLAP的方法使用在OLTP上, 或者是把OLTP的方法使用在OLAP上。如果这样,很可能会适得其反。
u          什么是OLTP
OLTP,也叫联机事务处理(Online Transaction Processing),表示事务性非常高的系统,一般都是高可用的在线系统,以小的事务以及小的查询为主,评估其系统的时候,一般看其每秒执行的 Transaction以及Execute SQL的数量。在这样的系统中,单个数据库每秒处理的Transaction往往超过几百个,或者是几千个,Select 语句的执行量每秒几千甚至几万个。典型的OLTP系统有电子商务系统、银行、证券等,如美国eBay的业务数据库,就是很典型的OLTP数据库。
注意:如果不特殊指定,本书中的高可用数据库都是指OLTP类型的数据库。
OLTP系统最容易出现瓶颈的地方就是CPU与磁盘子系统。
CPU出现瓶颈常表现在逻辑读总量与计算性函数或者是过程上,逻辑读总量等于单个语句的逻辑读乘以执行次数,如果单个语句 执行速度虽然很快,但是执行次数非常多,那么,也可能会导致很大的逻辑读总量。设计的方法与优化的方法就是减少单个语句的逻辑读,或者是减少它们的执行次 数。另外,一些计算型的函数,如自定义函数、decode等的频繁使用,也会消耗大量的CPU时间,造成系统的负载升高,正确的设计方法或者是优化方法, 需要尽量避免计算过程,如保存计算结果到统计表就是一个好的方法。关于逻辑读与CPU处理能力的详细计算方法,在第12章还有详细介绍。
磁盘子系统在OLTP环境中,它的承载能力一般取决于它的IOPS处理能力,关于IOPS,在第2章中有详细介绍。因为在 OLTP环境中,磁盘物理读一般都是db file sequential read,也就是单块读,但是这个读的次数非常频繁。如果频繁到磁盘子系统都不能承载其IOPS的时候,就会出现大的性能问题。另外,在第2章中,还会详 细介绍存储子系统的IOPS承载力的计算问题。
OLTP比较常用的设计与优化方式为Cache技术与B-tree索引技术,Cache决定了很多语句不需要从磁盘子系统 获得数据,所以,Web cache与Oracle data buffer对OLTP系统是很重要的。另外,在索引使用方面,语句越简单越好,这样执行计划也稳定,而且一定要使用绑定变量,减少语句解析,尽量减少表 关联,尽量减少分布式事务,基本不使用分区技术、MV技术、并行技术及位图索引。因为并发量很高,批量更新时要分批快速提交,以避免阻塞的发生。
u          什么是OLAP
OLAP,也叫联机分析处理(Online Analytical Processing)系统,有的时候也叫DSS决策支持系统,就是我们说的数据仓库。在这样的系统中,语句的执行量不是考核标准,因为一条语句的执行时 间可能会非常长,读取的数据也非常多。所以,在这样的系统中,考核的标准往往是磁盘子系统的吞吐量(带宽),如能达到多少MB/s的流量。
磁盘子系统的吞吐量则往往取决于磁盘的个数,这个时候,Cache基本是没有效果的,数据库的读写类型基本上是db file scattered read与direct path read/write。应尽量采用个数比较多的磁盘以及比较大的带宽,如4Gb的光纤接口。关于磁盘子系统的带宽承载量计算,在第2章也将有详细介绍。
在OLAP系统中,常使用分区技术、并行技术。如分区技术可以使得一些大表的扫描变得很快(只扫描单个分区),而且方便管 理。另外,如果分区结合并行的话,也可以使得整个表的扫描会变得很快。并行技术除了与分区技术结合外,在Oracle 10g中,与RAC结合实现多节点的同时扫描,效果也非常不错,可把一个任务,如select的全表扫描,平均地分派到多个RAC的节点上去。
在OLAP系统中,不需要使用绑定(BIND)变量,因为整个系统的执行量很小,分析时间对于执行时间来说,可以忽略,而 且可避免出现错误的执行计划。但是OLAP中可以大量使用位图索引,物化视图,对于大的事务,尽量寻求速度上的优化,没有必要像OLTP要求快速提交,甚 至要刻意减慢执行的速度。
u          分开设计与优化
以上描述了OLTP与OLAP的特点,在设计上要特别注意,如在高可用的OLTP环境中,不要盲目地把OLAP的技术拿过 来用。如分区技术,假设不是大范围地使用分区关键字,而采用其它的字段作为where条件,那么,如果是本地索引,将不得不扫描多个索引,而性能变得更为 低下。如果是全局索引,又失去分区的意义。
并行技术也是如此,一般在完成大型任务时才使用,如在实际生活中,翻译一本书,可以先安排多个人,每个人翻译不同的章节, 这样可以提高翻译速度。如果只是翻译一页书,也去分配不同的人翻译不同的行,再组合起来,就没必要了,因为在分配工作的时间里,一个人或许早就翻译完了。
位图索引也是一样,如果用在OLTP环境中,很容易造成阻塞与死锁。但是,在OLAP环境中,可能会因为其特有的特性,提 高OLAP的查询速度。MV也是基本一样,包括触发器等,在DML频繁的OLTP系统上,很容易成为瓶颈,甚至是Library Cache等待,而在OLAP环境上,则可能会因为使用恰当而提高查询速度。
1.6.1  美国eBay高可用环境分析
eBay( www.ebay.com)是世界上最大的C2C电子商务网站,到2007年年底,该网站的注册用户数量达到2.48亿,并存有10亿张以上的图片,该网站每天要支撑10亿次的PV,有合计2P(1P=1024T)的数据量,这么庞大的一个系统,还能整体上保证99.94%以上的高可用性。
那么,eBay是怎样保证这么大的系统,还有这么高访问量的高可用呢,这里先看看他们的规模与高可用构架结构(见图1-22)。
l           为了支撑这么多的业务量,有16000台以上的Web服务器,运行J2EE,以及1000台以上的逻辑数据库分布在400个不同的物理主机上,实现了良好的水平扩展性,每天的SQL执行量超过了440亿次。
l           数据库运行在Oracle上,应用服务器采用websphere。每个数据库至少有3个在线的备份,分布在8个不同的数据中心。数据库中没有存储过程,只 有少量的触发器,把很多 CPU密集型的任务从数据库迁移到Web服务器上,因为数据库更容易成为瓶颈,而Web服务器是廉价可扩展的。数据库采用多点复制,可负载均衡,不采用分 布式事务。
l           应用构架方面,采用连接池,应用层采用多层结构。eBay最大的成功在于解决了两个关键性的技术:分布式的数据库与实时的Search。
图1-22  EBay高可用体系结构
可以看到,eBay最核心的技术就是分布,首先Web服务器是分布的,存在16000台以上的Web服务器,相对于比较集中的数据库来说,Web服务器更容易扩展。为了保证数据库的负载最小,eBay采用了很多策略,把负载从数据库转移到可扩展的Web服务器上。
在数据库上面,eBay是先把数据库按照应用在一定程度上垂直分割,然后采用sheard plex水平分割,实现读写分离的分布策略。数据库分布在多个数据中心,应用通过数据层与负载均衡设备访问数据库,根本不必要关心数据库的位置在哪里。
另外,因为数据分布在多个数据中心,而这些数据中心并不在一个地点,也就实现了数据的容灾保护。当写站点发生故障的时候,只要切换到其他一个备用站点即可。
1.6.2  Myspace构架分析
上面分析了eBay的案例,再来看另外一个案例,那就是Myspace( www.myspace.com),从目前的情况来看,Myspace的月访问量超过400亿,并超越了YAHOO。
Myspace采用的是Ms SQL Server,但是,能做到这么好的性能与访问量,是很不容易的,这里分析一下它的高可用发展历程。
u          读写分离分布技术
Myspace最初只有两台老式的Web服务器与一个Dell 双CPU,4GB内存的PC Server数据库服务器,数据库是SQL Server。当2004年Myspace有50万用户的时候,数据库服务器实在不能满足当时的需求,Myspace采用3台SQL Server服务器,一个为主,所有的数据都向它提交,再由它复制到另外两个数据库上,实现了最简单的读写分离。
u          垂直分割技术
在2004中,Myspace用户达到1~2百万,存储成为当时最大的瓶颈,因为IO的响应时间过长,导致用户无法完成自 己的请求。这个时候,Myspace对数据库采用垂直分割的技术,不同的数据库运行不同的业务,存储也采用了SAN存储网络技术,提高了系统的性能与可靠 性。
u          水平分割技术
在用户越来越多的情况下,当用户继续增加到3百万后,采用以上垂直分割的技术也出现了问题,如一项单独的业务在一个数据库 中都可能成为瓶颈。最初为了适应单个业务的增长,Myspace采用了更好的机器,但是,更好的机器并不能解决数据量增长问题,马上又达到新的瓶颈。这个 时候,Myspace采用了分布计算与水平分割技术,按照用户进行水平分割,一个数据库大致保存200万个左右的用户以及对应用户信息。
u          虚拟化技术与Cache技术
水平分割解决了数据库主机的问题,但是,有些特定数据库上的用户可能数据量特别巨大,但是有些数据库上的用户数据量可能又 很小,导致存储设备使用严重不均衡。这个时候,Myspace采用了3PARdata的虚拟化存储技术,让存储设备统一管理,这样整个SAN被当作一个巨 大的存储池,不再要求某个特定的存储设备为特定应用服务。
当用户越来越多,压力越来越大,Myspace启用了新的策略以减轻存储系统的压力,增加数据缓存层——位于Web服务器 和数据库服务器之间,其在内存中建立被频繁访问的数据副本,以前100个用户查询同一数据,需要查询数据库100次,现在只需要1次。如果页面变化,缓存 中的数据将从内存中擦除,然后重新从数据库获取。
现在,Myspace已经超过2600万用户,设备已升级到SQL Server 2005版,因为可以支持64位系统,这样可以支持更大的内存,2006年后每台机器的标准配置是64GB内存。
从以上的情况来看,Myspace采用了高可用环境中的必需的几项技术:
首先就是分布式计算架构,包括应用与数据库,先是简单的读写分离,当读写分离不能适应需要的时候,采用了垂直分割技术,当垂直分割技术也不能适应需要的时候,最后采用了按照用户水平分割的技术。
然后,当存储过于分散的时候,存储系统出现了空间与负载使用的不均衡,他们又采取了虚拟存储的策略,把所有的存储整合成一个存储池,解决了存储设备使用不均匀的问题。
最后,当分布技术与虚拟存储也解决不了问题的时候,Myspace采用了Cache技术,这个才是它最终能承载那么大流量的关键,Cache技术可以快速地响应请求,大大地减少数据库压力。
 
本章的内容比较多,作为全书的一个导读,先介绍了Oracle的体系结构,包括内存结构、后台进程、物理对象与逻辑对象。
然后,介绍了Oracle的最高体系结构(MAA)与Oracle的典型高可用产品,为本书后面进一步介绍这些特性打下基础。
接下来,介绍了设计高可用性数据库的一些要点,如应用设计、网络设计、数据库设计等,并描述了在不同的数据库模式下,不同的设计方法。
最后,通过两个案例描述了典型的高可用设计的方法,读者或设计者可以根据这些设计方法或者是案例,得到一些设计自己所需要的高可用体系的启发。
光看本章,可能得到一些启发性的东西,但是具体的技术实现,则需要通过后面的章节来了解。

你可能感兴趣的:(Oracle数据库)