第1章 开发成功的Oracle应用 - 1.3 开发数据库应用的正确(和不正确)方法

1.3.1 了解Oracle体系结构

1. 在Oracle中使用一个连接

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

2. 使用绑定变量

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

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

MUZIYU@MYDB> SELECT * FROM Emp e WHERE e.EmpNo = 123;

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

MUZIYU@MYDB> SELECT * FROM Emp e WHERE e.EmpNo = :EmpNo;

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

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

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

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

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

MUZIYU@MYDB> CREATE TABLE t ( x INT );

表已创建。

MUZIYU@MYDB> CREATE OR REPLACE PROCEDURE proc1
  2  AS
  3  BEGIN
  4        FOR i IN 1 .. 10000
  5        LOOP
  6              EXECUTE IMMEDIATE
  7               'INSERT INTO t VALUES ( :x )'
  8               USING i;
  9         END LOOP;
 10  END;
 11  /

过程已创建。

MUZIYU@MYDB> CREATE OR REPLACE PROCEDURE proc2
  2  AS
  3  BEGIN
  4        FOR i IN 1 .. 10000
  5        LOOP
  6              EXECUTE IMMEDIATE
  7              'INSERT INTO t VALUES ( ' || i || ' )';
  8        END LOOP;
  9  END;
 10  /

过程已创建。

MUZIYU@MYDB> EXEC RunStats_Pkg.Rs_Start;

PL/SQL 过程已成功完成。

MUZIYU@MYDB> EXEC proc1;

PL/SQL 过程已成功完成。

MUZIYU@MYDB> EXEC RunStats_Pkg.Rs_Middle;

PL/SQL 过程已成功完成。

MUZIYU@MYDB> EXEC proc2;

PL/SQL 过程已成功完成。

MUZIYU@MYDB> EXEC RunStats_Pkg.Rs_Stop(1000);
Run1 ran in 2555 hsecs
Run2 ran in 3024 hsecs
Run1 ran in 84.49% of the time
        
Name                              Run1        Run2        Diff
STAT...recursive calls             10,340      13,411       3,071
STAT...parse count (hard)               5      10,024      10,019
LATCH.library cache pin            80,547      96,347      15,800
LATCH.library cache pin alloca     40,406      63,112      22,706
LATCH.session allocation              101      31,987      31,886
LATCH.row cache enqueue latch         262      41,692      41,430
LATCH.row cache objects               286      41,875      41,589
STAT...session uga memory               0      65,464      65,464
STAT...session uga memory max           0      65,464      65,464
STAT...session pga memory               0      65,536      65,536
LATCH.cache buffers chains        187,559     121,821     -65,738
LATCH.child cursor hash table          45      71,011      70,966
LATCH.library cache               111,163     239,983     128,820
LATCH.shared pool                  50,936     265,538     214,602
        
Run1 lathces total versus runs -- difference and pct
Run1        Run2        Diff        Pct
487,600     988,951     501,351     49.30%

PL/SQL 过程已成功完成。

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

1.3.2 理解并发控制

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

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

1. 实现锁定

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

Oracle锁定策略:

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

2. 防止丢失更新

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

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

Muziyu@MyDB> CREATE TABLE Resources
  2 (
  3   Resource_Name VARCHAR2(25) PRIMARY KEY,
  4   ...
  5 );

Muziyu@MyDB> CREATE TABLE Schedules
  2 (
  3   Resource_Name REFERENCES Resources,
  4   Start_Time DATE NOT NULL,
  5   End_Time DATE NOT NULL,
  6   Check(Start_Time < End_Time),
  7   PRIMARY KEY(Resource_Name, Start_Time)
  8 );

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

Muziyu@MyDB> SELECT COUNT(*)
  2    FROM Schedules
  3   WHERE Resource_Name := :Room_Name
  4     AND Start_Time <= :New_End_Time
  5     AND End_Time >= :New_End_Time;

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

MUZIYU@MYDB> SELECT * 
  2    FROM Resources 
  3   WHERE Resource_Name := :Room_Name FOR UPDATE;

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

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

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

 11    SELECT Resource_Name
 12      INTO l_Resource_Name
 13      FROM Resources
 14     WHERE Resource_Name = p_Resource_Name
 15       FOR UPDATE;

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

 16    SELECT COUNT(*)
 17      INTO l_Cnt
 18      FROM Schedules
 19     WHERE Resource_Name = p_Resource_Name
 20       AND Start_Time <= p_End_Time
 21       AND End_Time >= p_Start_Time;
 22    IF l_Cnt <> 0
 23    THEN
 24      RAISE_APPLICATION_ERROR(-20001, 'Room is already booked!');
 25    END IF;

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

 26    INSERT INTO Schedules
 27    VALUES(p_Resource_Name, p_Start_Time, p_End_Time);
 28  END Schedule_Resource;

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

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

