开发成功的Oracle应用

来源:互联网 发布:macbook使用入门 知乎 编辑:程序博客网 时间:2024/05/01 05:37

针对问题,采用一种以数据库为中心的方法。如果能在数据库中完成,肯定就会在数据库来做,而不是自行实现。

开发数据库软件的哲学:

  • 如果可能,尽量利用一条SQL语句完成工作。
  • 如果无法用一条SQL语句完成,就通过PL/SQL实现(不过,尽可能少用PL/SQL!)。
  • 如果在PL/SQL中也无法做到(因为它缺少一些特性,如列出目录中的文件),可以试试使用Java存储过程来实现。
  • 如果Java还办不到,那就C外部存储过程中实现。如果速度要求很高,或者要使用采用C编写的一个第三方API,就常常使用这种方法。
  • 如果在C外部例程中还无法实现,你就该好好想想有没有必要做这个工作了。

数据库项目失败的最常见的一个原因是对数据库的实际认识不足,缺乏对所用基本工具的了解。黑盒方法是指有意让开发人员对数据库退避三舍,甚至鼓励开发人员根本不用学习数据库!在很多情况下,开发人员没有充分利用数据库。这种方法的出现,原因归结为FUD[恐惧(fear)、不确定(uncertainty)和怀疑(doubt)]。一般都认为数据“很难”,SQL、事务和数据完整性都“很难”。所以“解决方法”就是:不要卷入难题中,要知难而退。他们把数据当成一个黑盒,利用一些软件工具来生成所有代码。他们试图利用重重保护与数据库完全隔离,以避免接触这么“难”的数据库。

书上举了一个例子,Tom参与的一个项目出现了性能问题,他们的系统很多事务串行进行。经过诊断发现是这个表的PROCESSED_FLAG上有一个位图索引,PROCESSED_FLAG列只有两个值:Y和N。对于插入到表中的记录,该列值为N(表示未处理)。其他进程读取和处理这个记录时,就会把该列值从N更新为Y。这些进程要很快地找出PROCESSED_FLAG列值为N的记录,所以开发人员知道,应该对这个列建立索引。他们在别处了解到,位图索引适用于低基数(low-cardinality)列,所谓低基数列就是指这个列只有很少的可取值,所以看上去位图索引是一个很自然的选择。

不过,所有问题的根由正是这个位图索引。采用位图索引,一个键指向多行,可能数以百计甚至更多。如果更新一个位图索引键,那么这个键指向的数百条记录会与你实际更新的那一行一同被有效地锁定。

可以用一个简单的例子说明这种情况:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE t   
  2.   2  (   
  3.   3    Processed_Flag VARCHAR2(1)   
  4.   4  );   
  5.   
  6. 表已创建。   
  7.   
  8. MUZIYU@MYDB> CREATE BITMAP INDEX t_Idx ON t ( Processed_Flag );   
  9.   
  10. 索引已创建。   
  11.   
  12. MUZIYU@MYDB> INSERT INTO t VALUES ( 'N' );   
  13.   
  14. 已创建 1 行。  

现在,如果我们在另外一个SQL*Plus会话中执行以下命令:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> INSERT INTO t VALUES ( 'N' );  

这条语句就会“挂起”,直到第一个阻塞会话中发出COMMIT为止。

这个问题就是缺乏足够的了解造成的。由于不了解数据库特性(位图索引),不清楚它做些什么以及做么做,就导致这个数据库从一开始可扩缩性就很差。一旦找到问题,修正起来就很容易了,这里需要一个传统的B*Tree索引。

这个项目的问题大致如此,所以我们需要解决以下方面的问题:

  • 如何对SQL调优而不修改SQL。
  • 如何测量性能。
  • 如何查看哪里出现了瓶颈。
  • 如何建立索引,对什么建立索引。
  • 如此等等。

软件环境:Windows XP SP3+Oracle 9i R2,[ORACLE_HOME]=C:/Oracle/Ora92

 

 

1.3.1 了解Oracle体系结构

1. 在Oracle中使用一个连接

在Oracle中,不论你想执行多少个查询,都希望最多打开一个连接。如果一个应用打开过多的连接,这就意味着,相对于服务器原本能支持的并发用户数,现在服务器只能支持1/n的并发用户数。

2. 使用绑定变量

这是导致性能问题的一个主要原因,也是阻碍可扩缩性的一个重要因数。Oracle将已解析、已编译的SQL连同其他内容存储在共享池(shared pool)中,这是系统全局区(System Global Area,SGA)中一个非常重要的共享内存结构。

绑定变量(bind variable)是查询中的一个占位符。例如,要获取员工123的相应记录,可以使用以下查询:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SELECT * FROM Emp e WHERE e.EmpNo = 123;  

或者,也可以将绑定变量:EmpNo设置为123,并执行以下查询:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SELECT * FROM Emp e WHERE e.EmpNo = :EmpNo;  

在典型的系统中,你可能只查询一次员工123,然后不再查询这个员工。之后,你可能会查询员工456,然后是员工789,如此等等。如果在查询中使用直接量(常量),那么每个查询都将是一个全新的查询,在数据库看来从前从未见过,必须对查询进行解析、限定(命名解析)、安全性检查、优化等。简单地讲,就是你执行的每条不同的语句都要在执行时进行编译。

第二个查询使用了一个绑定变量:EmpNO,变量值在查询执行时提供。这个查询只编译一次,随后会把查询计划存储在一个共享池(库缓存)中,以便以后获取和重用这个查询计划。以上两个查询在性能和可扩缩性方面有很大差别,甚至可以说有天壤之别。

从前面的描述应该能清楚地看到,与重用已解析的查询计划(称为软解析,soft parse)相比,解析包含有硬编码变量的语句(称为硬解析,hard parse)需要的时间更长,而且要消耗更多的资源。硬解析会减少系统能支持的用户数,但程度如何不太明显。这部分取决于多耗费了多少资源,但更重要的因数是库缓存所用的闩定(latching)机制。硬解析一个查询时,数据库会更长时间地占用一种低级串行化设备,这称为(latch)。这些闩能保护Oracle共享内存中的数据结构不会同时被两个进程修改(否则,Oracle最后会得到遭受破坏的数据结构),而且如果有人正在修改数据结构,则不允许另外的人来读取。对这些数据结构加闩的时间越长、越频繁,排队等待闩的进程就越多,等待队列也越长。

如果使用绑定变量,无论是谁,只要提交引用同一个对象的查询的同一查询,都会使用共享池中已编译的查询计划。这样你的子例程只编译一次就可以重复使用。这样做效率很高,这也正是数据库期望你采用的做法。你使用的资源会更少(软解析耗费的资源相当少),不仅如此,占用闩的时间也更短,而且不再那么频繁地需要闩。这些都会改善应用的性能和可扩缩性。

