Oracle 9i & 10g编程艺术-深入数据库体系结构——第一章 开发成功的Oracle应用程序

第1章                      开发成功的Oracle应用程序我花了大量时间使用Oracle数据库软件,更确切地讲,一直在与和使用Oracle数据库软件的人打交道。在过去的18年间,我参与过许多项目,有的相当成功,有点却彻底失败,如果把这些经验用几句话来概括,可以总结如下:q         基于数据库(或依赖于数据库)构建的应用是否成功,这取决于如何使用数据库。另外,从
摘要由CSDN通过智能技术生成

第1章                      开发成功的Oracle应用程序

我花了大量时间使用Oracle数据库软件,更确切地讲,一直在与和使用Oracle数据库软件的人打交道。在过去的18年间,我参与过许多项目,有的相当成功,有点却彻底失败,如果把这些经验用几句话来概括,可以总结如下:

q         基于数据库(或依赖于数据库)构建的应用是否成功,这取决于如何使用数据库。另外,从我的经验看,所有应用的构建都围绕着数据库。如果一个应用未在任何地方持久地存储数据,很难想象这个应用真的有用。

q         应用总是在“来来去去”,而数据不同,它们会永远存在。从长远来讲,我们的目标并不是构建应用,而应该是如何使用这些应用底层的数据。

q         开发小组的核心必须有一些精通数据库的开发人员,他们要负责确保数据库逻辑是可靠的,系统能够顺利构建。如果已成事实(应用已经部署)之后再去调优,这通常表明,在开发期间你没有认真考虑这些问题。

这些话看上去再显然不过了。然而,我发现太多的人都把数据库当成是一个黑盒(black box),好像不需要对它有输入了解。他们可能有一个SQL生成器,认为有了这个工具,就不需要再费功夫去学SQL语言。也可能认为使用数据库就像使用平面文件一样,只需要根据索引读数据就行。不管他们怎么想,有一点可以告诉你,如果按这种思路来考虑,往往会被误导:不了解数据库,你将寸步难行。这一章将讨论为什么需要了解数据库,具体地讲,就是为什么需要理解以下内容:

q         数据库的体系结构,数据库如何工作,以及有怎样的表现。

q         并发控制是什么,并发控制对你意味着什么。

q         性能、可扩缩性和安全性都是开发时就应该考虑的需求,必须适当地做出设计,不要指望能碰巧满足这些需求。

q         数据库的特性如何实现。某个特定数据库特性的实际实现方式可能与你想象的不一样。你必须根据数据库实际上如何工作(而不是认为它应该如何工作)来进行设计。

q         数据库已经提供了哪些特性,为什么使用数据库已提供的特性要优于自行构建自己的特性。

q         为什么粗略地了解SQL还不够,还需要更深入地学习SQL

q         DBA和开发人员都在为同一个目标努力,他们不是敌对的两个阵营,不是想在每个回合中比试谁更聪明。

初看上去,好像要学习的东西还不少,不过可以做个对照,请考虑这样一个问题:如果你在一个全新的操作系统(operating systemOS)上开发一个高度可扩缩的企业应用,首先要做什么?希望你的答案是:“了解这个新操作系统如何工作,应用在它上面怎样运行,等等”。如果不是这样,你的开发努力将会付诸东流。

例如,可以考虑一下WindowsLinux,它们都是操作系统。能为开发人员提供大致相同的一组服务,如文件管理、内存管理、进程管理、安全性等。不过,这两个操作系统的体系结构却大相径庭。因此,如果你一直是Windows程序员,现在给你一个任务,让你在Linux平台上开发新应用,那么很多东西都得从头学起。内存管理的处理就完全不同。建立服务器进程的方式也有很大差异。在Window下,你会开发一个进程,一个可执行程序,但有许多线程。在Linux下则不同,不会开发单个独立的可执行程序;相反,会有多个进程协作。总之,你在Windows环境下学到的许多知识到了Linux上并不适用(公平地讲,反之亦然)。要想在新平台上也取得成功,你必须把原来的一些习惯丢掉。

在不同操作系统上运行的应用存在上述问题,基于不同数据库运行的应用也存在同样的问题:你要懂得,数据库对于成功至关重要。如果不了解你的数据库做什么,或者不清楚它怎么做,那你的应用很可能会失败。如果你认为应用在SQL Server上能很好地运行,那它在Oracle上也肯定能很好地工作,你的应用往往会失败。另外,公平地讲,反过来也一样:一个Oracle应用可能开发得很好,可扩缩性很好,但是如果不对体系结构做重大改变,它在SQL Server上不一定能正常运行。WindowsLinux都是操作系统,但是二者截然不同,同样地,OracleSQL Server(甚至可以是任何其他数据库)尽管都是数据库,但二者也完全不同。

1.1   我的方法