1.3.3 多版本

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

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

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

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

MUZIYU@MYDB> CREATE TABLE t
  2  AS
  3    SELECT *
  4      FROM All_Users;

表已创建。

MUZIYU@MYDB> VARIABLE x RefCursor
MUZIYU@MYDB> BEGIN
  2    OPEN :x FOR SELECT * FROM t;
  3  END;
  4  /

PL/SQL 过程已成功完成。

MUZIYU@MYDB> DELETE FROM t;

已删除32行。

MUZIYU@MYDB> COMMIT;

提交完成。

MUZIYU@MYDB> PRINT x;

USERNAME                          USER_ID CREATED
------------------------------ ---------- ----------
SYS                                     0 12-5月 -02
SYSTEM                                  5 12-5月 -02
OUTLN                                  11 12-5月 -02
DBSNMP                                 19 12-5月 -02
WMSYS                                  21 12-5月 -02
ORDSYS                                 30 12-5月 -02
ORDPLUGINS                             31 12-5月 -02
MDSYS                                  32 12-5月 -02
CTXSYS                                 33 12-5月 -02
XDB                                    35 12-5月 -02
ANONYMOUS                              36 12-5月 -02
WKSYS                                  39 12-5月 -02
WKPROXY                                40 12-5月 -02
ODM                                    42 12-5月 -02
ODM_MTR                                43 12-5月 -02
OLAPSYS                                44 12-5月 -02
RMAN                                   60 12-5月 -02
HR                                     46 12-5月 -02
OE                                     47 12-5月 -02
PM                                     48 12-5月 -02
SH                                     49 12-5月 -02
QS_ADM                                 51 12-5月 -02
QS                                     52 12-5月 -02
QS_WS                                  53 12-5月 -02
QS_ES                                  54 12-5月 -02
QS_OS                                  55 12-5月 -02
QS_CBADM                               56 12-5月 -02
QS_CB                                  57 12-5月 -02
QS_CS                                  58 12-5月 -02
SCOTT                                  59 12-5月 -02
MUZIYU                                 62 29-8月 -07
PERFSTAT                               66 17-9月 -07

已选择32行。

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

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

MUZIYU@MYDB> CREATE TABLE t
  2  AS
  3    SELECT *
  4      FROM All_Users;

表已创建。

MUZIYU@MYDB> VARIABLE x REFCURSOR;
MUZIYU@MYDB> BEGIN
  2    OPEN :x FOR SELECT * FROM t;
  3  END;
  4  /

PL/SQL 过程已成功完成。

MUZIYU@MYDB> DROP TABLE t;

表已丢弃。

MUZIYU@MYDB> COMMIT;

提交完成。

MUZIYU@MYDB> PRINT x;

USERNAME                             USER_ID CREATED
------------------------------ ------------- --------------
SYS                                        0 12-5月 -02
SYSTEM                                     5 12-5月 -02
OUTLN                                     11 12-5月 -02
DBSNMP                                    19 12-5月 -02
WMSYS                                     21 12-5月 -02
ORDSYS                                    30 12-5月 -02
ORDPLUGINS                                31 12-5月 -02
MDSYS                                     32 12-5月 -02
CTXSYS                                    33 12-5月 -02
XDB                                       35 12-5月 -02
ANONYMOUS                                 36 12-5月 -02
WKSYS                                     39 12-5月 -02
WKPROXY                                   40 12-5月 -02
ODM                                       42 12-5月 -02
ODM_MTR                                   43 12-5月 -02
OLAPSYS                                   44 12-5月 -02
RMAN                                      60 12-5月 -02
HR                                        46 12-5月 -02
OE                                        47 12-5月 -02
PM                                        48 12-5月 -02
SH                                        49 12-5月 -02
QS_ADM                                    51 12-5月 -02
QS                                        52 12-5月 -02
QS_WS                                     53 12-5月 -02
QS_ES                                     54 12-5月 -02
QS_OS                                     55 12-5月 -02
QS_CBADM                                  56 12-5月 -02
QS_CB                                     57 12-5月 -02
QS_CS                                     58 12-5月 -02
SCOTT                                     59 12-5月 -02
MUZIYU                                    62 29-8月 -07
PERFSTAT                                  66 17-9月 -07