下面做一个非常小的测试来看看绑定变量在性能方面会带来多大的差别:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE t ( x INT );   
  2.   
  3. 表已创建。   
  4.   
  5. MUZIYU@MYDB> CREATE OR REPLACE PROCEDURE proc1   
  6.   2  AS  
  7.   3  BEGIN  
  8.   4        FOR i IN 1 .. 10000   
  9.   5        LOOP   
  10.   6              EXECUTE IMMEDIATE   
  11.   7               'INSERT INTO t VALUES ( :x )'  
  12.   8               USING i;   
  13.   9         END LOOP;   
  14.  10  END;   
  15.  11  /   
  16.   
  17. 过程已创建。   
  18.   
  19. MUZIYU@MYDB> CREATE OR REPLACE PROCEDURE proc2   
  20.   2  AS  
  21.   3  BEGIN  
  22.   4        FOR i IN 1 .. 10000   
  23.   5        LOOP   
  24.   6              EXECUTE IMMEDIATE   
  25.   7              'INSERT INTO t VALUES ( ' || i || ' )';   
  26.   8        END LOOP;   
  27.   9  END;   
  28.  10  /   
  29.   
  30. 过程已创建。   
  31.   
  32. MUZIYU@MYDB> EXEC RunStats_Pkg.Rs_Start;   
  33.   
  34. PL/SQL 过程已成功完成。   
  35.   
  36. MUZIYU@MYDB> EXEC proc1;   
  37.   
  38. PL/SQL 过程已成功完成。   
  39.   
  40. MUZIYU@MYDB> EXEC RunStats_Pkg.Rs_Middle;   
  41.   
  42. PL/SQL 过程已成功完成。   
  43.   
  44. MUZIYU@MYDB> EXEC proc2;   
  45.   
  46. PL/SQL 过程已成功完成。   
  47.   
  48. MUZIYU@MYDB> EXEC RunStats_Pkg.Rs_Stop(1000);   
  49. Run1 ran in 2555 hsecs   
  50. Run2 ran in 3024 hsecs   
  51. Run1 ran in 84.49% of the time  
  52.            
  53. Name                              Run1        Run2        Diff   
  54. STAT...recursive calls             10,340      13,411       3,071   
  55. STAT...parse count (hard)               5      10,024      10,019   
  56. LATCH.library cache pin            80,547      96,347      15,800   
  57. LATCH.library cache pin alloca     40,406      63,112      22,706   
  58. LATCH.session allocation              101      31,987      31,886   
  59. LATCH.row cache enqueue latch         262      41,692      41,430   
  60. LATCH.row cache objects               286      41,875      41,589   
  61. STAT...session uga memory               0      65,464      65,464   
  62. STAT...session uga memory max           0      65,464      65,464   
  63. STAT...session pga memory               0      65,536      65,536   
  64. LATCH.cache buffers chains        187,559     121,821     -65,738   
  65. LATCH.child cursor hash table          45      71,011      70,966   
  66. LATCH.library cache               111,163     239,983     128,820   
  67. LATCH.shared pool                  50,936     265,538     214,602   
  68.            
  69. Run1 lathces total versus runs -- difference and pct   
  70. Run1        Run2        Diff        Pct   
  71. 487,600     988,951     501,351     49.30%   
  72.   
  73. PL/SQL 过程已成功完成。  

结果清楚地显示,从墙上时钟来看,proc2(没有使用绑定参数)插入10000条记录的时间比proc1(使用参数绑定变量)多,最重要的是proc2使用的闩是proc1使用的闩的一倍。

1.3.2 理解并发控制

如果对你的特定数据库如何实现并发控制了解不够,就会遭遇以下结果:

  • 破坏数据的完整性。
  • 随着用户数的增加,应用的运行速度减慢。
  • 不能很好地扩缩应用来支持大量用户。

1. 实现锁定

数据使用(lock)来保证任何给定时刻最多只有一个事务在修改给定的一段数据。实质上讲,正是锁定机制才使并发控制成为可能。不过,如果滥用或者使用不当,锁反倒会阻碍并发行。因此,要理解什么是锁定,你的数据库锁定是怎样工作的,这对于开发可扩缩的、正确的应用至关重要。

Oracle锁定策略:

  • Oracle只在修改时才对数据加行级锁。
  • 如果只是读数据,Oracle绝不会对数据锁定。
  • 写入器(writer)不会阻塞读取器(reader)。
  • 写入器想写某行数据,但另一个写入器已经锁定了这行数据,此时该写入器才会被阻塞。

2. 防止丢失更新

Oracle的无阻塞方法有一个副作用,如果确实想保证一次最多只有一个用户访问一行数据,开发人员就得自己做些工作。

下面这个资源调度程序为例,要实现在给定的任何时间段都不能将一种资源分配给多个人:

view plaincopy to clipboardprint?
  1. Muziyu@MyDB> CREATE TABLE Resources   
  2.   2 (   
  3.   3   Resource_Name VARCHAR2(25) PRIMARY KEY,   
  4.   4   ...   
  5.   5 );   
  6.   
  7. Muziyu@MyDB> CREATE TABLE Schedules   
  8.   2 (   
  9.   3   Resource_Name REFERENCES Resources,   
  10.   4   Start_Time DATE NOT NULL,   
  11.   5   End_Time DATE NOT NULL,   
  12.   6   Check(Start_Time < End_Time),   
  13.   7   PRIMARY KEY(Resource_Name, Start_Time)   
  14.   8 );  

在分配资源之前,应用将查询:

view plaincopy to clipboardprint?
  1. Muziyu@MyDB> SELECT COUNT(*)   
  2.   2    FROM Schedules   
  3.   3   WHERE Resource_Name := :Room_Name   
  4.   4     AND Start_Time <= :New_End_Time   
  5.   5     AND End_Time >= :New_End_Time;  

看上去,这个应用是没问题。实际上,如果有2个用户在同一时间,预定同一时间段的资源的话,就会出现2个用户都预定成功,但这肯定是有问题的,部分原因就在于Oracle的非阻塞读。所以,开发人员的解决方案就是加入串行化机制:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SELECT *    
  2.   2    FROM Resources    
  3.   3   WHERE Resource_Name := :Room_Name FOR UPDATE;  

建议把逻辑实现为事务API:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE OR REPLACE Schedule_Resource   
  2.   2  (   
  3.   3    p_Resource_Name IN VARCHAR2,   
  4.   4    p_Start_Time IN DATE,   
  5.   5    p_End_Time IN DATE  
  6.   6  )   
  7.   7  AS  
  8.   8    l_Resource_Name Resource.Resource_Name%TYPE;   
  9.   9    l_Cnt NUMBER;   
  10.  10  BEGIN  

首先在Resources表中锁定我们想调度的那个资源的相应行。如果别人已经锁定了这一行,我们就会阻塞并等待:

view plaincopy to clipboardprint?
  1. 11    SELECT Resource_Name   
  2. 12      INTO l_Resource_Name   
  3. 13      FROM Resources   
  4. 14     WHERE Resource_Name = p_Resource_Name   
  5. 15       FOR UPDATE;  

既然我们已经有了锁,那么只有我们能在这个Schedules表中插入对应此资源名的调度,所以如下查看这个表是安全的:

view plaincopy to clipboardprint?
  1. 16    SELECT COUNT(*)   
  2. 17      INTO l_Cnt   
  3. 18      FROM Schedules   
  4. 19     WHERE Resource_Name = p_Resource_Name   
  5. 20       AND Start_Time <= p_End_Time   
  6. 21       AND End_Time >= p_Start_Time;   
  7. 22    IF l_Cnt <> 0   
  8. 23    THEN  
  9. 24      RAISE_APPLICATION_ERROR(-20001, 'Room is already booked!');   
  10. 25    END IF;  

如果能运行到这里而没有发生错误,就可以安全地在Schedules表中插入预定资源的相应记录行,而不用担心出现重叠:

view plaincopy to clipboardprint?
  1. 26    INSERT INTO Schedules   
  2. 27    VALUES(p_Resource_Name, p_Start_Time, p_End_Time);   
  3. 28  END Schedule_Resource;  

这个解决方案是高度并发的。

注:以上代码有一个前提,就是假设事务隔离级别是READCOMMITED。如果事务隔离级别是SERIALIZABLE,这个逻辑将无法正常工作。

1.3.3 多版本

多版本(multi-versioning)实质上指Oracle能够从数据库同时物化多个版本的数据。

这个主题与并发控制的关系非常紧密,因为这正是Oracle并发控制机制的基础。实质上讲,Oracle利用这种机制提供了一下特性:

  • 读一致查询:对于一个时间点(point in time),查询会产生一致的结果。
  • 非阻塞读查询:查询不会被写入器阻塞,但在其他数据库中可能不是这样。

