《SQL语言艺术(PDF格式)》第16章


只有上述活动之后,才能检查最新状态的日期,即必须按照子查询编写的顺序来执行。 
上述两个子查询是关联子查询,这很不好。因为必须要扫描 orders 表,这意味着我们必须检 
查 orders 的每条订单记录状态是否为 “PLETE”,虽然检查状态的子查询执行很快,但多 
次重复执行就不那么快了。而且,若第一个子查询没找到 “PLETE” 状态时,还必须执行 
第二个子查询。那么,何不试试非关联子查询呢? 
要编写非关联子查询,最简单的办法是在第二个子查询上做文章。事实上,在某些 SQL 方言 
中,我们可以这么写: 
and (o。ordid; os。statusdate) = (select ordid; max(statusdate) 
from orderstatus 
group by ordid) 
这个子查询会对 orderestatus 作“全扫描”,但未必是坏事,下面会对此加以解释。 
重写的子查询条件中,等号左端的“字段对”有点别扭,因为这两个字段来自不同的表,其实不 
必这样。我们想让orders和orderstatus的订单ID相等,但优化器能感知这一点吗?答案是不一 
…………………………………………………………Page 47……………………………………………………………
定。所以优化器可能依然先执行子查询,依然要把orders和orderstatus这两个表连接起来。我 
们应该将查询稍加修改,使优化器更容易明白我们的描述,最终按照“先获得子查询的结果,然 
后再连接orders和orderstatus表”的顺序工作: 
and (os。ordid; os。statusdate) = (select ordid; max(statusdate) 
from orderstatus 
group byordid) 
这次,等号左端的字段来自相同的表,从而不必连接orders和orderstatus这两个表了。尽管好 
的优化器可能会帮我们做到这一点,但保险起见,一开始就指定这两个字段来自相同的表是更 
明智的选择。为优化器保留最大的自由度总是上策。 
前面已经看到了,非关联子查询可以变成内嵌视图,且改动不大。下面,我们写出“列出待办订 
单”的整个查询语句: 
select c。custname; o。ordid; os。status; os。statusdate 
from customersc; 
orders o; 
orderstatus os; 
(select ordid; max(statusdate) laststatusdate 
from orderstatus 
group byordid) x 
where o。ordid = os。ordid 
and not exists (select null 
from orderstatus os2 
where os2。status = "PLETE" 
and os2。ordid = o。ordid) 
and os。statusdate = x。laststatusdate 
and os。ordid = x。ordid 
and o。custid = c。custid 
但还有问题,如果最终状态确实是“PLETE”,我们就没有必要用子查询检查其最新状态了。 
内嵌视图能帮我们找出最后状态,无论它是不是“PLETE”。所以我们把查询改为“检查已知 
的最新状态”,这个过滤条件非常令人满意: 
select c。custname; o。ordid; os。status; os。statusdate 
from customers c; 
orders o; 
orderstatus os; 
(select ordid; max(statusdate) laststatusdate 
from orderstatus 
group byordid) x 
where o。ordid = os。ordid 
and os。statusdate = x。laststatusdate 
and os。ordid = x。ordid 
and os。status != "PLETE" 
…………………………………………………………Page 48……………………………………………………………
and o。custid = c。custid 
如果进一步利用 OLAP 或SQL 引擎提供的分析功能,还可以避免对orderstatus的重复参照。 
不过就此打住,来思考一下我们是如何修改查询的,更重要的是“执行路径(execution path)” 
为何。基本上,正常路径是先扫描orders表,接着利用orderstatus表上预计非常高效的索引进 
行访问。在最后一版的代码中,我们改用完整扫描orderstatus的方法,这是为了执行group by。 
orderstatus中的记录条数一定会比 orders 中的大好几倍,然而,只以要扫描的数据量来看, 
估计前者比较小(而且可能小很多),这取决于为每张订单保存了多少信息。 
无法确定哪种方法一定更好,这一切都取决于实际数据。补充说明一点,最好别在预期会增大 
的表上做全表扫描操作(若能把搜索限制在最近一个月或几个月的数据上则会好些)。不过,最 
后一版的代码肯定比第一版的(在where子句用子查询)要好。 
在结束“大数据量查询”的话题之前,有个特殊情况值得一提。当查询要返回非常大量的数据时, 
该查询很可能不是某个用户坐在电脑前敲入的命令,而是来自于某个批处理操作。即便“预备阶 
段”稍长,只要整个处理能达到令人满意的结果,就是可以接受的。当然,不要忘了,无论是不 
是预备阶段,都会需要资源——CPU、内存,可能还有临时磁盘空间。即使最基本的查询完全 
相同,优化器在返回大量数据时所选择的路径,仍可能会与返回少量数据时完全不同,了解这 
一点是有用的。 
总结:尽早过滤掉不需要的数据。 
取出数据在表中的比例 
The Proportions of Retrieved Data 
有个典型的说法:当查询返回的记录数超过表中数据总量的 10% 时,就不要使用索引。这种 
说法暗示,当(常规)索引的键指向表中不足10%的记录时,它是高效的。正如第3章中所指出 
的,这个经验法则建立于许多公司仍对关系数据库有所怀疑的年代,那时,关系数据库一般用 
于部门级数据库,包含十万行数据的表就被认为是大型表。与含有五亿行数据的表相比,十万 
行的 10% 不值一提。所以,执行计划“佳者恒佳”仅是个美好的愿望罢了。 
就算不考虑“10%的记录”这条“经验法则(rule of thumb)”产生的年代(现在的表大小早已今非 
昔比了),要知道,返回的记录数除了与期望响应时间有关之外,它本身并无意义。例如,计算 
十亿行数据的某字段的平均值,虽然返回结果只有一行,但DBMS 要做大量工作。甚至没有任 
何聚合处理,DBMS要访问的数据页的数量也会造成影响。因为要访问的数据页并非只依赖索 
引:第3章曾指出,表中记录的物理顺序与索引顺序是否一致,对要访问的页数有极大影响;第 
5章将讨论的一些物理实现也会造成影响,由于数据的物理存储方式不同,检索出相同数量的记 
…………………………………………………………Page 49……………………………………………………………
录所要访问的数据页数量可能差异很大;此外,有的访问路径将以串行方式执行,有的则以大 
规模并行(parallelized)方式执行……。因此,再别拿“10%的记录”这根鸡毛当令箭了。 
总结:当查询的结果集很大时,索引未必必要。 
SQL “ ” 
SSQQLL语句为了返回结果集或更改数据,必须访问一定数量的数据。““战斗””的环境和条件,决定 
“ ” 4 “ ” 
了我们““进攻””那些数据的方法。就如第44章所讨论的,““进攻””取决于:结果集的数据量、必须 
“ ” 
访问的数据量、可动用的““部队””(过滤条件)。 
任何大型的、复杂的查询,都可以被分成一连串较简单的步骤,其中一些步骤可以并行执行, 
就像综合战役通常要面对敌军的不同部队。每次战斗的结果差异可能很大,但关键是最后的综 
合结果。 
当我们分析查询的每个步骤时可能不会深入执行细节,但这些步骤可能的组合数量跟国际象棋 
不相上下,可以非常复杂。 
本章讨论存取经过适当规范化的数据时,经常遇到的情况。虽然本章主要讨论查询,但也适用 
where 
于更新和删除操作,只要它们也有wwhheerree 子句,毕竟要
小说推荐
返回首页返回目录