频 道 直 达 - 新闻 - 培训 - 软件 - 教程 - 前沿 - 组网 - 系统应用 - 安全 - 编程 - 存储 - 操作系统 - 数据库 - 服务器 - 专题 - 产品 - 案例库 - 读书 - 博客 - BBS
51CTO.COM_中国最大的网络技术网站
找资料:

大数据量查询

作者: [美] Stéphane Faroult, Peter Robson 著 温昱 靳向. 出处:电子工业出版社  2008-03-27 11:34    砖    好    评论   进入论坛
阅读提示:《SQL语言艺术》主要讲了九种SQL经典查询场景以及其性能影响讨论实际案例,第四章深入讨论SQL查询,并研究如何根据不同情况的具体要求,来编写SQL语句。本文讲了大数据量查询。

大数据量查询
Querying Large Quantities of Data

越快剔除不需要的数据,查询的后续阶段必须处理的数据量就越少,自然查询的效率就越高,这听起来显而易见。集合操作符(set operator)是这一原理的绝佳应用,其中的union使用最为广泛,我们经常看到通过union操作将几个表“粘”在一起。中等复杂程度的union语句较为常见,大多数被连接的表都会同时出现在union两端的select 语句中。例如下面这段代码:

 select ...
from A,
B,
C,
D,
E1
where (condition on E1)
and (joins and other conditions)

union
select ...
from A,
B,
C,
D,
E2
where (condition on E2)
and (joins and other conditions)
这类查询是典型的“照搬式”编程。为了提高效率,可以仅对代码中非共用的表(本例中即E1和E2)使用union,然后配合筛选条件,把 union 语句降级为内嵌视图。代码如下:

select ...
from A,
B,
C,
D,
(select ...
from E1
where (condition on E1)
union
select ...
from E2
where (condition on E2)) E
where (joins and other conditions)


另一个“查询条件用错了地方”的经典例子,和在含有 group by 子句的查询中进行过滤操作有关。你可以过滤分了组的字段,也可以过滤聚合(aggregate)结果(例如检查 count() 的结果是否小于某阈值),或者同时过滤两者;SQL 允许在 having 子句中使用这类条件,但应该在 group by 完成后才进行过滤(比如排序之后再进行聚合操作)。任何影响聚合函数(aggregate function)结果的条件都应放在 having 子句中,因为在 group by 之前无从知道聚合函数的结果。任何与聚合无关的条件都应放在 where 子句中,从而减少为进行group by而必须执行的排序操作所处理的数据量。

现在回过头来看客户与订单的例子,我承认先前处理订单的方法比较复杂。在订单完成之前,必须经历几个阶段,这些都记录在表orderstatus中,该表的主要字段有:ordid(订单ID)、status、statusdate(时间戳)等,主键由ordid和statusdate组成。我们的需求是列出所有尚未标记为完成状态的订单(假设所有交易都已终止)的下列字段:订单号、客户名、订单的最后状态,以及设置状态的时间。最终,我们写出下列查询,滤掉已完成的订单,并找出订单当前状态:

select c.custname, o.ordid, os.status, os.statusdate
from customers c,
orders o,
orderstatus os
where o.ordid = os.ordid
and not exists (select null
from orderstatus os2
where os2.status = 'COMPLETE'
and os2.ordid = o.ordid)
and os.statusdate = (select max(statusdate)
from orderstatus os3
where os3.ordid = o.ordid)
and o.custid = c.custid
乍一看,这个查询很合理,但事实上,它让人非常担心。首先,上面代码中有两个子查询,但它们嵌入的方式和前一个例子的方式不同,它们只是彼此间接相关的。最让人担心的是,这两个子查询访问相同的表,而且该表在外层已经被访问过。我们编写的过滤条件质量如何呢?因为只检查了订单是否完成,所以它不是非常精确。

这个查询如何执行的呢?很显然,可以扫描 orders 表,检查每一条订单记录是否为已完成状态——注意,仅通过表 orders 即可找出所要信息似乎令人高兴,但实际情况并非如此,因为只有上述活动之后,才能检查最新状态的日期,即必须按照子查询编写的顺序来执行。