下面用一个方法来演示Oracle中的多版本:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE t   
  2.   2  AS  
  3.   3    SELECT *   
  4.   4      FROM All_Users;   
  5.   
  6. 表已创建。   
  7.   
  8. MUZIYU@MYDB> VARIABLE x RefCursor   
  9. MUZIYU@MYDB> BEGIN  
  10.   2    OPEN :x FOR SELECT * FROM t;   
  11.   3  END;   
  12.   4  /   
  13.   
  14. PL/SQL 过程已成功完成。   
  15.   
  16. MUZIYU@MYDB> DELETE FROM t;   
  17.   
  18. 已删除32行。   
  19.   
  20. MUZIYU@MYDB> COMMIT;   
  21.   
  22. 提交完成。   
  23.   
  24. MUZIYU@MYDB> PRINT x;   
  25.   
  26. USERNAME                          USER_ID CREATED   
  27. ------------------------------ ---------- ----------   
  28. SYS                                     0 12-5月 -02   
  29. SYSTEM                                  5 12-5月 -02   
  30. OUTLN                                  11 12-5月 -02   
  31. DBSNMP                                 19 12-5月 -02   
  32. WMSYS                                  21 12-5月 -02   
  33. ORDSYS                                 30 12-5月 -02   
  34. ORDPLUGINS                             31 12-5月 -02   
  35. MDSYS                                  32 12-5月 -02   
  36. CTXSYS                                 33 12-5月 -02   
  37. XDB                                    35 12-5月 -02   
  38. ANONYMOUS                              36 12-5月 -02   
  39. WKSYS                                  39 12-5月 -02   
  40. WKPROXY                                40 12-5月 -02   
  41. ODM                                    42 12-5月 -02   
  42. ODM_MTR                                43 12-5月 -02   
  43. OLAPSYS                                44 12-5月 -02   
  44. RMAN                                   60 12-5月 -02   
  45. HR                                     46 12-5月 -02   
  46. OE                                     47 12-5月 -02   
  47. PM                                     48 12-5月 -02   
  48. SH                                     49 12-5月 -02   
  49. QS_ADM                                 51 12-5月 -02   
  50. QS                                     52 12-5月 -02   
  51. QS_WS                                  53 12-5月 -02   
  52. QS_ES                                  54 12-5月 -02   
  53. QS_OS                                  55 12-5月 -02   
  54. QS_CBADM                               56 12-5月 -02   
  55. QS_CB                                  57 12-5月 -02   
  56. QS_CS                                  58 12-5月 -02   
  57. SCOTT                                  59 12-5月 -02   
  58. MUZIYU                                 62 29-8月 -07   
  59. PERFSTAT                               66 17-9月 -07   
  60.   
  61. 已选择32行。  

在上面的例子中,创建了一个测试表T,并把All_Users表的一些数据加载到这个表中。然后在这个表上打开一个游标,再从该表删除数据,甚至用COMMIT提交了删除所做的工作,但还是可以通过游标获取到数据。因为DLETE命令为我们把数据保留下来,把它放在一个称为undo段(undo segment)的数据区,这个数据区也称为回滚段(rollback segment)。

注意:DELETE和DROP都会把数据保留到回滚段里,但TRUNCATE不会。

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE t   
  2.   2  AS  
  3.   3    SELECT *   
  4.   4      FROM All_Users;   
  5.   
  6. 表已创建。   
  7.   
  8. MUZIYU@MYDB> VARIABLE x REFCURSOR;   
  9. MUZIYU@MYDB> BEGIN  
  10.   2    OPEN :x FOR SELECT * FROM t;   
  11.   3  END;   
  12.   4  /   
  13.   
  14. PL/SQL 过程已成功完成。   
  15.   
  16. MUZIYU@MYDB> DROP TABLE t;   
  17.   
  18. 表已丢弃。   
  19.   
  20. MUZIYU@MYDB> COMMIT;   
  21.   
  22. 提交完成。   
  23.   
  24. MUZIYU@MYDB> PRINT x;   
  25.   
  26. USERNAME                             USER_ID CREATED   
  27. ------------------------------ ------------- --------------   
  28. SYS                                        0 12-5月 -02   
  29. SYSTEM                                     5 12-5月 -02   
  30. OUTLN                                     11 12-5月 -02   
  31. DBSNMP                                    19 12-5月 -02   
  32. WMSYS                                     21 12-5月 -02   
  33. ORDSYS                                    30 12-5月 -02   
  34. ORDPLUGINS                                31 12-5月 -02   
  35. MDSYS                                     32 12-5月 -02   
  36. CTXSYS                                    33 12-5月 -02   
  37. XDB                                       35 12-5月 -02   
  38. ANONYMOUS                                 36 12-5月 -02   
  39. WKSYS                                     39 12-5月 -02   
  40. WKPROXY                                   40 12-5月 -02   
  41. ODM                                       42 12-5月 -02   
  42. ODM_MTR                                   43 12-5月 -02   
  43. OLAPSYS                                   44 12-5月 -02   
  44. RMAN                                      60 12-5月 -02   
  45. HR                                        46 12-5月 -02   
  46. OE                                        47 12-5月 -02   
  47. PM                                        48 12-5月 -02   
  48. SH                                        49 12-5月 -02   
  49. QS_ADM                                    51 12-5月 -02   
  50. QS                                        52 12-5月 -02   
  51. QS_WS                                     53 12-5月 -02   
  52. QS_ES                                     54 12-5月 -02   
  53. QS_OS                                     55 12-5月 -02   
  54. QS_CBADM                                  56 12-5月 -02   
  55. QS_CB                                     57 12-5月 -02   
  56. QS_CS                                     58 12-5月 -02   
  57. SCOTT                                     59 12-5月 -02   
  58. MUZIYU                                    62 29-8月 -07   
  59. PERFSTAT                                  66 17-9月 -07   
  60.   
  61. 已选择32行。  
view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE t   
  2.   2  AS  
  3.   3    SELECT *   
  4.   4      FROM All_Users;   
  5.   
  6. 表已创建。   
  7.   
  8. MUZIYU@MYDB> VARIABLE x REFCURSOR;   
  9. MUZIYU@MYDB> BEGIN  
  10.   2    OPEN :x FOR SELECT * FROM t;   
  11.   3  END;   
  12.   4  /   
  13.   
  14. PL/SQL 过程已成功完成。   
  15.   
  16. MUZIYU@MYDB> TRUNCATE TABLE t;   
  17.   
  18. 表已截掉。   
  19.   
  20. MUZIYU@MYDB> PRINT x;   
  21. ERROR:   
  22. ORA-08103: 对象不再存在   
  23.   
  24.   
  25.   
  26. 未选定行  

1.多版本和闪回

过去,Oracle总是基于查询的某个时间点来做决定(从这个时间点开始查询是一致的)。也就是说,Oracle会保证打开的结果集肯定是以下两个时间点之一的当前结果集:

  • 游标打开时的时间点。这是READ COMMITTED隔离模式的默认行为,该模式是默认的事务模式。
  • 查询所属事务开始的时间点。这是READ ONLY和SERIALIZABLE隔离级别的默认行为。

不过,从Oracle9i开始,提供一种称为闪回查询(flashback query)的特性。

下面举一个例子:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> VARIABLE SCN NUMBER;   
  2. MUZIYU@MYDB> EXEC :SCN := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER;   
  3.   
  4. PL/SQL 过程已成功完成。   
  5.   
  6. MUZIYU@MYDB> PRINT SCN;   
  7.   
  8.    SCN   
  9. ----------   
  10. 1880470   
  11.   
  12. MUZIYU@MYDB> SELECT COUNT(*) FROM t1;   
  13.   
  14. COUNT(*)   
  15. ----------   
  16. 1000000   
  17.   
  18. MUZIYU@MYDB> DELETE FROM t1;   
  19.   
  20. 已删除1000000行。   
  21.   
  22. MUZIYU@MYDB> SELECT COUNT(*) FROM t1;   
  23.   
  24. COUNT(*)   
  25. ----------   
  26.       0   
  27.   
  28. MUZIYU@MYDB> SELECT COUNT(*) FROM t1 AS OF SCN :SCN;   
  29.   
  30. COUNT(*)   
  31. ----------   
  32.   1000000   
  33.   
  34. MUZIYU@MYDB> COMMIT;   
  35.   
  36. 提交完成。   
  37.   
  38. MUZIYU@MYDB> SELECT *   
  39.   2     FROM (SELECT COUNT(*) FROM T1),   
  40.   3                  (SELECT COUNT(*) FROM T1 AS OF SCN :SCN);   
  41.   
  42. COUNT(*)        COUNT(*)   
  43. --------------- ---------------   
  44.        0        1000000  