在阅读下面的内容之前, 我觉得有必要先解释一下我的开发方法。针对问题,我喜欢采用一种以数据库为中心的方法。如果能在数据库中完成,我肯定就会让数据库来做,而不是自行实现。对此有几个原因。首先,也是最重要的一点,我知道如果让数据库来实现功能,应用就可以部署在任何环境中。据我所知,没有哪个流行的服务器操作系统不支持Oracle;从Windows到一系列UNIX/Linux系统,再到OS/390大型机系统,都支持Oracle软件和诸多选项。我经常在我的笔记本电脑上构建和测试解决方案,其中在LinuxWindows XP上(或者使用VMware来模拟这些环境)运行Oracle9i/Oracle 10g 。这样一来,我就能把这些解决方案部署在运行相同数据库软件但有不同操作系统的多种服务器上。我发现,如果某个特性不是在数据库中实现,要想在希望的任何环境中部署这个特性将极其困难。Java语言之所以让人趋之若鹜,一个主要原因就是Java程序总在同样的虚拟环境[即Java虚拟机(Java Virtual MachineJVM)]中编译,这使得这些程序的可移植性很好。有意思的是,也正是这个特性让我对数据库着迷不已。数据库就是我的“虚拟机”,它也是我的“虚拟操作系统”。

前面已经提到,我采用的方法是尽可能在数据库中实现功能。如果数据库环境无法满足我的需求,我也会在数据库之外使用JavaC来实现。采用这种方式,操作系统的复杂细节对我来说几乎是隐藏的。我要了解我的“虚拟机”如何工作(也就是Oracle如何工作,有时可能还需要用到JVM),毕竟,起码要了解自己使用的工具才行。不过,至于在一个给定的操作系统上怎么才能最好地工作,这些都由“虚拟机”来负责。

所以,只需知道这个“虚拟操作系统”的细节,我构建的应用就能在多种操作系统上很好地工作和扩缩。我并不是暗示你可以完全忽略底层的操作系统。不过,作为一个构建数据库应用的软件开发人员,还是尽量避开它比较好,你不必处理操作系统的诸多细微之处。应该让你的 DBA(负责运行Oracle软件)来考虑如何适应操作系统(如果他或她做不到,你就该换个新的DBA了!)。如果你在开发客户/服务器软件,而且大量代码都是在数据库和虚拟机(VMJVM可能是最流行的虚拟机了)之外实现,那你还得再次考虑你的操作系统。

对于开发数据库软件,我有一套很简单的哲学,这是我多年以来一直信守的思想:

q         如果可能,尽量利用一条SQL语句完成工作。

q         如果无法用一条SQL语句完成,就通过PL/SQL实现(不过,尽可能少用PL/SQL!)。

q         如果在PL/SQL中也无法做到(因为它缺少一些特性,如列出目录中的文件),可以试试使用Java存储过程来实现。不过,有了Oracle9i 及以上版本后,如今需要这样做的可能性极小。

q         如果用Java还办不到,那就在C外部过程中实现。如果速度要求很高,或者要使用采用C编写的一个第三方API,就常常使用这种做法。

q         如果在C外部例程中还无法实现,你就该好好想想有没有必要做这个工作了。

在这本书中,你会看到我是怎样将上述思想付诸实现的。我会尽可能使用SQL,充分利用它强大的新功能,如使用分析函数来解决相当复杂的问题,而不是求助于过程性代码。如果需要,我会使用PL/SQLPL/SQL中的对象类型来完成SQL本身办不到的事情。PL/SQL发展至今已经有很长时间了,它得到了长达18年的调整和优化。实际上,Oracle 10g 编译器本身就首次重写为一个优化编译器。你会发现,没有哪种语言能像PL/SQL这样与SQL如此紧密地耦合,也没有哪种语言得到如此优化,可以与SQL更好地交互。在PL/SQL中使用SQL是一件相当自然的事情,而在几乎所有其他语言(从Visual BasicJava)中,使用SQL感觉上都很麻烦。对于这些语言来说,使用SQL绝对没有“自然”的感觉;它不是这些语言本身的扩缩。如果PL/SQL还无法做到(这在Oracle 9i10g 中可能相当少见),我们会使用Java。有时,如果C是惟一的选择,或者需要C才能提供的高速度,我们也会用C来完成工作,不过这往往是最后一道防线。随着本地Java编译(native Java compilation)的闪亮登场(可以把Java字节码转换为具体平台上特定于操作系统的对象码),你会发现,在许多情况下,JavaC的运行速度相差无几。所以,需要用到C的情况越来越少。

1.2   黑盒方法 