已选择32行。
MUZIYU@MYDB> CREATE TABLE t
  2  AS
  3    SELECT *
  4      FROM All_Users;

表已创建。

MUZIYU@MYDB> VARIABLE x REFCURSOR;
MUZIYU@MYDB> BEGIN
  2    OPEN :x FOR SELECT * FROM t;
  3  END;
  4  /

PL/SQL 过程已成功完成。

MUZIYU@MYDB> TRUNCATE TABLE t;

表已截掉。

MUZIYU@MYDB> PRINT x;
ERROR:
ORA-08103: 对象不再存在



未选定行

1.多版本和闪回

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

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

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

下面举一个例子:

MUZIYU@MYDB> VARIABLE SCN NUMBER;
MUZIYU@MYDB> EXEC :SCN := DBMS_FLASHBACK.GET_SYSTEM_CHANGE_NUMBER;

PL/SQL 过程已成功完成。

MUZIYU@MYDB> PRINT SCN;

   SCN
----------
1880470

MUZIYU@MYDB> SELECT COUNT(*) FROM t1;

COUNT(*)
----------
1000000

MUZIYU@MYDB> DELETE FROM t1;

已删除1000000行。

MUZIYU@MYDB> SELECT COUNT(*) FROM t1;

COUNT(*)
----------
      0

MUZIYU@MYDB> SELECT COUNT(*) FROM t1 AS OF SCN :SCN;

COUNT(*)
----------
  1000000

MUZIYU@MYDB> COMMIT;

提交完成。

MUZIYU@MYDB> SELECT *
  2     FROM (SELECT COUNT(*) FROM T1),
  3                  (SELECT COUNT(*) FROM T1 AS OF SCN :SCN);

COUNT(*)        COUNT(*)
--------------- ---------------
       0        1000000

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

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

2. 读一致性和非阻塞读

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

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

MUZIYU@MYDB> CREATE TABLE Accounts
  2  (
  3    Account_Number NUMBER PRIMARY KEY,
  4    Account_Balance NUMBER
  5  );

表已创建。

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

Accounts表的内容
账户账户余额
1123$500.00
2234$250.00
3345$400.00
4456$100.00

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

MUZIYU@MYDB> SELECT SUM(a.Account_Balance) 
  2  FROM Accounts a;

SUM(A.ACCOUNT_BALANCE)
----------------------
                  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行现在有$500
T6读第4行:发现第4行已修改。这会将块回滚到T1时刻的状态。查询从这个块读到值$100 
T7得到答案$1250 

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

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

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

MUZIYU@MYDB> CREATE TABLE t
  2  AS
  3    SELECT * FROM All_Users WHERE ROWNUM <= 5;

表已创建。

MUZIYU@MYDB> SELECT * FROM t;

USERNAME                          USER_ID CREATED
------------------------------ ---------- ----------
SYS                                     0 12-5月 -02
SYSTEM                                  5 12-5月 -02
OUTLN                                  11 12-5月 -02
DBSNMP                                 19 12-5月 -02
WMSYS                                  21 12-5月 -02

MUZIYU@MYDB> BEGIN
  2    FOR x IN (SELECT * FROM t)
  3    LOOP
  4      INSERT INTO t VALUES(x.UserName, x.User_ID, x.Created);
  5    END LOOP;
  6  END;
  7  /

PL/SQL 过程已成功完成。

MUZIYU@MYDB> SELECT * FROM t;

USERNAME                          USER_ID CREATED
------------------------------ ---------- ----------
SYS                                     0 12-5月 -02
SYSTEM                                  5 12-5月 -02
OUTLN                                  11 12-5月 -02
DBSNMP                                 19 12-5月 -02
WMSYS                                  21 12-5月 -02
SYS                                     0 12-5月 -02
SYSTEM                                  5 12-5月 -02
OUTLN                                  11 12-5月 -02
DBSNMP                                 19 12-5月 -02
WMSYS                                  21 12-5月 -02

已选择10行。

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

1.3.4 数据库独立性?

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

1. 标准的影响

SQL92标准有4个层次:

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

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