注:SCN系统修改号(System Change Number)或系统提交号(System Commit Number)。

如果你使用的是Oracle 10g或者以上版本,就有一个“闪回”命令,它使用了这种底层多版本技术,可以把对象返回到以前某个时间点的状态。因为我用的是Oracle 9i R2,这里就不做测试了。

2. 读一致性和非阻塞读

下面用一个例子来解释多版本、读一致查询以及非阻塞读的含义(这里假设我们读取的表在每个数据库块中只存放一行,而且下面的例子要全面扫描这个表):

我们查询的表是一个简单的Accounts表。其中包含了一家银行的帐户余额。其结构很简单:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE Accounts   
  2.   2  (   
  3.   3    Account_Number NUMBER PRIMARY KEY,   
  4.   4    Account_Balance NUMBER   
  5.   5  );   
  6.   
  7. 表已创建。  

在实际中,Accounts表中可能有上百万行记录,但未了力求简单,这里只考虑一个仅有4行的表,如下表所示:

Accounts表的内容账户账户余额1123$500.002234$250.003345$400.004456$100.00

我们可能想运行一下日报表,了解银行里有多少钱。查询如下:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SELECT SUM(a.Account_Balance)    
  2.   2  FROM Accounts a;   
  3.   
  4. SUM(A.ACCOUNT_BALANCE)   
  5. ----------------------   
  6.                   1250  

答案很明显:$1250.00。不过,如果我们现在读了第1行,准备读第2行和第3行时,一个自动柜员机(ATM)针对这个表发生了一个事务,将$400.00从账户123转到账户456,又会怎么样呢?查询会计算出第4行的余额为$500.00,最后的得到了$1650.00,是这样吗?当然,应该避免这种情况,因为这是不对的,任何时刻帐户余额列中的实际总额都不是这个数。读一致性就是Oracle为避免发生这种情况所采用的方法。

时间查询转账事务T1读第1行:到目前为止sum=$500 T2 

更新第1行:对第1行加一个排他锁(exclusive lock),阻止其他更新。第1行现在有$100

T3读第2行:到目前为止sum=$750 T4读第3行:到目前为止sum=$1150 T5 更新第4行:对第4行加一个排他锁,阻止其他更新。第4行现在有$500T6读第4行:发现第4行已修改。这会将块回滚到T1时刻的状态。查询从这个块读到值$100 T7得到答案$1250 

在T6时,Oracle有效地“摆脱”了事务加在第4行上的锁。非阻塞读是这样实现的:Oracle只看数据是否改变,它并不关心数据当前是否锁定。Oracle只是从回滚段中取回来的值,并继续处理下一个数据块。

下一个例子也能很好地展示多版本。在数据库中,可以得到同一个信息处于不同时间点的多个版本。Oracle能充分使用不同时间点的数据快照来提供读一致查询和非阻塞读。

数据的读一致视图总是在SQL语句级执行。SQL语句的结果对于查询开始的时间点来说是一致的。

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE t   
  2.   2  AS  
  3.   3    SELECT * FROM All_Users WHERE ROWNUM <= 5;   
  4.   
  5. 表已创建。   
  6.   
  7. MUZIYU@MYDB> SELECT * FROM t;   
  8.   
  9. USERNAME                          USER_ID CREATED   
  10. ------------------------------ ---------- ----------   
  11. SYS                                     0 12-5月 -02   
  12. SYSTEM                                  5 12-5月 -02   
  13. OUTLN                                  11 12-5月 -02   
  14. DBSNMP                                 19 12-5月 -02   
  15. WMSYS                                  21 12-5月 -02   
  16.   
  17. MUZIYU@MYDB> BEGIN  
  18.   2    FOR x IN (SELECT * FROM t)   
  19.   3    LOOP   
  20.   4      INSERT INTO t VALUES(x.UserName, x.User_ID, x.Created);   
  21.   5    END LOOP;   
  22.   6  END;   
  23.   7  /   
  24.   
  25. PL/SQL 过程已成功完成。   
  26.   
  27. MUZIYU@MYDB> SELECT * FROM t;   
  28.   
  29. USERNAME                          USER_ID CREATED   
  30. ------------------------------ ---------- ----------   
  31. SYS                                     0 12-5月 -02   
  32. SYSTEM                                  5 12-5月 -02   
  33. OUTLN                                  11 12-5月 -02   
  34. DBSNMP                                 19 12-5月 -02   
  35. WMSYS                                  21 12-5月 -02   
  36. SYS                                     0 12-5月 -02   
  37. SYSTEM                                  5 12-5月 -02   
  38. OUTLN                                  11 12-5月 -02   
  39. DBSNMP                                 19 12-5月 -02   
  40. WMSYS                                  21 12-5月 -02   
  41.   
  42. 已选择10行。  

SELECT * FROM t 的结果在查询开始执行就已经确定了。这个SELECT并不看INSERT生成的任何新数据。倘如镇的能看到新插入的数据,这条语句就会陷入无限循环。

1.3.4 数据库独立性?

要构建一个完全数据库独立的应用,而且是高度可扩缩的应用,是极其困难的。实际上,除非你真正了解每个数据库具体如何工作。如果你清楚每个数据库工作的具体细节,就会知道,数据库独立性可能并不是你真正想要的。

1. 标准的影响

SQL92标准有4个层次:

  • 入门级(Entry level)
  • 过渡级
  • 中间级
  • 完备级

SQL99只定义了两级一致性:核心一致性(core)和增强(enhanced)一致性。

2. 防御式编程(defensive programming)

防御式编程的目标是充分利用可用的工具,但是确保能够根据具体情况逐一修改实现。

例如,许多数据库应用都有一个功能,即为每一行生成一个唯一的键。插入时,系统应自动生成一个键。为此,每个数据库都提供了一个解决方法。所以,开发人员有两条路可以走:

  1. 开发一个完全独立于数据库的方法来实现生成唯一的键。
  2. 在各个数据库中实现键时,提供不同的方法,并使用不同的技术。

从理论上讲,第一种方法的好处是从一个数据库转向另外一个数据库时无需执行任何修改。

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE ID_Table   
  2.   2  (   
  3.   3    ID_Name VARCHAR2(30) PRIMARY KEY,   
  4.   4    ID_Value NUMBER   
  5.   5  );   
  6.   
  7. 表已创建。   
  8.   
  9. MUZIYU@MYDB> INSERT INTO ID_Table VALUES('MY_KEY', 0);   
  10.   
  11. 已创建 1 行。   
  12.   
  13. MUZIYU@MYDB> COMMIT;   
  14.   
  15. 提交完成。   
  16.   
  17. MUZIYU@MYDB> UPDATE ID_Table   
  18.   2     SET ID_Value = ID_Value + 1   
  19.   3   WHERE ID_Name = 'MY_KEY';   
  20.   
  21. 已更新 1 行。   
  22.   
  23. MUZIYU@MYDB> SELECT ID_Value   
  24.   2    FROM ID_Table   
  25.   3   WHERE ID_Name = 'MY_KEY';   
  26.   
  27.   ID_VALUE   
  28. ----------   
  29.          1  

看上去很简单,但是有以下结果:

  • 一次只能有一个用户处理事务行。
  • 在Oracle中,倘如隔离级别为SERIALIZABLE,除第一个用户外,试图并发完成此操作的其他用户都会接到这样一个错误:“ORA-08177: can't seialize access for this transaction”(ORA-08177:无法串行访问这个事务)。