上述两个子查询是关联子查询,这很不好。因为必须要扫描 orders 表,这意味着我们必须检查 orders 的每条订单记录状态是否为 “COMPLETE”,虽然检查状态的子查询执行很快,但多次重复执行就不那么快了。而且,若第一个子查询没找到 “COMPLETE” 状态时,还必须执行第二个子查询。那么,何不试试非关联子查询呢?
要编写非关联子查询,最简单的办法是在第二个子查询上做文章。事实上,在某些 SQL 方言中,我们可以这么写:

and (o.ordid, os.statusdate) = (select ordid, max(statusdate)
from orderstatus
group by ordid)
这个子查询会对 orderestatus 作“全扫描”,但未必是坏事,下面会对此加以解释。
重写的子查询条件中,等号左端的“字段对”有点别扭,因为这两个字段来自不同的表,其实不必这样。我们想让orders和orderstatus的订单ID相等,但优化器能感知这一点吗?答案是不一定。所以优化器可能依然先执行子查询,依然要把orders和orderstatus这两个表连接起来。我们应该将查询稍加修改,使优化器更容易明白我们的描述,最终按照“先获得子查询的结果,然后再连接orders和orderstatus表”的顺序工作:

and (os.ordid, os.statusdate) = (select ordid, max(statusdate)
from orderstatus
group by ordid)
这次,等号左端的字段来自相同的表,从而不必连接orders和orderstatus这两个表了。尽管好的优化器可能会帮我们做到这一点,但保险起见,一开始就指定这两个字段来自相同的表是更明智的选择。为优化器保留最大的自由度总是上策。

前面已经看到了,非关联子查询可以变成内嵌视图,且改动不大。下面,我们写出“列出待办订单”的整个查询语句:

select c.custname, o.ordid, os.status, os.statusdate
from customers c,
orders o,
orderstatus os,
(select ordid, max(statusdate) laststatusdate
from orderstatus
group by ordid) x
where o.ordid = os.ordid
and not exists (select null
from orderstatus os2
where os2.status = 'COMPLETE'
and os2.ordid = o.ordid)
and os.statusdate = x.laststatusdate
and os.ordid = x.ordid
and o.custid = c.custid
但还有问题,如果最终状态确实是“COMPLETE”,我们就没有必要用子查询检查其最新状态了。内嵌视图能帮我们找出最后状态,无论它是不是“COMPLETE”。所以我们把查询改为“检查已知的最新状态”,这个过滤条件非常令人满意:

select c.custname, o.ordid, os.status, os.statusdate
from customers c,
orders o,
orderstatus os,
(select ordid, max(statusdate) laststatusdate
from orderstatus
group by ordid) x
where o.ordid = os.ordid
and os.statusdate = x.laststatusdate
and os.ordid = x.ordid
and os.status != 'COMPLETE'
and o.custid = c.custid
如果进一步利用 OLAP 或SQL 引擎提供的分析功能,还可以避免对orderstatus的重复参照。不过就此打住,来思考一下我们是如何修改查询的,更重要的是“执行路径(execution path)”为何。基本上,正常路径是先扫描orders表,接着利用orderstatus表上预计非常高效的索引进行访问。在最后一版的代码中,我们改用完整扫描orderstatus的方法,这是为了执行group by。orderstatus中的记录条数一定会比 orders 中的大好几倍,然而,只以要扫描的数据量来看,估计前者比较小(而且可能小很多),这取决于为每张订单保存了多少信息。

无法确定哪种方法一定更好,这一切都取决于实际数据。补充说明一点,最好别在预期会增大的表上做全表扫描操作(若能把搜索限制在最近一个月或几个月的数据上则会好些)。不过,最后一版的代码肯定比第一版的(在where子句用子查询)要好。

在结束“大数据量查询”的话题之前,有个特殊情况值得一提。当查询要返回非常大量的数据时,该查询很可能不是某个用户坐在电脑前敲入的命令,而是来自于某个批处理操作。即便“预备阶段”稍长,只要整个处理能达到令人满意的结果,就是可以接受的。当然,不要忘了,无论是不是预备阶段,都会需要资源——CPU、内存,可能还有临时磁盘空间。即使最基本的查询完全相同,优化器在返回大量数据时所选择的路径,仍可能会与返回少量数据时完全不同,了解这一点是有用的。