2. 防御式编程(defensive programming)

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

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

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

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

MUZIYU@MYDB> CREATE TABLE ID_Table
  2  (
  3    ID_Name VARCHAR2(30) PRIMARY KEY,
  4    ID_Value NUMBER
  5  );

表已创建。

MUZIYU@MYDB> INSERT INTO ID_Table VALUES('MY_KEY', 0);

已创建 1 行。

MUZIYU@MYDB> COMMIT;

提交完成。

MUZIYU@MYDB> UPDATE ID_Table
  2     SET ID_Value = ID_Value + 1
  3   WHERE ID_Name = 'MY_KEY';

已更新 1 行。

MUZIYU@MYDB> SELECT ID_Value
  2    FROM ID_Table
  3   WHERE ID_Name = 'MY_KEY';

  ID_VALUE
----------
         1

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

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

eg.

MUZIYU@MYDB> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

事务处理集。

MUZIYU@MYDB> UPDATE ID_Table
  2     SET ID_Value = ID_Value + 1
  3   WHERE ID_Name = 'MY_KEY';

已更新 1 行。

MUZIYU@MYDB> SELECT ID_Value
  2    FROM ID_Table
  3   WHERE ID_Name = 'MY_KEY';

  ID_VALUE
----------
         1

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

MUZIYU@MYDB> SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

事务处理集。

MUZIYU@MYDB> UPDATE ID_Table
  2     SET ID_Value = ID_Value + 1
  3   WHERE ID_Name = 'MY_KEY';

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

MUZIYU@MYDB> COMMIT;

提交完成。

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

MUZIYU@MYDB> UPDATE ID_Table
  2     SET ID_Value = ID_Value + 1
  3   WHERE ID_Name = 'MY_KEY';
UPDATE ID_Table
       *
ERROR 位于第 1 行:
ORA-08177: 无法连续访问此事务处理

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

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

MUZIYU@MYDB> CREATE TABLE t (PK NUMBER PRIMARY KEY);

表已创建。

MUZIYU@MYDB> CREATE SEQUENCE t_Seq;

序列已创建。

MUZIYU@MYDB> CREATE TRIGGER t_Trigger BEFORE INSERT ON t FOR EACH ROW
  2  BEGIN
  3    SELECT t_Seq.NEXTVAL INTO :New.PK FROM DUAL;
  4  END;
  5  /

触发器已创建

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

MUZIYU@MYDB> INSERT INTO t VALUES(t_Seq.NEXTVAL);

已创建 1 行。

3. 特性和功能

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

4. 简单地解决问题

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

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

MUZIYU@MYDB> CREATE PROFILE One_Session LIMIT SESSIONS_PER_USER 1;

配置文件已创建

MUZIYU@MYDB> ALTER USER Scott PROFILE One_Session;

用户已更改。

MUZIYU@MYDB> ALTER SYSTEM SET Resource_Limit = TRUE;

系统已更改。

MUZIYU@MYDB> CONNECT Scott/tiger;
已连接。
MUZIYU@MYDB> HOST SQLPLUS Scott/tiger;

SQL*Plus: Release 9.2.0.1.0 - Production on 星期五 9月 28 20:13:36 2007

Copyright (c) 1982, 2002, Oracle Corporation.  All rights reserved.

ERROR:
ORA-02391: exceeded simultaneous SESSIONS_PER_USER limit


请输入用户名:

5. 开放性

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

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

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

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

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

MUZIYU@MYDB> ALTER SESSION SET CURSOR_SHARING = EXACT;

会话已更改。

    MUZIYU@MYDB> SELECT /* TAG */ SUBSTR(UserName, 1, 1)
  2    FROM All_Users
  3   WHERE ROWNUM = 1;

SU
--
S

MUZIYU@MYDB> ALTER SESSION SET CURSOR_SHARING = FORCE;

会话已更改。

MUZIYU@MYDB> SELECT /* TAG */ SUBSTR(UserName, 1, 1)
  2    FROM All_Users
  3   WHERE ROWNUM = 1;

SU
--
S

MUZIYU@MYDB> ALTER SESSION SET CURSOR_SHARING = SIMILAR;

会话已更改。

    MUZIYU@MYDB> SELECT /* TAG */ SUBSTR(UserName, 1, 1)
  2    FROM All_Users
  3   WHERE ROWNUM = 1;

SU
--
S

1.3.6 DBA与开发人员的关系

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值