eg.

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;   
  2.   
  3. 事务处理集。   
  4.   
  5. MUZIYU@MYDB> UPDATE ID_Table   
  6.   2     SET ID_Value = ID_Value + 1   
  7.   3   WHERE ID_Name = 'MY_KEY';   
  8.   
  9. 已更新 1 行。   
  10.   
  11. MUZIYU@MYDB> SELECT ID_Value   
  12.   2    FROM ID_Table   
  13.   3   WHERE ID_Name = 'MY_KEY';   
  14.   
  15.   ID_VALUE   
  16. ----------   
  17.          1  

下面,再到另外一个SQL*Plus会话完成同样的操作,并发地请求唯一的ID:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;   
  2.   
  3. 事务处理集。   
  4.   
  5. MUZIYU@MYDB> UPDATE ID_Table   
  6.   2     SET ID_Value = ID_Value + 1   
  7.   3   WHERE ID_Name = 'MY_KEY';  

此时它会阻塞,因为一次只有一个事务可以更新这一行。

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> COMMIT;   
  2.   
  3. 提交完成。  

第二个会话立即显示以下错误:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> UPDATE ID_Table   
  2.   2     SET ID_Value = ID_Value + 1   
  3.   3   WHERE ID_Name = 'MY_KEY';   
  4. UPDATE ID_Table   
  5.        *   
  6. ERROR 位于第 1 行:   
  7. ORA-08177: 无法连续访问此事务处理  

所以,尽管这个逻辑原本想做到独立于数据库,但它根本不是数据库独立的。取决于隔离级别,这个逻辑甚至在单个数据库中都无法可靠地完成,更不用说跨数据库了!

对于这个问题,正确的解决方法是针对各个数据库使用最合适的代码。例如,在Oracle中,可以用下面的方法:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE TABLE t (PK NUMBER PRIMARY KEY);   
  2.   
  3. 表已创建。   
  4.   
  5. MUZIYU@MYDB> CREATE SEQUENCE t_Seq;   
  6.   
  7. 序列已创建。   
  8.   
  9. MUZIYU@MYDB> CREATE TRIGGER t_Trigger BEFORE INSERT ON t FOR EACH ROW   
  10.   2  BEGIN  
  11.   3    SELECT t_Seq.NEXTVAL INTO :New.PK FROM DUAL;   
  12.   4  END;   
  13.   5  /   
  14.   
  15. 触发器已创建  

其效果是为插入的每一行自动地指定一个唯一键。还有一种性能更优的方法:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> INSERT INTO t VALUES(t_Seq.NEXTVAL);   
  2.   
  3. 已创建 1 行。  

3. 特性和功能

不必努力争取数据库独立性,这还有一个很自然的理由:你应当准确地知道特定数据库必须提供什么,并充分加以利用。

4. 简单地解决问题

下面用一个比喻来说明为什么要简单地解决问题。如果你在建一个狗窝,就不会用到重型机器。你只需要几样小工具就行了,大玩意是用不上的。另一方面,如果你在建一套公寓楼,就要下大功夫,可能要用到大型机器。与建狗窝相比,解决这个问题所用的工具完全不同。应用开发也是如此。

例如,怎么确保最终用户在数据库中只有一个会话?可能你的解决方案是建立一个由操作系统运行的批作业,这个批作业将查看V$Session表:如果有多个会话,就坚决地关闭这些会话。又或者可能创建自己的表,用户登录时由应用在这个表中插入一行,用户注销时删除相应行。不过下面的方法更为简单:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> CREATE PROFILE One_Session LIMIT SESSIONS_PER_USER 1;   
  2.   
  3. 配置文件已创建   
  4.   
  5. MUZIYU@MYDB> ALTER USER Scott PROFILE One_Session;   
  6.   
  7. 用户已更改。   
  8.   
  9. MUZIYU@MYDB> ALTER SYSTEM SET Resource_Limit = TRUE;   
  10.   
  11. 系统已更改。   
  12.   
  13. MUZIYU@MYDB> CONNECT Scott/tiger;   
  14. 已连接。   
  15. MUZIYU@MYDB> HOST SQLPLUS Scott/tiger;   
  16.   
  17. SQL*Plus: Release 9.2.0.1.0 - Production on 星期五 9月 28 20:13:36 2007   
  18.   
  19. Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.   
  20.   
  21. ERROR:   
  22. ORA-02391: exceeded simultaneous SESSIONS_PER_USER limit   
  23.   
  24.   
  25. 请输入用户名:  

5. 开放性

过分的追求开放性是不可取的。不管你使用什么数据库,都应该充分加以利用,把它的每一个功能都“挤出来”。

1.3.5 “怎么能让应用运行得更快?”

如果你想让应用快起来,最好的方法是:在整个开发阶段,你都要把性能作为一个目标精心设计,合理地构建,并且不断地测试。绝对不能依靠数据库提供的一些“开关”,让你的应用运行得更快。数据库提供的这些开关或许能使你的应用运行得更快,但是,它也可能带来一些副作用。

eg.Oracle8.1.6增加的一个新参数CURSOR_SHARING=FORCE:

不知道为什么我做的试验和书上说的不一样,谁知道的给点建议,不胜感激!

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> ALTER SESSION SET CURSOR_SHARING = EXACT;   
  2.   
  3. 会话已更改。   
  4.   
  5.     MUZIYU@MYDB> SELECT /* TAG */ SUBSTR(UserName, 1, 1)   
  6.   2    FROM All_Users   
  7.   3   WHERE ROWNUM = 1;   
  8.   
  9. SU   
  10. --   
  11. S   
  12.   
  13. MUZIYU@MYDB> ALTER SESSION SET CURSOR_SHARING = FORCE;   
  14.   
  15. 会话已更改。   
  16.   
  17. MUZIYU@MYDB> SELECT /* TAG */ SUBSTR(UserName, 1, 1)   
  18.   2    FROM All_Users   
  19.   3   WHERE ROWNUM = 1;   
  20.   
  21. SU   
  22. --   
  23. S   
  24.   
  25. MUZIYU@MYDB> ALTER SESSION SET CURSOR_SHARING = SIMILAR;   
  26.   
  27. 会话已更改。   
  28.   
  29.     MUZIYU@MYDB> SELECT /* TAG */ SUBSTR(UserName, 1, 1)   
  30.   2    FROM All_Users   
  31.   3   WHERE ROWNUM = 1;   
  32.   
  33. SU   
  34. --   
  35. S  

1.3.6 DBA与开发人员的关系

实质上讲,开发人员不必知道如何运行数据库,他们只需知道如何在数据库运行。开发人员和DBA要协同解决问题,但各有分工。

软件环境:Windows XP SP3+Oracle 9i R2,[ORACLE_HOME]=C:/Oracle/Ora92

 

如果你要用Oracle开发,应该做到:

  • 需要理解Oracle体系结构。
  • 需要理解锁定和并发控制特性,而且知道每个数据库都以不同的方式实现这些特性。
  • 不要把数据库当作黑盒,也就是说,不要以为无需了解数据库。
  • 用尽可能简单的方法解决问题,要尽量使用Oracle提供的内置功能。
  • 软件项目、编程语言以及框架总是如走马灯似地在变。
  • 还是上面这一条,但数据时永远存在的。

在Oracle领域中有两个词很容易混淆,这就是“实例”(instance)和“数据库”(database)。作为Oracle术语,这两个词的定义如下:

  • 数据库(database):物理操作系统文件和磁盘(disk)的集合。
  • 实例(instance) :一组Oracle后台进程/线程以及一个共享内存区,这些内存由同一个计算机上运行的线程/进程所共享。

实例和数据库之间的关系是:数据库可以由多个实例装载和打开,而实例可以在任何时间点装载和打开一个数据库。准确地讲,实例在整个生存期中最多能装载和打开一个数据库。

实例就是一组操作系统进程(或者是一个多线程的进程)以及一些内存。这些进程可以操作数据库;而数据库只是一个文件集合(包括数据库文件、临时文件、重做日志文件和控制文件)。在任何时刻,一个实例实例只能由一组相关的文件(和一个数据库关联)。大多数情况下,反过来也成立:一个数据库上只有一个实例对其进行操作。不过,Oracle 的真正应用集群(Real Application Clusters,RAC)是一个例外。