根据我个人的第一手经验(这表示,在学习软件开发时我自己也曾犯过错误),我对基于数据库的软件开发为什么如此频繁地遭遇失败有一些看法。先来澄清一下,这里提到的这些项目可能一般不算失败,但是启用和部署所需的时间比原计划多出许多,原因是需要大幅重写,重新建立体系结构,或者需要充分调优。我个人把这些延迟的项目称为“失败”,因为它们原本可以按时完成(甚至可以更快完成)。

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

我一直很难理解这种数据库开发方法,原因有二。一个原因是对我来说,学习JavaC比学习数据库基本概念要难多了。现在我对JavaC已经很精通,但是在能熟练使用JavaC之前我经受了许多磨炼,而掌握数据库则没有这么费劲。对于数据库,你要知道它是怎么工作的,但无需了解每一个细节。用CJava编程时,则确实需要掌握每一个细枝末节,而这些语言实在是很“庞大”。

让我无法理解这种方法的另一个原因是,构建数据库应用时,最重要的软件就是数据库。成功的开发小组都会认识到这一点,而且每个开发人员都要了解数据库,并把重点放在数据库上。但我接触到的许多项目中,情况却几乎恰恰相反。例如,下面就是一种典型的情况:

q         在构建前端所用的GUI工具或语言(如Java)方面,开发人员得到了充分的培训。在很多情况下,他们会有数周甚至数月的培训。

q         开发人员没有进行过Oracle培训,也没有任何Oracle经验。大多数人都没有数据库经验,所以并未理解如何使用核心的数据库构造(如各种可用的索引和表结构)。

q         开发人员力图谨守“数据库独立性”这一原则,但是出于许多原因,他们可能做不到。最明显的一个原因是:他们对于数据库没有足够的了解,也不清楚这些数据库可能有什么区别。这样一个开发小组无法知道要避开数据库的哪些特性才能保证数据库独立性。

q         开发人员遇到大量性能问题、数据完整性问题、挂起问题等(但这些应用的界面往往很漂亮)。

因为出现了无法避免的性能问题,他们把我找来,要求帮助解决这些难题。我最早就是从构建数据库独立的应用做起的(从某种程度上讲,在ODBC问世之前,我就已经编写了自己的ODBC驱动程序),我知道哪些地方可能会犯错误,因为我以前就曾犯过这些错误。我总会查看是否存在下面这些问题:存在效率低下的SQL;有大量过程性代码,但这些工作原本用一条SQL语句就足够了;为了保持数据库独立性,没有用到新特性(1995年以后的新特性都不敢用),等等。

我还记得这样一个特例,有个项目找我来帮忙。当时要用到一个新命令,但我记不清那个新命令的语法。所以我让他们给我拿一本SQL Reference手册,谁知他们给了我一本Oracle 6.0文档。那个项目开发用的是7.3版本,要知道,6.0版本和7.3版本之间整整相差5年!7.3才是所有开发人员使用的版本,但似乎谁都不关心这一点。不用说他们需要了解的跟踪和调优工具在6.0版本中甚至不存在。更不用说在这5年间又增加了诸如触发器、存储过程和数百个其他特性,这些都是编写6.0版文档(也就是他们现在参考的文档)时根本没有的特性。由此很容易看出他们为什么需要帮助,但解决起来就是另一码事了。

注意      甚至时至今日,已经到了2005年,我还是经常发现有些数据库应用开发人员根本不花些时间来看文档。我的网站(http://asktom.oracle.com)上经常会有:“……的语法是什么”这样的问题,并解释说“我们拿不到文档,所以请告诉我们”。对于许多这样的问题,我拒绝直接做出回答,而是会把在线文档的地址告诉他们。无论你身处何地,都能免费得到这些文档。在过去10年中,“我们没有文档”或“我们无法访问资源”之类的借口已经站不住脚了。如今已经有了诸如http://otn.oracle.comOracle技术网络)和http://groups. google.com (Google Groups Usenet论坛)等网站,它们都提供了丰富的资源,如果你手边还没有一套完整的文档,那就太说不过去了!

构建数据库应用的开发人员要避开数据库的主张实在让我震惊,不过这种做法还顽固不化地存在着。许多人还认为开发人员没办法花那么多时间来进行数据库培训,而且他们根本不需要了解数据库。为什么?我不止一次地听到这样的说法:“Oracle是世界上最可扩缩的数据库,所以我们不用了解它,它自然会按部就班地把事情做好的。”Oracle是世界上最可扩缩的数据库,这一点没错。不过,用Oracle不仅能写出好的、可扩缩的代码,也同样能很容易地写出不好的、不可扩缩的代码(这可能更容易)。把这句话里的“Oracle”替换为其他任何一种技术的名字,这句话仍然正确。事实是:编写表现不佳的应用往往比编写表现优秀的应用更容易。如果你不清楚自己在做什么,可能会发现你打算用世界上最可扩缩的数据库建立一个单用户系统!

数据库是一个工具;不论是什么工具,如果使用不当都会带来灾难。举个例子,你想用胡桃钳弄碎胡桃,会不会把胡桃钳当锤子一样用呢?当然这也是可以的,不过这样用胡桃钳很不合适,而且后果可能很严重,没准会重重地伤到你的手指。如果还是对你的数据库一无所知,你也会有类似的结局。

例如,最近我参与了一个项目。开发人员正饱受性能问题之苦,看上去他们的系统中许多事务在串行进行。他们的做法不是大家并发地工作,而是每个人都要排一个长长的队,苦苦等着前面的人完成后才能继续。应用架构师向我展示了系统的体系结构,这是经典的三层方法。他们想让Web浏览器与一个运行JSPJavaServer Pages)的中间层应用服务器通信。JSP再使用另一个EJBEnterprise JavaBeans)层,在这一层执行所有SQLEJB中的SQL由某个第三方工具生成,这是采用一种数据库独立的方式完成的。

