友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
第三电子书 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

SQL语言艺术(PDF格式)-第7部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!


  结果集的大小 

  获得结果集所涉及的表的数量 

  多少用户会同时修改这些数据 



数据总量 



Total Quantity of Data 

必须访问的数据总量,是要考虑的最重要因素。一个查询方案,用于只有14 行数据的 emp表 

和4行数据的dept表时表现非常出色,但它可能完全不适用于有1 500万行数据的 

financial_flows 表与有 500 万行数据的 products 表的join操作。注意,以许多公司的标准来 

看,1 500 万行的表并不算特别大。所以结论是,没有确定目标容量之前,很难断定查询执行 

的效率。 



定义结果集的查询条件 



CriteriaDefining the Result Set 

在编写 SQL 语句时,多数情况下会涉及 where 子句的条件,而在子查询或视图(普通视图 

或内嵌视图)中可能有多个 where 子句。然而,过滤条件的效率有高有低,这会受到其他因 

素的极大影响,例如物理实现(将在第5章中讨论)及要访问的数据量等因素。 



为了定义结果集,必须从几个方面来考虑,包括过滤、主要SQL语句,以及庞大的数据量对查 

询的影响等。这是个复杂的问题,须做深度探讨,详见本章“过滤”一节。 



结果集的大小 

Size of the Result Set 

查询所返回的数据量(或是SQL语句改动的数据量),是个重要且常被忽略的因素。一般而言, 

这取决于表的大小和过滤条件的细节,但不都是这样。典型的情况是,若干个独立使用时效率 

不高的条件,结合起来使用时会产生极高的效率;例如,以“是否获得理工科或文科学位”作为 

查询学生姓名的条件,结果集会非常大,但如果同时使用这两个条件(获得这两个学位),则产 

生的结果集就会大幅缩小。 



从技术的角度来看,查询结果集的大小并不重要,重要的是最终用户的感觉。用户的耐心,在 

很大的程度上和预期返回的记录条数有关:用户只检索一条记录,则他期望非常快,他不会关 

心整个数据库有多大。更极端的例子是,查询之后并未返回任何结果:好的开发者都会努力使 


…………………………………………………………Page 36……………………………………………………………

返回少量记录或不返回记录的查询尽量快,因为对用户而言,最令人沮丧的事莫过于等待了数 

分钟后,看到“无相符数据”的结果;若是按下回车键后马上察觉查询语句有误,而又无法终止 

查询,等待就更为恼人。最终用户情愿等待的,是预期返回大量数据时。如果把每个过滤条件 

定义的特定结果集看作中间结果,而最终结果是它们的交集(在条件中用and相连)或并集(在 

条件中用or相连),那么小型中间结果集的交集很可能为空。换言之,更精确的条件经常是零结 

果集产生的主要原因。无论何时,只要查询有可能返回零结果集时,都应该先检查那个最大可 

能导致空结果集的条件——尤其是在该检查执行非常快捷时。不用说,条件的顺序与条件所在 

上下文的关系十分密切,这在稍后“过滤”一节中讲述。 



总结:熟练的开发者应该努力使响应时间与返回的记录数成比例。 



表的数量 



Number of Tables 

查询中涉及的表的数量,自然会对性能有所影响。这不是因为 DBMS 引擎不能很好地执行连 

接操作——恰恰相反,现代的DBMS都能非常高效地连接很多表。 



    Join 

连接(JJooiinn) 

认为连接效率不高的想法,来自另一个对关系数据库的成见。通常的说法是不该连接太多表, 

建议的上限是 5 个。事实上,连接 15 个表也一样可以极高效地执行。但在连接大量表时, 

会产生一些额外的问题。 

  当需要连接多个表时(例如 15 个),按常理你就应该质疑设计的正确性。回忆一下第1章的 



内容—— 表的一条记录陈述了某个事实,而且可以将它比作数学的公理,通过连接表的操作, 

可衍生出其他事实。但要清楚一点,即哪些是显而易见的事实,可以称为公理;哪些是较不明 

显的事实,必须推衍得到。如果我们需要花大量时间来推衍事实,或许最初选择的公理就不合 

适。 



  对于优化器来说,随着表数量的增加,复杂度将呈指数增长。再次提醒,统计优化器通常有 



出色的表现,但同时其耗时在查询总响应时间中的比例也很高,尤其是在查询第一次执行时。 

如果表比较多,让优化器分析所有可能的查询路径,是非常不切实际的。除非查询语句是为方 

便优化器刻意编写的,否则,查询越复杂,优化器越容易“押错宝(bet on the wrong horse)”。 



  编写涉及许多表的复杂查询时,若可以用好几种截然不同的方式进行连接,最终选择失误的 