等过几天把VMWare + Linux + Oracle 10g R2环境配置好了,再把例子补上!

 

 

Oralce实例和数据库的抽象图:

Figure 2-1. Oracle instance and database

图2-1 Oracle实例和数据库

Oracle有一个很大的内存块,称为系统全局区(SGA),在这里它会做以下工作:

  • 维护所有进程需要访问的多种内部数据结构;
  • 缓存磁盘上的数据,另外重做数据写至磁盘之前先在这里缓存;
  • 保存已解析的SQL计划;
  • 等等。

Oracle有一组“附加到”SGA的进程,附加机制因操作系统而异。

在UNIX环境中,这些进程会物理地附加到一个很大的共享内存段,这是操作系统中分配的一个内存块,可以由多个进程并发地访问;

在Windows中,这些进程只是使用C调用(malloc())来分配内存,因为它们实际上是一个大进程中的线程,所以会共享相同的虚拟内存空间。使用PSList,可以看到以下线程:

view plaincopy to clipboardprint?
  1. C:>PSList Oracle   
  2.   
  3. pslist v1.28 - Sysinternals PsList   
  4. Copyright ? 2000-2004 Mark Russinovich   
  5. Sysinternals   
  6.   
  7. Process information for BIGSUN:   
  8.   
  9. Name                Pid Pri Thd  Hnd   Priv        CPU Time    Elapsed Time  
  10. oracle             1772   8  13  258 169848     0:00:00.981     0:17:13.496  

具体的详细信息:

view plaincopy to clipboardprint?
  1. C:>PSList -d Oracle   
  2.   
  3. pslist v1.28 - Sysinternals PsList   
  4. Copyright ? 2000-2004 Mark Russinovich   
  5. Sysinternals   
  6.   
  7. Thread detail for BIGSUN:   
  8.   
  9.   
  10. oracle 1772:   
  11.  Tid Pri    Cswtch            State    User Time   Kernel Time   Elapsed Time  
  12. 1776   9       200   Wait:Executive  0:00:00.010   0:00:00.040    0:17:18.883   
  13. 1780   8       114     Wait:UserReq  0:00:00.010   0:00:00.000    0:17:17.271   
  14. 1792   8         2     Wait:UserReq  0:00:00.000   0:00:00.000    0:17:17.261   
  15. 1928   8       406     Wait:UserReq  0:00:00.000   0:00:00.000    0:17:16.220   
  16. 1948   8       533     Wait:UserReq  0:00:00.000   0:00:00.020    0:17:15.909   
  17. 1956   8       779     Wait:UserReq  0:00:00.000   0:00:00.010    0:17:15.599   
  18. 1992   9      1514     Wait:UserReq  0:00:00.010   0:00:00.010    0:17:15.298   
  19. 1996   8       245     Wait:UserReq  0:00:00.060   0:00:00.010    0:17:14.998   
  20. 2000   8        14     Wait:UserReq  0:00:00.000   0:00:00.000    0:17:14.697   
  21. 2008   8       237     Wait:UserReq  0:00:00.000   0:00:00.010    0:17:14.277   
  22. 2012   8       336     Wait:UserReq  0:00:00.120   0:00:00.020    0:17:13.956   
  23. 2016   8        39     Wait:UserReq  0:00:00.000   0:00:00.000    0:17:13.656   
  24. 2024   8        66     Wait:UserReq  0:00:00.000   0:00:00.010    0:17:13.295  

软件环境:Windows XP SP3+Oracle 9i R2,[ORACLE_HOME]=C:/Oracle/Ora92

PSList v1.28:http://www.microsoft.com/china/technet/sysinternals/utilities/PsList.mspx

 

2.3.1 专用服务器(dedicated server)

如果现在在使用一个专用服务器登陆数据库,则会创建一个新的进程,提供专门的服务:

view plaincopy to clipboardprint?
  1. C:>PSList Oracle   
  2.   
  3. pslist v1.28 - Sysinternals PsList   
  4. Copyright ? 2000-2004 Mark Russinovich   
  5. Sysinternals   
  6.   
  7. Process information for BIGSUN:   
  8.   
  9. Name                Pid Pri Thd  Hnd   Priv        CPU Time    Elapsed Time  
  10. oracle             3768   8  13  260 169768     0:00:01.822     0:21:40.259   
  11.   
  12. C:>SQLPLUS Muziyu/me   
  13.   
  14. SQL*Plus: Release 9.2.0.1.0 - Production on 星期六 10月 6 14:57:46 2007   
  15.   
  16. Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.   
  17.   
  18.   
  19. 连接到:   
  20. Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production   
  21. With the Partitioning, OLAP and Oracle Data Mining options   
  22. JServer Release 9.2.0.1.0 - Production   
  23.   
  24. MUZIYU@MYDB> HOST PSList Oracle   
  25.   
  26. pslist v1.28 - Sysinternals PsList   
  27. Copyright ? 2000-2004 Mark Russinovich   
  28. Sysinternals   
  29.   
  30. Process information for BIGSUN:   
  31.   
  32. Name                Pid Pri Thd  Hnd   Priv        CPU Time    Elapsed Time  
  33. oracle             3768   8  14  268 170416     0:00:01.832     0:22:12.776   
  34.   
  35. MUZIYU@MYDB> QUIT   
  36. 从Oracle9i Enterprise Edition Release 9.2.0.1.0 - Production   
  37. With the Partitioning, OLAP and Oracle Data Mining options   
  38. JServer Release 9.2.0.1.0 - Production中断开   
  39.   
  40. C:>PSList Oracle   
  41.   
  42. pslist v1.28 - Sysinternals PsList   
  43. Copyright ? 2000-2004 Mark Russinovich   
  44. Sysinternals   
  45.   
  46. Process information for BIGSUN:   
  47.   
  48. Name                Pid Pri Thd  Hnd   Priv        CPU Time    Elapsed Time  
  49. oracle             3768   8  13  260 169768     0:00:01.842     0:22:28.619  

可以看到,服务器刚启动的时候Oracle有13个线程(可能你的数据库启动时的线程数和我的不同,不过不用奇怪,这与你的数据库启动服务多少有一定的关系)。当我们用专用服务器连接Oracle时,Oracle线程由13个增加到14个。注销时,这个额外的线程也没有了。在UNIX上,可以看到正在运行的Oracle进程列表上会加一个进程,这就是我们的专用服务器。

现在如果按最常用的配置连接Oracle,如图2-2所示:

Figure 2-2. Typical dedicated server configuration

图2-2 典型的专用服务器配置

在我登陆时,Oracle总会为我创建一个新的进程。这通常称为专用服务器配置,因为这个服务器进程会在我的会话生存期中专门为我服务。对于每个会话,都会出现一个新的专用服务器,会话与专用服务器之间存在一对一的映射。按照定义,这个专用服务器不是实例的一部分。我的客户进程(也就是想要连接数据库的程序)会通过某种网络通道(如TCP/IP Socket)与这个专用服务器直接通信,并由这个服务器进程接收和执行我的SQL。这个服务器进程的主要目标就是对我提交的SQL调用做出响应。

2.3.2 共享服务器(shared server)

共享服务器,正式的说法是多线程服务器(Multi-Threaded Server,MTS)。如果采用这种方式,就不会对每条用户连接创建另外的线程或者新的UNIX进程。在共享服务器中,Oracle使用一个“共享进程”池为大量用户提供服务。共享服务器实际上就是一种连接池机制。这样我们可以为更多的用户提供数据库连接。

共享服务器连接和专用服务器连接之间有一个重大区别,与数据库连接的客户进程不会与共享服务器直接通信,但专用服务器则不然,客户进程会与专用服务器直接通信。之所以不能与共享服务器直接对话,原因就在于这个服务器进程是共享的。为了共享这些进程,还需要另外一种机制,通过这种机制才能与数据库进程共享“对话”。为此,Oracle使用了一个或一组称为调度器(dispatcher,也称分配器)的进程。客户进程通过网络与一个调度器进程通信。这个调度器进程将客户的请求放入SGA中的请求队列(这也是SGA的用途之一)。共享服务器请求的流程如图2-3所示:

Figure 2-3. Steps in a shared server request

图2-3 共享服务器请求的流程步骤

客户连接向调度器发送一个请求。调度器首先将这个请求放在SGA中的请求队列中①。第一个可用的共享服务器从请求队列中取出这个请求②并处理。共享服务器的处理结束后,再把响应(返回码、数据等)放到响应队列中③,接下来调度器拿到这个响应④,并回给客户。

2.3.3 TCP/IP连接的基本原理

这里将分析网络上最常见的一种情形:在TCP/IP连接上建立一个基于网络的连接请求。例如,客户可以发起以下命令:

view plaincopy to clipboardprint?
  1. C:>SQLPLUS NQ/nqdev@NQ   
  2.   
  3. SQL*Plus: Release 9.2.0.1.0 - Production on 星期日 10月 7 14:36:50 2007    
  4.   
  5. Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.    
  6.   
  7. 连接到:   
  8. Oracle9i Enterprise Edition Release 9.2.0.1.0 - 64bit Production    
  9. With the Partitioning, OLAP and Oracle Data Mining options    
  10. JServer Release 9.2.0.1.0 - Production    
  11.   
  12. NQ@NQ>  

这里,客户是程序SQL*Plus,NQ/nqdev为用户名/密码,NQ是一个TNS服务名。

TNS代表透明网络底层(Transparent Network Substrate),这是Oracle客户中处理远程连接的“基础”软件。一般地,你的机器上运行的客户软件会读取一个tnsnames.ora文件,这是一个纯文本的配置文件,通常放在[ORACLE_HOME]/Network/Admin目录下。例如,在我的机器上,tnsnames.ora存放在C:/Oracle/Ora92/Network/Admin目录下:

view plaincopy to clipboardprint?
  1. NQ =   
  2.   (DESCRIPTION =   
  3.     (ADDRESS_LIST =   
  4.       (ADDRESS = (PROTOCOL = TCP)(HOST = 200.200.200.240)(PORT = 1521))   
  5.     )   
  6.     (CONNECT_DATA =   
  7.       (SERVICE_NAME = NQ)   
  8.     )   
  9.   )  

根据这个配置信息,Oracle客户软件可以把我们使用的TNS连接串NQ映射到某些有用的信息,也就是主机名、该主机上“监听器”进程接受(监听)连接的端口、盖主机上所连接数据库的服务名,等等。服务名表示具有公共属性、服务级阈值和优先级的应用组。连接串(NQ)还可以使用Oracle Internet目录(Oracle Internet Directory,OID)来解析,这是一个分部式轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP)服务器,其作用就相当于解析主机名的DNS。

注意:tnsnames.ora通常只适用于大多数小到中型安装,在这些情况下,这些配置文件的副本不算太多,尚可管理。

既然客户软件知道连接到哪里,它会于主机名为NQ的服务器在端口1521上打开一条TCP/IP Socket连接。如果服务器DBA安装并配置了Oracle Net,并且有一个监听器在端口1521上监听连接请求,就会受到这个连接。在网络环境中,我们会在服务器上运行一个称为TNS监听器的进程。就是这个监听进程能让我们与数据物理连接。当它接受请求时,它会使用自己的配置文件检查这个请求,可能会拒绝请求,也可能拒绝请求,并真正建立连接。

如果建立一条专用服务器连接,监听器进程就会为我们创建一个专用服务器。在UNIX上,这是通过fork()和exec()系统调用做到的。这个新的专用服务器进程继承了监听器建立的连接,现在就与数据库物理地连接上了。在Windows上,监听器进程请求数据库进程为连接创建一个新的线程。一旦创建了这个线程,客户就会“重定向”到该线程,相应地就能建立物理连接。图2-4显示了UNIX上的监听器进程和专用服务器连接:

Figure 2-4. The listener process and dedicated server connections

图2-4 监听器进程和专用服务器连接

另一方面,如果我们发起共享服务器连接请求,接收到连接请求后,监听器会从可用的调度器池里选择一个调度器进程,监听器会向客户返回连接信息,其中说明了客户如何与调度器进程连接;如果可能的话,还可以把连接“转发”给调度器进程。监听器发回连接信息后,它的工作就结束了,因为监听器一直在特定主机的特定端口上运行,而调度器会在服务器上随意指派的端口上接收连接。监听器要知道调度器指定的这些随机端口号,并为我们选择一个服务器。客户再与监听器断开连接,并与调度器直接连接。现在就与数据库有了一个物理连接。如图2-5所示:

Figure 2-5. The listener process and shared server connections

图2-5 监听器进程和共享服务器连接

软件环境:Windows XP SP3+Oracle 9i R2,[ORACLE_HOME]=C:/Oracle/Ora92

PSList v1.28:http://www.microsoft.com/china/technet/sysinternals/utilities/PsList.mspx

 

 

在这一章中,我们将分析构成数据库和实例的8种文件类型。

与实例相关的文件只有:

  • 参数文件(parameter file):这些文件告诉Oracle实例在哪里可以找到控制文件,并且指定某些初始化参数,这些参数定义了某种内存结构有多大等设置。
  • 跟踪文件(trace file):这通常是一个服务器进程对某种异常错误条件做出响应时创建的诊断文件。
  • 警告文件(alert file):与跟踪文件类似,但是包含“期望”事件的有关信息,并且通过一个集中式文件(其中包含多个数据库事件)警告DBA。

构成数据库的文件包括:

  • 数据文件(data file):这些文件是数据库的主要文件:其中包括数据表、索引和所有其他的段。
  • 临时文件(temp file):这些文件用于完成基于磁盘的排序和临时存储。
  • 控制文件(control file):这些文件告诉你数据文件、临时文件和重做日志文件在哪里,还会指出与文件状态有关的其他元数据。
  • 重做日志文件(redo log file):这些就是事务日志。
  • 密码文件 (password file):这些文件用于对通过网络完成管理活动的用户进行认证。

Oracle 10g新增的文件:

  • 修改跟踪文件(change tracking file):这个文件有利于对Oracle建立真正的增量备份。
  • 闪回日志文件(flashback log file):这些文件存储数据库块的“前映像”,以便完成新增加的FLASHBACK DATABASE命令。

与数据库有关的其他类型的文件:

  • 转储文件(dump file,DMP file):这些文件由Export(导出)数据库实用程序生成,并由Import(导入)数据库实用程序使用。
  • 数据泵文件(Data Pump file):这些文件由Oracle 10g新增的数据泵导出(Data Pump Export)进程生成,并由数据泵(Data Pump Import)进程使用。外部表也可以创建和使用这种文件格式。
  • 平面文件(flat file):这些无格式文件可以在文本编辑器中查看。通常会使用这些文件向数据库中加载数据。

以上文件中,最重要的是数据文件重做日志文件,因为其中包含了你辛辛苦苦才积累起来的数据。

 

 

数据库的参数文件通常称为初始文件(init file),或者init.ora文件。在Oracle 9i Release 1之前,它的默认名为 init<ORACLE_SID>.ora。从Oracle 9i Release 1开始,引入了新方法:服务器参数文件(server parameter file),或简称为SPFILE ,默认名为spfile<ORACLE_SID>.ora。

注:SID - 站点标识符(site identifile)。

3.1.1 什么是参数?

简单地说,可以把数据库参数想成是一个“键”/“值”对,eg. DB_NAME = Ora10g,这里的“键”是 DB_NAME,“值”是Ora10g。

要得到一个实例参数的当前值,可以查询V$Parameter视图:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SELECT vp.Value<BR>  2    FROM V$Parameter vp<BR>  3   WHERE vp.Name = 'pga_aggregate_target';<BR><BR>VALUE<BR>------------------------------------------------------------------------------<BR>16777216  