现在看来,对这个系统很难做任何诊断,因为没有可测量或可跟踪的代码。测量代码(instrumenting code)堪称一门艺术,可以把开发的每行代码变成调试代码,这样就能跟踪应用的执行,遇到性能、容量甚至逻辑问题时就能跟踪到问题出在哪里。在这里,我们只能肯定地说问题出在“浏览器和数据库之间的某个地方”。换句话说,整个系统都是怀疑对象。对此有好消息也有坏消息。一方面,Oracle数据库完全可测量;另一方面,应用必须能够在适当的位置打开和关闭测量,遗憾的是,这个应用不具备这种能力。

所以,我们面对的困难是,要在没有太多细节的情况下诊断出导致性能问题的原因,我们只能依靠从数据库本身收集的信息。一般地,要分析应用的性能问题,采用应用级跟踪更合适。不过,幸运的是,这里的解决方案很简单。通过查看一些 Oracle V$表(V$ 表是Oracle 提供其测量结果或统计信息的一种方法),可以看出,竞争主要都围绕着一个表,这是一种排队表。结论是根据V$LOCK视图和V$SQL做出的,V$LOCK视图可以显示阻塞的会话,V$SQL会显示这些阻塞会话试图执行的SQL。应用想在这个表中放记录,而另外一组进程要从表中取出记录并进行处理。通过更深入地“挖掘”,我们发现这个表的PROCESSED_FLAG列上有一个位图索引。

注意    12章会详细介绍位图索引,并讨论为什么位图索引只适用于低基数值,但是对频繁更新的列不适用。

原因在于,PROCESSED_FLAG列只有两个值:YN。对于插入到表中的记录,该列值为N(表示未处理)。其他进程读取和处理这个记录时,就会把该列值从N更新为Y。这些进程要很快地找出PROCESSED_FLAG列值为N的记录,所以开发人员知道,应该对这个列建立索引。他们在别处了解到,位图索引适用于低基数(low-cardinality)列,所谓低基数列就是指这个列只有很少的可取值,所以看上去位图索引是一个很自然的选择。

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

所以,如果有人插入一条新记录(PROCESSED_FLAG列值为N),就会锁定位图索引中的N键,而这会有效地同时锁定另外数百条PROCESSED_FLAG列值为N的记录(以下记作N记录)。此时,想要读这个表并处理记录的进程就无法将N记录修改为Y记录(已处理的记录)。原因是,要想把这个列从N更新为Y,需要锁定同一个位图索引键。实际上,想在这个表中插入新记录的其他会话也会阻塞,因为它们同样想对这个位图索引键锁定。简单地讲,开发人员实现了这样一组结构,它一次最多只允许一个人插入或更新!

可以用一个简单的例子说明这种情况。在此,我使用两个会话来展示阻塞很容易发生:

ops$tkyte@ORA 10G > create table t ( processed_flag varchar2(1) );

Table created.

ops$tkyte@ORA 10G > create bitmap index t_idx on t(processed_flag);

Index created.

ops$tkyte@ORA 10G > insert into t values ( 'N' );

1 row created.

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

ops$tkyte@ORA 10G > insert into t values ( 'N' );

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

这里的问题就是缺乏足够的了解造成的;由于不了解数据库特性(位图索引),不清楚它做些什么以及怎么做,就导致这个数据库从一开始可扩缩性就很差。一旦找出了问题,修正起来就很容易了。处理标志列上确实要有一个索引,但不能是位图索引。这里需要一个传统的B*Tree索引。说服开发人员接受这个方案很是费了一番功夫,因为这个列只有两个不同的可取值,却需要使用一个传统的索引,对此没有人表示赞同。不过,通过仿真(我很热衷于仿真、测试和试验),我们证明了这确实是正确的选择。对这个列加索引有两种方法:

q         在处理标志列上创建一个索引。