几率很高。如果我们连接表 A、B、C 和 D,优化器可能没有足够的信息判断出A 直接与 D 连 

接的效率会很高。想以 distinct 解决记录重复问题的开发者,也常会遗漏连接条件。 



复杂查询与复杂视图 



我们必须明白,表面上看到的参与查询的表的数量可能不真实,有些表实际上是视图,它们有 

时很复杂。和查询一样,视图的复杂程度也差异极大。视图可以屏蔽字段、记录、甚至是字段 

和记录的组合,只让少数有权限的用户可以访问。视图从特定视角反映数据,从表的现存关系 

中推衍出新的关系。此时,视图可以看作查询的简略表达方式,这是视图最常见的用途之一。 

随着查询复杂度的增加,似乎应该把查询拆成一系列独立视图,每个视图代表复杂查询的一部 

分。 



总结:表明简单的查询背后,可能隐藏着复杂的视图。 


…………………………………………………………Page 37……………………………………………………………

不要走极端,完全不使用视图也不合理,一般它们并无坏处。然而,将视图用在复杂查询中时, 

我们多半只对视图返回数据中的一小部分感兴趣——可能是几十个字段中的几个字段——这 

时,优化器会试图将简单视图重新并入一段更大的查询语句中。但是,一旦查询复杂到一定程 

度,此方法就太复杂了,以至于难以保证效率。 



在某些情况下,视图的编写方式,能有效地预防优化器把它并入上级语句中。我已提过 rownum, 

那是 Oracle 使用的虚拟字段,用来显示记录最初被查到时的顺序。如果在视图中使用rownum, 

复杂性会进一步增加。任何想把参照了 rownum 的视图并入上级查询中的尝试,都会改变后续 

rownum 的顺序,所以此时不允许优化器改写查询。于是,复杂查询中这种视图将独立执行。 

DBMS 优化器常把视图原样并入语句中,把它当成语句执行的一步来运行(注2),而且只使用 

视图执行结果中所需要的部分。 



视图中执行的操作(典型的例子是通过join获取ID号对应的描述信息),往往与其所属查询的上 

下文无关;或者,查询条件很特殊,会淘汰组成视图的一些表。例如,对若干个表进行union 

得到的视图,代表了多个子类型,而上级查询的过滤器只针对其中一个子类型,所以unio其实 

是不必要的。将“视图”与“视图中出现的表”进行join也有危险,这会强制多次扫描该表并多次访 

问相同记录,但其实只扫描一次就足够了。 



当视图返回的数据远多于上级查询所需时,放弃使用该视图(或改用一个较简单的视图),通常 

可使效率大为改善。首先,用SQL 查询取代主查询中用到的视图。对视图的组成部分有了整体 

的了解之后,要去除严格意义上不必要的部分就容易多了。改用较简单视图的效果也不错,从 

查询中去除了不必要部分,执行速度快多了。 



许多开发者不愿在复杂查询中,再引入复杂的视图,他们认为这会使情况更为复杂。推导与分 

解复杂的SQL表达式的确有点令人生畏,不过,和高中时常做的数学表达式推导也差不多。在 

我看来,这有助于形成良好的编程风格,值得花些时间去掌握。对于渴望提高编程技巧的开发 

者来说,研究上述技巧有利于对查询内部工作原理的深入了解,常常使你受益匪浅。 



总结:当视图返回不必要的元素时,别把视图内嵌在查询中,而是应将视图分解,将其组成部 

分加到查询主体中。 



并发用户数 



Number of other Users 



最后,在设计 SQL 程序时,并发性(concurrency)是个必须认真对待的因素。写数据库时需 

要关注并发性:是否存在数据块访问争用(block…access contention)、阻塞(locking)、或闩 

定(latching)(DBMS内部资源阻塞)等重要问题;甚至有时,为保证读取一致性(read 

consistency)也会导致某种程度的资源争用。任何服务器的处理能力都是有限的,不管其说明 

书有多令人震撼。在机器相同的情况下,很少并发或没有并发操作时设计可能是完美的,但对 

有大量并发操作的情况未必完美。排序操作可能没有足够内存可用,于是转而求助于磁盘,引 

发了新的资源争用……一些计算密集型(CPUintensive)操作——例如负责复杂计算的函数、 

索引区块的重复扫描,均可引起计算机负荷过多。我遇到过一些案例,增加物理 I/O 会使任务 

执行效率更高,因为其中计算密集操作的并发执行程度很高,一个进程刚因等待 I/O 而阻塞, 

被释放的CPU就被另一个进程占用了,这样一来CPU资源就被充分利用了。一般而言,我们必 

须考虑特定商业任务的整体吞吐量(throughput),而不是个别用户的响应时间(response…time)。 


…………………………………………………………Page 38……………………………………………………………

注意 



第9章将更详细地探讨并发性。 