总结:尽早过滤掉不需要的数据。

【责任编辑:雪花 TEL:(010)68476606】

回书目   上一节   下一节
专题
Sun以10亿美元并购开源数据库厂商MySQL
Oracle数据库开发之PL/SQL基础应用
SQL Server 2008/2005全解
SQL Sever 2005新品推荐
MySQL5.0新品推介
我也说两句

匿名发表

(如果看不清请点击图片进行更换)


中 国 最 大 的 网 络 技 术 网 站 ·
技 术 成 就 梦 想
订阅技术快讯
电子杂志下载
名称:SQL Server数据库管理精品黄皮书
简介:书中文章经过精挑细选,便于用户能根据自己的实际工作和学习,快速在本书寻找到相关资料。内容涵盖了SQL Server的安装与升级、语句查询、数据备份和恢复、自动化任务、数据同步、数据字典、安全和预防、性能和优化、集群等各方面应用信息,以及DBA管理人员在数据库管理工作中
名称:2007路由技术大全
简介:《2007路由技术大全》由51CTO.com网站特别策划制作,该书包括路由器技术、路由器产品、路由器配置、安全设置、路由器故障处理、路由器密码恢复,以及广大网友在实践使用中的心得经验和技巧文章,内容注重实用性,适用于初学者入门,也适合多年从业者提高,是一本实践和理论完
名称:网络安全精品应用黄皮书
简介:《2007精品网络安全黄皮书》包括了9个大类24个小类, 800余篇文章,内容包含了熊猫烧香病毒、DDOS攻击、ARP病等热点问题的介绍及解决方案。从病毒查杀、防范、系统、数据等各方面的安全设置到黑客技术的了解、防范,涉及到了安全应用的全部领域, 由浅至深内容全面。
CISSP认证成长之路
CISSP认证成长之路
隐私保护技术探讨
隐私保护技术探讨
WCF开发基础
WCF开发基础
· WCF开发基础
· 珊瑚虫QQ作者侵权案开庭
· SQL Server 2008/2005..
· 华为员工自杀频频拷问..
· 计算机网络维护入门
· 开源虚拟化技术Xen
· 贝恩资本携手华为22亿..
· 如何优化IT 控制能耗
· VMware技术应用
· 打造安全服务器
· iSCSI应用与发展
· FTTx光纤接入
· Windows Server 2008专..
· Linux——从菜鸟到高手
· 微软出价446亿美元收购..
· RAID——磁盘阵列基础
清除流氓软件——51CTO特别专题
清除流氓软件——51CTO特别专..
ARP攻击防范与解决方案
ARP攻击防范与解决方案
iSCSI应用与发展
iSCSI应用与发展
· iSCSI应用与发展
· SQL Server 2008/2005..
· SQL Server 2008/2005..
· iSCSI应用与发展
· RAID——磁盘阵列基础
· 中间件应用技术专题
· 深入了解PGP加密技术
· 病毒查杀专题
· 国际文档格式标准开战
· SSL VPN详细知识
· Linux防火墙
· 打造安全服务器
· Sniffer安全技术从入门..
· 木马原理与防范
· ADSL应用面面俱到
· 入侵防护系统(IPS)初探
ARP攻击防范与解决方案
ARP攻击防范与解决方案
SQL Server 2008/2005全解
SQL Server 2008/2005全解
iSCSI应用与发展
iSCSI应用与发展
· iSCSI应用与发展
· 中间件应用技术专题
· SQL Server 2008/2005..
· iSCSI应用与发展
· RAID——磁盘阵列基础
· 身份认证技术
· 病毒查杀专题
· 清除流氓软件——51CTO..
· SSL VPN详细知识
· Sniffer安全技术从入门..
· 了解统一威胁管理(UTM)..
· 网络钓鱼
· ADSL应用面面俱到
· ADSL应用面面俱到
· 木马原理与防范
· 反垃圾邮件技术应用