另外,还可以在SQL*Plus中使用SHOW PARAMETER命令来查看:

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> SHOW PARAMETER pga_agg<BR><BR>NAME                                 TYPE        VALUE<BR>------------------------------------ ----------- ------------------------------<BR>pga_aggregate_target                 big integer 16777216  

建议使用SHOW PARAMETER,因为这个命令更简单,而且它会自动完成“通配”。

对不同版本的ORACLE,有记录的(documented)参数是不同的。同时,在不同的操作系统上参数也是有差异的。大多数参数(如 DB_BLOCK_SIZE)保留已久,不过,随着时间的推移,其他的很多参数会随着实现的改变而过时。eg.

view plaincopy to clipboardprint?
  1. MUZIYU@MYDB> ALTER SYSTEM SET distributed_transactions = 10;<BR>ALTER SYSTEM SET distributed_transactions = 10<BR>*<BR>ERROR 位于第 1 行:<BR>ORA-25138: DISTRIBUTED_TRANSACTIONS初始化参数已废弃  

ORACLE除了“有记录的”(documented)参数,同时,还有一些无记录的(undocumented)参数。如果参数名用下划线(_)开 头,就说明这个参数在文档中未做说明,即所谓的“无记录”。不建议使用无记录的参数,因为它表示只是过时的功能以及为保证 向后兼容性而设置的标志。还有一些参数有助于数据的恢复,而不是数据库本身的恢复。

在我的开发数据库中,即使有无记录的参数,也只会设置一个这样的参数:

view plaincopy to clipboardprint?
  1. _TRACE_FILES_PUBLIC = TRUE  

有了这个参数,所有人都可以读取跟踪文件,而不仅限于DBA小组。

3.1.2 遗留的init.ora参数文件

遗留的ORACLE init.ora文件从结构上来讲是一个相当简单的文件。以下是一个init.ora文件示例:

view plaincopy to clipboardprint?
  1. db_name='MyDB'<BR>db_block_size=4096<BR>control_files=('D:/Oracle/OraData/MyDB/CONTROL01.CTL',<BR>               'D:/Oracle/OraData/MyDB/CONTROL02.CTL',<BR>               'D:/Oracle/OraData/MyDB/CONTROL03.CTL')  

遗留参数文件的命名默认为:

view plaincopy to clipboardprint?
  1. init$ORACLE_SID.ora  (Unix environment variable)<BR>init%ORACLE_SID%.ora (Windows environment variable)  

默认目录:

view plaincopy to clipboardprint?
  1. $ORACLE_HOME/dbs       (Unix)<BR>%ORACLE_HOME%/DATABASE (Windows)  

IFILE指令可以在当前文件中包含指定文件的内容:

view plaincopy to clipboardprint?
  1. IFILE='C:/oracle/ora92/admin/sample/pfile/init.ora'  

注:参数文件不必放在特定的位置上。启动一个实例时,可以在启动命令上使用pfile=filename选项。

遗留的参数文件可以利用任何纯文本编辑器来维护。ORACLE数据库本身没有命令可以用来维护init.ora文件中包含的值。例如,如果使用 init.ora参数文件,发出ALTER SYSTEM命令来改变SGA组件的大小时,这并不会作为一个永久修改反映到init.ora。如果希望这个修改是永久的 ,换句话说,如果希望这成为以后数据库重启时的默认值,你就要负责确保可能用于启动数据库的所有init.ora参数文件都得到手动地更新。

注意,遗留的参数文件不一定位于数据库服务器上,因为启动数据库的客户机上必须有遗留的参数文件。

3.1.3 服务器参数文件

SPFILE可以消除传统参数文件存在的两个严重问题:

  • 可以杜绝参数文件的繁殖。
  • 无需在数据库之外使用文本编辑器手动地维护参数文件。

服务器参数文件的命名默认为:

view plaincopy to clipboardprint?
  1. spfile$ORACLE_SID.ora (Unix environment variable)<BR>spfile%ORACLE_SID.ora (Windows environment variable)  

建议使用默认位置,负责会影响SPFILE的简单性。

1. 转换为SPFILE

eg.

view plaincopy to clipboardprint?
  1. SYS@MYDB> SHOW PARAMETER SPFILE;<BR><BR>NAME                                 TYPE        VALUE<BR>------------------------------------ ----------- ------------------------------<BR>spfile                               string<BR>SYS@MYDB> CREATE SPFILE FROM PFILE;<BR><BR>文件已创建。<BR><BR>SYS@MYDB> STARTUP FORCE;<BR>ORACLE 例程已经启动。<BR><BR>Total System Global Area  143727516 bytes<BR>Fixed Size                   453532 bytes<BR>Variable Size             109051904 bytes<BR>Database Buffers           33554432 bytes<BR>Redo Buffers                 667648 bytes<BR>数据库装载完毕。<BR>数据库已经打开。<BR>SYS@MYDB> SHOW PARAMETER SPFILE;<BR><BR>NAME                                 TYPE        VALUE<BR>------------------------------------ ----------- ------------------------------<BR>spfile                               string      %ORACLE_HOME%/DATABASE/SPFILE%<BR>                                                 ORACLE_SID%.ORA  

上面的例子,使用SHOW PARAMETER命令显示出原先没有使用SPFILE,但是创建SPFILE并重启实例后,确定使用了这个SPFILE,而且它采用了 默认名。

注意:在集群环境中,通过使用Oracle RAC,所有实例共享同一个SPFILE。这个SPFILE可以包含所有参数设置,甚至各个实例特有的设置都 可以放在这一个SPFILE中,但是必须把所有必须的参数文件合并为一个有以下格式的SPFILE。

eg.

view plaincopy to clipboardprint?
  1. *.cluster_database_instances=2<BR>*.cluster_database=TRUE<BR>*.cluster_interconnects='10.10.10.0'<BR>*.compatible='10.1.0.2.0'<BR>*.control_files='/ocfs/O10G/control01.ctl','/ocfs/O10G/control02.ctl'<BR>*.db_name='O10G'<BR>...<BR>*.processes=150<BR>*.undo_management='AUTO'<BR>O10G1.instance_number=1<BR>O10G2.instance_number=2<BR>O10G1.local_listener='LISTENER_O10G1'<BR>O10G2.local_listener='LISTENER_O10G2'<BR>O10G1.remote_listener='LISTENER_O10G2'<BR>O10G2.remote_listener='LISTENER_O10G1'<BR>O10G1.thread=1<BR>O10G2.thread=2<BR>O10G1.undo_tablespace='UNDOTBS1'<BR>O10G2.undo_tablespace='UNDOTBS2'  

集群中所有实例共享的参数设置都以*.开头,单个实例特有的参数设置(INSTANCE_NUMBER和所用的重做THREAD)都以实例名(Oracle SID)为前缀。

  • PFILE对应包含两个节点的集群,其中的实例分别为010G1和010G2。
  • *.db_name='010G'这个赋值指示,使用这个SPFILE的所有实例会装载一个名为010G的数据库。
  • 010G1.undo_tablespace='UNDOTBS1'指示,名为010G1的实例会使用这个特定的撤销(undo)表空间,等等。

2. 设置SPFILE中的参数值

记住,SPFILE是二进制文件,它们可不能用文本编辑器来编辑。这个问题的答案就是使用ALTER SYSTEM命令,语法如下(<>中的部分是可选的,其中的管道符号(|)表示“取候选列表中的一个选项”):

view plaincopy to clipboardprint?
  1. ALTER SYSTEM SET parameter=value  <DEFERRED></DEFERRED><BR>                    

3. 取消SPFILE中的值设置

4. 从SPFILE创建PFILE

5. 修改被破坏的SPFILE

3.1.4 参数文件小节

软件环境:Windows XP SP3+Oracle 9i R2,[ORACLE_HOME]=C:/Oracle/Ora92

 

 

 

 

http://blog.csdn.net/ZhouZhiqiangFreesky/archive/2007/09/24/1798701.aspx

原创粉丝点击