过滤 



Filtering 



如何限定结果集是最关键的因素之一,有助于你在编写 SQL 语句时判断该用哪些技巧。用来 

过滤数据的所有准则,通常被视为是 where 子句中各种各样的条件,我们必须认真研究各种 

where 子句(及 having 子句)。 



过滤条件的含义 



Meaning of Filtering Conditions 

若从SQL语法来看,where子句中表达的所有过滤条件当然大同小异。但事实并非如此。有些 

过滤条件通过关系理论直接作用于select 运算符:where子句检查记录中的字段是否与指定条 

件相符。但其实,where 子句中的条件还可以使用另一个关系运算符 join。自从 SQL92 出现 

join 语法后,人们就试图将“join过滤条件”(位在主 from 子句和 where 子句之间)和“select 

过滤条件”(位于where子句内)区分开来。从逻辑上讲,连接两个(或多个)表建立了新的关 

系。 

下面是个常见的连接(join)的例子: 

select 。。。。。 

from t1 

inner join t2 

on t1。join1 = t2。joind2 

where 。。。 



假设表t2中有一字段c2,该不该把 c2上的条件当作 inner join 的额外条件呢?即是否应认为参 

与连接的不是“t2表”而是“t2表的子集”呢?或者,假设where 子句中有一些关于t1 字段的条件, 

那么这些条件是否会应用到 t1 连接 t2 的结果呢?连接条件放在何处应该都一样,然而其运 

行效率却会因优化器不同而异。 



除了连接条件和简单的过滤条件之外,还有其他种类的条件。例如,规定返回的记录集为某种 

子类型的条件,以及检查另一个表内是否存在特定数据的条件。虽然从 SQL 语法上看它们相 

似,但在语义上却未必完全相同。有时条件的计算顺序无足轻重,但有时却影响重大。 



下面的例子说明了条件计算顺序的重要性,实际上,在许多商用软件包中都能找到这样的例子。 

假设有个 parameters 表,它包含字段:parameter_name、parameter_type、 



parameter_value,无论由parameter_type定义了什么参数属性,其中 parameter_ value 均以 

字符串表示。(从逻辑上来说,上述情况堪比罗密欧与茱莉叶的悲剧,因为属性parameter_value 

所表示的领域类型非常多,所以违反了关系理论的主要规则。)假设进行如下查询: 

  select * from parameters 


…………………………………………………………Page 39……………………………………………………………

  where parameter_name like'%size' 

  and parameter_type = 'NUMBER' 



在这个查询中,无论先计算两个条件中的哪一个,都无关紧要。然而,如果增加了以下条件, 

计算的顺序就变得非常重要了,其中int()是将字符转换为整数值的函数: 

   and int(parameter_value) 》 1000 



这时,parameter_type上的条件必须先计算,而parameter_value上的条件后计算,否则会因为 

试图把非数字字符串转换为整数,而造成运行时错误(假设 parameter_ type字段的类型定义 

为char)。如果你无法向数据库说明这一点,那么优化器也无从知道哪个条件应该有较高的优先 

权。 



总结:查询条件是有差异的,有的好,有的差。 



过滤条件的好坏 



Evaluation of Filtering Conditions 

编写 SQL 语句时,应首先考虑的问题是: 



  哪些数据是最终需要的,这些数据来自哪些表? 



  哪些输入值会传递到 DBMS 引擎? 

  哪些过滤条件能滤掉不想要的记录? 



然而要清楚的是,有些数据(主要是用来连接表的数据)可能冗余地存储在几个表中。所以, 

即使所需的返回值是某表的主键,也不代表这个表必须出现在from子句中,这个主键可能会以 

外键的形式出现在另一个表中。 



在编写查询之前,我们甚至应该对过滤条件进行排序,真正高效的条件(可能有多个,涉到不 

同的表)是查询的主要驱动力,低效条件只起辅助作用。那么定义高效过滤条件的准则是什么 

呢?首先,要看过滤条件能否尽快减少必须处理的数据量。所以,我们必须倍加关注条件的编 

写方式,下面通过例子说明这一点。 



蝙蝠车买主 



假设有四个表: customers、orders、orderdetail、articles,如图4…5所示。注意,图中各表的 

方框大小不同,这代表各表的数据量大小,而不代表字段数量;加下划线的字段为主键。 

现在假设 SQL 要处理的问题是:找出最近六个月内居住在Gotham市、订购了蝙蝠车的所有客 

户。当然,编写这个查询有多种方法,ANSISQL的推崇者可能写出下列语句: 

  select distinct c。custname 

  from customersc 

  join orders o 

  on o。custid = c。custid 

  join orderdetail od 

  on od。ordid = o。ordid 

返回目录 上一页 下一页 回到顶部 0 0
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!