q         只在处理标志为N时在处理标志列上创建一个索引,也就是说,只对感兴趣的值加索引。通常,如果处理标志为Y,我们可能不想使用索引,因为表中大多数记录处理标志的值都可能是Y。注意这里的用辞,我没有说“我们绝对不想使用索引”,如果出于某种原因需要频繁地统计已处理记录的数目,对已处理记录加索引可能也很有用。

最后,我们只在处理标志为N的记录上创建了一个非常小的索引,由此可以快速地访问感兴趣的记录。

到此就结束了吗?没有,绝对没有结束。开发人员的解决方案还是不太理想。由于他们对所用工具缺乏足够的了解,我们只是修正了由此导致的主要问题,而且经过大量研究后才发现系统不能很好地测量。我们还没有解决以下问题:

q         构建应用时根本没有考虑过可扩缩性。可扩缩性必须在设计中加以考虑。

q         应用本身无法调优,甚至无法修改。经验证明,80%90%的调优都是在应用级完成的,而不是在数据库级。

q         应用完成的功能(排队表)实际上在数据库中已经提供了,而且数据库是以一种高度并发和可扩缩的方式提供的。我指的就是数据库已有的高级排队(Advanced QueuingAQ)软件,开发人员没有直接利用这个功能,而是在埋头重新实现。

q         开发人员不清楚bean在数据库中做了什么,也不知道出了问题要到哪里去查。

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

q         如何对SQL调优而不修改SQL。这看起来很神奇,不过在Oracle 10g 中确实可以办得到,从很大程度上讲堪称首创。

q         如何测量性能。

q         如何查看哪里出现了瓶颈。

q         如何建立索引,对什么建立索引。

q         如此等等。

一周结束后,原本对数据库敬而远之的开发人员惊讶地发现,数据库居然能提供如此之多的功能,而且了解这些信息是如此容易。最重要的是,这使得应用的性能发生了翻天覆地的变化。最终他们的项目还是成功了,只是比预期的要晚几个星期。

这个例子不是批评诸如EJB和容器托管持久存储之类的工具或技术。我们要批评的是故意不去了解数据库,不去学习数据库如何工作以及怎样使用数据库这种做法。这个案例中使用的技术本来能很好地工作,但要求开发人员对数据库本身有一些深入的了解。

关键是:数据库通常是应用的基石。如果它不能很好地工作,那其他的都没有什么意义了。如果你手上有一个黑盒,它不能正常工作,你能做些什么呢?可能只能做一件事,那就是盯着这个黑盒子发愣,搞不明白为什么它不能正常工作。你没办法修理它,也没办法进行调整。你根本不知道它是怎样工作的,最后的决定只能是保持现状。我提倡的方法则是:了解你的数据库,掌握它是怎么工作的,弄清楚它能为你做什么,并且最大限度地加以利用。

1.3   开发数据库应用的正确(和不正确)方法 

到目前为止,我一直是通过闲聊来强调理解数据库的重要性。本章后面则会靠实验说话,明确地讨论为什么了解数据库及其工作原理对于成功的实现大有帮助(而无需把应用写两次!)。有些问题修改起来很简单,只要你知道怎么发现这些问题即可。还有一些问题则不同,必须大动干戈地重写应用方能更正。这本书的目标之一就是首先帮助避免问题的发生。

注意      在下面的几节中,我会谈到一些核心的Oracle特性,但并不深入地讨论这些特性到底是什么,也不会全面地介绍使用这些特性的全部细节。如果想了解更多的信息,建议你接着阅读本书后面的章节,或者参考相关的Oracle文档。

1.3.1             了解Oracle体系结构

最近,我参与了一个客户的项目,他运行着一个大型的生产应用。这个应用已经从SQL Server“移植到”Oracle。之所以把“移植”一词用引号括起来,原因是我看到的大多数移植都只是“怎么能只对SQL Server代码做最少的改动,就让它们在Oracle上编译和执行”。要把应用从一个数据库真正移植到另一个数据库,这绝对是一项大工程。必须仔细检查算法,看看算法在目标数据库上能否正确地工作;诸如并发控制和锁定机制等特性在不同的数据库中实现不同,这会直接影响应用在不同数据库上如何工作。另外还要深入地分析算法,看看在目标数据库中实现这些算法是否合理。坦率地讲,我看到的大多数应用通常都是根据“最小移植”得来的,因为这些应用最需要帮助。当然,反过来也一样:把一个Oracle应用简单地“移植”到SQL Server,而且尽可能地避免改动,这也会得到一个很成问题、表现极差的应用。

无论如何,这种“移植”的目标都是扩缩应用,要支持更大的用户群。不过,“移植”应用的开发人员一方面想达到这个目的,另一方面又想尽量少出力。所以,这些开发人员往往保持客户和数据库层的体系结构基本不变,简单地将数据从SQL Server移到Oracle,而且尽可能不改动代码。如果决定将原来SQL Server上的应用设计原封不动地用在Oracle上,就会导致严重的后果。这种决定最糟糕的两个后果是:

q         Oracle中采用与SQL Server同样的数据库连接体系结构。

q         开发人员在SQL中使用直接量(而不是绑定变量)。

这两个结果导致系统无法支持所需的用户负载(数据库服务器的可用内存会耗尽),即使用户能登录和使用应用,应用的性能也极差。

1.      Oracle中使用一个连接

目前SQL Server中有一种很普遍的做法,就是对想要执行的每条并发语句都打开一个数据库连接。如果想执行5个查询,可能就会在SQL Server中看到5个连接。SQL Server就是这样设计的,就好像Windows是针对多线程而不是多进程设计的一样。在Oracle中,不论你想执行5个查询还是500个查询,都希望最多打开一个连接。Oracle就是本着这样的理念设计的。所以,SQL Server中常用的做法在Oracle中却不提倡;你可能并不想维护多个数据库连接。

不过,他们确实这么做了。一个简单的Web应用对于每个网页可能打开5个、10个、15个甚至更多连接,这意味着,相对于服务器原本能支持的并发用户数,现在服务器只能支持其1/51/101/15甚至更少的并发用户数。另外,开发人员只是在Windows平台本身上运行数据库,这是一个平常的Windows XP服务器,而没有使用Datacenter版本的Windows。这说明,Windows单进程体系结构会限制Oracle数据库服务器总共只有大约1.75 GBRAM。由于每个Oracle连接要同时处理多条语句,所以Oracle连接通常比SQL Server连接占用更多的RAM(不过Oracle连接比SQL Server连接能干多了)。开发人员能否很好地扩缩应用,很大程度上受这个硬件的限制。尽管服务器上有8 GBRAM,但是真正能用的只有2 GB左右。

注意      Windows环境中还能通过其他办法得到更多的RAM,如利用/AWE开关选项,但是只有诸如Windows Server Datacenter Edition等版本的操作系统才支持这个选项,而在这个项目中并没有使用这种版本。

针对这个问题,可能的解决方案有3种,无论哪一种解决方案都需要做大量工作(另外要记住,这可是在原先以为“移植”已经结束的情况下补充的工作!)。具体如下:

q         重建应用,充分考虑到这样一个事实:应用是在Oracle上运行,而不是在另外某个数据库上;另外生成一个页面只使用一个连接,而不是515个连接。这是从根本上解决这个问题的惟一方法。

q         升级操作系统(这可不是小事情),使用Windows Datacenter版本中更大的内存模型(这本身就非区区小事,而且还会带来相当复杂的数据库安装,需要一些间接的数据缓冲区和其他非标准的设置)。

q         把数据库从Windows系列操作系统移植到另外某个使用多进程的操作系统,以便数据库使用所安装的全部RAM(重申一遍,这个任务也不简单)。

可以看到,以上都不是轻轻松松就能办到的。不论哪种方法,你都不会毫无芥蒂地一口应允“好的,我们下午就来办”。每种方案都相当复杂,所要解决的问题原本在数据库“移植”阶段修正才最为容易,那是你查看和修改代码的第一个机会。另外,如果交付生产系统之前先对“可扩缩性”做一个简单的测试,就能在最终用户遭遇苦痛之前及时地捕捉到这些问题。

2.      使用绑定变量

如果我要写一本书谈谈如何构建不可扩缩的Oracle应用,肯定会把“不要使用绑定变量”作为第一章和最后一章的标题重点强调。这是导致性能问题的一个主要原因,也是阻碍可扩缩性的一个重要因素。Oracle将已解析、已编译的SQL连同其他内容存储在共享池(shared pool)中,这是系统全局区(System Global Area SGA)中一个非常重要的共享内存结构。第4章将详细讨论共享池。这个结构能完成“平滑”操作,但有一个前提,要求开发人员在大多数情况下都会使用绑定变量。如果你确实想让Oracle缓慢地运行,甚至几近停顿,只要根本不使用绑定变量就可以办到。

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

select * from emp where empno = 123;

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

select * from emp where empno = :empno;

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

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

从前面的描述应该能清楚地看到,与重

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
目录回到顶部↑第1章 开发成功Oracle应用 1 1.1 我的方法 2 1.2 黑盒方法 4 1.3 开发数据库应用的正确(和不正确)方法 8 1.3.1 了解Oracle体系结构 8 1.3.2 理解并发控制 14 1.3.3 多版本 19 1.3.4 数据库独立性? 25 1.3.5 “怎么能让应用运行得更快?” 41 1.3.6 DBA与开发人员的关系 45 1.4 小结 46 第2章 体系结构概述 47 2.1 定义数据库和实例 48 2.2 SGA和后台进程 53 2.3 连接Oracle 56 2.3.1 专用服务器 56 2.3.2 共享服务器 57 2.3.3 TCP/IP连接的基本原理 58 2.4 小结 61 第3章 文件 63 .3.1 参数文件 64 3.1.1 什么是参数? 65 3.1.2 遗留的init.ora参数文件 67 3.1.3 服务器参数文件 69 3.1.4 参数文件小结 75 3.2 跟踪文件 76 3.2.1 请求的跟踪文件 77 3.2.2 针对内部错误生成的跟踪文件 80 3.2.3 跟踪文件小结 83 3.3 警告文件 83 3.4 数据文件 86 3.4.1 简要回顾文件系统机制 86 3.4.2 Oracle数据库中的存储层次体系 87 3.4.3 字典管理和本地管理的表空间 91 3.5 临时文件 93 3.6 控制文件 95 3.7 重做日志文件 95 3.7.1 在线重做日志 96 3.7.2 归档重做日志 98 3.8 密码文件 100 3.9 修改跟踪文件 103 3.10 闪回日志文件 104 3.10.1 闪回数据库 104 3.10.2 闪回恢复区 105 3.11 DMP文件(EXP/IMP文件) 106 3.12 数据泵文件 107 3.13 平面文件 110 3.14 小结 111 第4章 内存结构 113 4.1 进程全局区和用户全局区 113 4.1.1 手动PGA内存管理 114 4.1.2 自动PGA内存管理 121 4.1.3 手动和自动内存管理的选择 131 4.1.4 PGA和UGA小结 132 4.2 系统全局区 133 4.2.1 固定SGA 137 4.2.2 重做缓冲区 137 4.2.3 块缓冲区缓存 138 4.2.4 共享池 145 4.2.5 大池 148 4.2.6 Java池 149 4.2.7 流池 150 4.2.8 自动SGA内存管理 150 4.3 小结 151 第5章 Oracle进程 153 5.1 服务器进程 153 5.1.1 专用服务器连接 154 5.1.2 共享服务器连接 156 5.1.3 连接与会话 157 5.1.4 专用服务器与共享服务器 163 5.1.5 专用/共享服务器小结 166 5.2 后台进程 167 5.2.1 中心后台进程 168 5.2.2 工具后台进程 175 5.3 从属进程 178 5.3.1 I/O从属进程 178 5.3.2 并行查询从属进程 179 5.4 小结 179 第6章 锁 181 6.1 什么是锁? 181 6.2 锁定问题 184 6.2.1 丢失更新 184 6.2.2 悲观锁定 185 6.2.3 乐观锁定 187 6.2.4 乐观锁定还是悲观锁定? 197 6.2.5 阻塞 198 6.2.6 死锁 201 6.2.7 锁升级 206 6.3 锁类型 206 6.3.1 DML锁 207 6.3.2 DDL锁 215 6.3.3 闩 218 6.3.4 手动锁定和用户定义锁 226 6.4 小结 227 第7章 并发与多版本 229 7.1 什么是并发控制? 229 7.2 事务隔离级别 230 7.2.1 READ UNCOMMITTED 232 7.2.2 READ COMMITTED 233 7.2.3 REPEATABLE READ 235 7.2.4 SERIALIZABLE 237 7.2.5 READ ONLY 239 7.3 多版本读一致性的含义 240 7.3.1 一种会失败的常用数据仓库技术 240 7.3.2 解释热表上超出期望的I/O 241 7.4 写一致性 244 7.4.1 一致读和当前读 244 7.4.2 查看重启动 247 7.4.3 为什么重启动对我们很重要? 250 7.5 小结 251 第8章 事务 253 8.1 事务控制语句 254 8.2 原子性 255 8.2.1 语句级原子性 255 8.2.2 过程级原子性 257 8.2.3 事务级原子性 260 8.3 完整性约束和事务 260 8.3.1 IMMEDIATE约束 260 8.3.2 DEFERRABLE约束和级联更新 261 8.4 不好的事务习惯 263 8.4.1 在循环中提交? 264 8.4.2 使用自动提交? 270 8.5 分布式事务 271 8.6 自治事务 273 8.6.1 自治事务如何工作? 273 8.6.2 何时使用自治事务? 276 8.7 小结 279 第9章 redo与undo 281 9.1 什么是redo? 281 9.2 什么是undo? 282 9.3 redo和undo如何协作? 285 9.4 提交和回滚处理 289 9.4.1 COMMIT做什么? 289 9.4.2 ROLLBACK做什么? 296 9.5 分析redo 297 9.5.1 测量redo 298 9.5.2 redo生成和BEFORE/AFTER触发器 300 9.5.3 我能关掉重做日志生成吗? 306 9.5.4 为什么不能分配一个新日志? 310 9.5.5 块清除 312 9.5.6 日志竞争 315 9.5.7 临时表和redo/undo 317 9.6 分析undo 321 9.6.1 什么操作会生成最多和最少的undo? 321 9.6.2 ORA-01555: snapshot too old错误 323 9.7 小结 334 第10章 数据库表 335 10.1 表类型 335 10.2 术语 337 10.2.1 段 337 10.2.2 段空间管理 339 10.2.3 高水位线 340 10.2.4 freelists 342 10.2.5 PCTFREE和PCTUSED 345 10.2.6 LOGGING和NOLOGGING 348 10.2.7 INITRANS和MAXTRANS 349 10.3 堆组织表 349 10.4 索引组织表 352 10.5 索引聚簇表 368 10.6 散列聚簇表 376 10.7 有序散列聚簇表 386 10.8 嵌套表 390 10.8.1 嵌套表语法 390 10.8.2 嵌套表存储 399 10.8.3 嵌套表小结 402 10.9 临时表 402 10.10 对象表 410 10.11 小结 418 第11章 索引 421 11.1 Oracle索引概述 422 11.2 B*树索引 423 11.2.1 索引键压缩 426 11.2.2 反向键索引 429 11.2.3 降序索引 435 11.2.4 什么情况下应该使用B*树索引? 437 11.2.5 B*树小结 448 11.3 位图索引 448 11.3.1 什么情况下应该使用位图索引? 449 11.3.2 位图联结索引 453 11.3.3 位图索引小结 455 11.4 基于函数的索引 456 11.4.1 重要的实现细节 456 11.4.2 一个简单的基于函数的索引例子 457 11.4.3 只对部分行建立索引 465 11.4.4 实现有选择的惟一性 467 11.4.5 关于CASE的警告 467 11.4.6 关于ORA-01743的警告 469 11.4.7 基于函数的索引小结 470 11.5 应用域索引 470 11.6 关于索引的常见问题和神话 472 11.6.1 视图能使用索引吗? 472 11.6.2 Null和索引能协作吗? 472 11.6.3 外键是否应该加索引? 475 11.6.4 为什么没有使用我的索引? 476 11.6.5 神话:索引中从不重用空间 483 11.6.6 神话:最有差别的元素应该在最前面 486 11.7 小结 490 第12章 数据类型 491 12.1 Oracle数据类型概述 491 12.2 字符和二进制串类型 494 12.2.1 NLS概述 494 12.2.2 字符串 497 12.3 二进制串:RAW类型 504 12.4 数值类型 506 12.4.1 NUMBER类型的语法和用法 509 12.4.2 BINARY_FLOAT/BINARY_DOUBLE类型的语法和用法 513 12.4.3 非固有数值类型 513 12.4.4 性能考虑 514 12.5 LONG类型 515 12.5.1 LONG和LONG RAW类型的限制 516 12.5.2 处理遗留的LONG类型 517 12.6 DATE、TIMESTAMP和INTERVAL类型 523 12.6.1 格式 523 12.6.2 DATE类型 525 12.6.3 TIMESTAMP类型 533 12.6.4 INTERVAL类型 541 12.7 LOB 类型 544 12.7.1 内部LOB 545 12.7.2 BFILE 557 12.8 ROWID/UROWID类型 559 12.9 小结 560 第13章 分区 561 13.1 分区概述 561 13.1.1 提高可用性 562 13.1.2 减少管理负担 564 13.1.3 改善语句性能 569 13.2 表分区机制 571 13.2.1 区间分区 571 13.2.2 散列分区 574 13.2.3 列表分区 579 13.2.4 组合分区 581 13.2.5 行移动 583 13.2.6 表分区机制小结 585 13.3 索引分区 586 13.3.1 局部索引与全局索引 587 13.3.2 局部索引 587 13.3.3 全局索引 594 13.4 再论分区和性能 610 13.5 审计和段空间压缩 617 13.6 小结 618 第14章 并行执行 619 14.1 何时使用并行执行 620 14.2 并行查询 622 14.3 并行DML 628 14.4 并行DDL 631 14.4.1 并行DDL和使用外部表的数据加载 632 14.4.2 并行DDL和区段截断 634 14.5 并行恢复 643 14.6 过程并行化 643 14.6.1 并行管道函数 644 14.6.2 DIY并行化 648 14.7 小结 652 第15章 数据加载和卸载 655 15.1 SQL*Loader 655 15.1.1 用SQLLDR加载数据的FAQ 660 15.1.2 SQLLDR警告 686 15.1.3 SQLLDR小结 686 15.2 外部表 687 15.2.1 建立外部表 688 15.2.2 处理错误 693 15.2.3 使用外部表加载不同的文件 697 15.2.4 多用户问题 697 15.2.5 外部表小结 698 15.3 平面文件卸载 698 15.4 数据泵卸载 708 15.5 小结 710 索引 711
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值