友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
SQL语言艺术(PDF格式)-第2部分
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!
人们常常使用战术,而忽略了战略。战略要求从大局上把握整个架构与设计。和战争一样,战
略的基本原则并不多,且经常被忽视。架构错误的代价非常高,SQL 程序员必须准备充分,明
确目标,了解如何实现目标。在本章中,我们讨论编写高效访问数据库的程序需要实现哪些关
键目标。
查询的识别
Query Identification
QQuueerryy IIddeennttiiffiiccaattiioonn
数个世纪以来,将军通过辨别军装颜色和旗帜等来判断各部队的位置,以此检查激战中部队行
进情况。同样,当一些进程消耗了过多的 CPU 资源时,通常也可以确定是由哪些正被执行的
SQL 语句造成的。但是,要确定是应用的哪部分提交了这些SQL语句却困难得多,特别是复杂
的大型系统包含动态建立的查询的时候。尽管许多产品提供良好的监控工具,但要确定一小段
SQL语句与整个系统的关系,有时却非常困难。因此,要养成为程序和关键模块加注释的习惯,
在SQL中插入注释有助于辨别查询在程序中的位置。例如:
/* CUSTOMER REGISTRATION */ select blah 。。。
这些注释在查错时非常有用。另外,注释也有助于判断单独应用对服务器造成的负载有多大;
例如我们希望本地应用承担更多工作,需要判断当前硬件是否能承受突发高负载,这时注释特
别有用。
有些产品还提供了专门的记录功能(registration facilities),将你从“为每个语句加注释”的乏味
工作中解放出来。例如Oracle 的dbms_application_info包,它支持48个字
符的模块名称(module name)、32 个字符的动作名称(action name)和64个 字符的客户信
息,这些字段的内容可由我们定制。在 Oracle 环境下,你可以利用这个程序包记录哪个应用
正在执行,以及它在何时正在做什么。因为应用是通过“Oracle V 动态视图”(能显示目前内存
中发生的情况)向程序包传递信息的,于是我们可以轻易地掌握这些信息。
总结:易识别的语句有助于定位性能问题。
保持数据库连接稳定
Stable Database Connections
SSttaabbllee DDaattaabbaassee CCoonnnneeccttiioonnss
建立一个新的数据库连接,既快又方便,但这其中往往掩藏着重复建立数据库连接带来的巨大
开销。所以,管理数据库连接必须非常小心。允许多重连接——可能就藏在你的应用中——的
后果可能很严重,下面即是一例。
…………………………………………………………Page 7……………………………………………………………
不久前,我遇到一个应用,要处理很多小的文本文件。这些文本文件最大的也不超过一百行,
每一行包含要加载的数据及数据库等信息。此例中固然只有一个数据库实例,但即使有上百个,
这里所说明的原理也是适用的。
处理每个文件的代码如下:
Open the file
Until the end of fileisreached
Readarow
Connect tothe server specified bythe row
Insert the data
Disconnect
Close the file
上述处理工作令人满意,但当大量小文件都在极短的时间内到达时,可能应用程序来不及处理,
于是积压大量待处理文件,花费时间相当可观。
我用 C 语言编了个简单的程序来模拟上述情况,以说明频繁的数据库连接和中断所造成的系
统性能下降问题。表 2…1列出了模拟的结果。
注意
产生表 2…1结果的程序使用了常规的insert语句。顺便提一下,直接加载(direct…loading)的技
术会更快。
表2…1:连接/中断性能测试结果
测 试 结 果
依次对每一行作连接/中断 7。4 行/秒
连接一次,所有行逐个插入
1 681 行/秒
连接一次,以 10 行为一数组插入 5 914 行/秒
连接一次,以 100 行为一数组插入 9 190 行/秒
…………………………………………………………Page 8……………………………………………………………
此例说明了尽量减少分别连接数据库次数的重要性。对比表中前后两次针对相同数据库的插入
操作,明显发现性能有显著提升。其实还可以做进一步的优化。因为数据库实例的数量势必有
限,所以可以建立一组处理程序(handler)分别负责一个数据库连接,每个数据库只连接一次,
使性能进一步提高。正如表 2…1 所示,仅连接数据库一次(或很少次)的简单技巧,再加上一
点额外工作,就能让效率提升200倍以上。
当然,在上述改进的基础上,再将欲更新的数据填入数组,这样就尽可能减少了程序和数据库
核心间的交互次数,从而使性能产生了另一次飞跃。这种每次插入几行数据的做法,可以使数
据的总处理能力又增加了5倍。表 2…1 中的结果显示改进后的性能几乎是最初的 1 200 倍。
为何有如此大的性能提升?
第一个原因,也是最大的原因,在于数据库连接是很“重”的操作,消耗资源很多。
在常见的客户/服务器模式中(现在仍广为使用),简单的连接操作背后潜藏着如下事实:首先,
客户端与远程服务器的监听程序(listener program)建立联系;接着,监听程序要么创建一个
进程或线程来执行数据库核心程序,要么直接或间接地把客户请求传递给已存在的服务器进程,
这取决于此服务器是否为共享服务器。
除了这些系统操作(创建进程或线程并开始执行)之外,数据库系统还必须为每
次session建立新环境,以跟踪它的行为。建立新session前,DBMS还要检查密码是否与保存
的加密的账户密码相符。或许,DBMS还要执行登录触发器(logon trigger),还要初始化存储
过程和程序包(如果它们是第一次被调用)。上面这些还不包括客户端进程和服务器进程之间要
完成的握手协议。正因为如此,连接池(connection pooling)等保持永久数据库连接的技术对
性能才如此重要。
第二个原因,你的程序(甚至包括存储过程)和数据库之间的交互也有开销。
即使数据库连结已经建立且仍未中断,程序和 DBMS 核心之间的上下文切换(context switch)
也有代价。因此,如果 DBMS 支持数据通过数组传递,应毫不犹豫地使用它。如果该数组接
口是隐式的(API内部使用,但你不能使用),那么明智的做法是检查它的默认大小并根据具体
需要修改它。当然,任何逐行处理的方式都面临上下文切换的问题,并对性能产生严重影响—
—本章后面还会多次涉及此问题。
总结:数据库连接和交互好似万里长城——长度越长,传递消息越耗时。
战略优先于战术
Strategy Before Tactics
SSttrraatteeggyy BBeeffoorree TTaaccttiiccss
战略决定战术,反之则谬也。思考如何处理数据时,有经验的开发者不会着眼于细微步骤,而
是着眼于最终结果。要获得想要的结果,最显而易见的方法是按照业务规则规定的顺序按部就
班地处理,但这不是最有效的方法——接下来的例子将显示,刻意关注业务处理流程可能会使
…………………………………………………………Page 9……………………………………………………………
我们错失最有效的解决方案。
几年前,有人给了我一个存储过程,让我“尝试”着进行一下优化。为什么说是“尝试”呢?因为
该存储过程已经被优化两次了,一次是由原开发者,另一次是由一个自称Oracle 专家的人。但
尽管如此,这个存储过程的执行仍会花上20分钟,使用者无法接受。
此存储过程的目的,是根据现有库存和各地订单,计算出总厂需要订购的原料数量。大体上,
它就是把不同数据源的几个相同的表聚合(aggregate)到一个主表(master table)中。首先,
将每个数据源的数据插入主表;接着,对主表中的各项数据进行合计并更新;最后,将与合计
结果无关的数据从表中删除。针对每个数据源,重复执行上述步骤。所有 SQL 语句都不是特
别复杂,也没有哪个单独的SQL语句特别低效。
为了理解这个存储过程,我花了大半天时间,终于发现了问题:为什么该过程要用这么多步骤
呢?在from子句中加上包含 union 的子查询,就能得到所有数据源的聚合(aggregation)。一条
select 语句,只需一步就得到了结果集,而之前要通过插入目标表(target table)得到结果集。
优化后,性能的提升非常惊人——从 20 分钟减至 20 秒;当然,之后我花了一些时间验证了
结果集,与未优化前完全相同。
想要获得上述的大幅提高性能,无需特别技能,仅要求站在局外思考(think outside thebox)的
能力。之前两次优化因“太关注问题本身”而收到了干扰。我们需要大胆的思维,站得远一些,
试着从大局的角度看待问题。要问自己一些关键的问题:写存储过程之前,我们已有哪些数据?
我们希望存储过程返回什么结果?再辅以大胆的思维,思考这些问题的答案,就能得到一个性
能大幅提升的处理方式了。
总结:考虑解决方案的细节之前,先站得远一些,把握大局。
先定义问题,再解决问题
Problem Definition Before Solution
PPrroobblleemm DDeeffiinniittiioonn BBeeffoorree SSoolluuttiioonn
一知半解是危险的。人们常在听说了新技术或特殊技术之后——有时的确很吸引人——试图采
用它作为新的解决方案。普通开发者和设计师通常会立即采纳这些新“解决方案”,直到后来才
发现它们会产生许多后续问题。
现成的解决方案中,非规范化设计引人注目。设计伊始,非规范化设计的拥护者就提出此方案,
为了寻求“性能”而无视最终将会面临的升级恶魔——而事实上,在开发周期早期,改进设计(或
学习如何使用join)也是一个不错的选择。作为非规范化设计的一种手段,物化视图(materialized
view)常被认为是灵丹妙药。物化视图有时被称为快照(snapshot),这个更加平常的词更形象
地反映了可悲的事实:物化视图是某时间点的数据副本。在没有其他办法时,这个理论上遭到
质疑的技术也未尝不值得一试,借用卡夫卡(Franz Kafka)的一句名言:“逻辑诚可贵,生存价
更高。”
然而,绝大部分问题都可借助传统技术巧妙解决。首先,应学会充分利用简单、传统的技术。
只有完全掌握了这些技术,才能正确评价它们的局限性,最终发现它相当于新技术的潜在优势
(如果有的话)。
所有技术方案,都只是我们达到目标的手段。没有经验的开发者误把新技术本身当成了目标。
…………………………………………………………Page 10……………………………………………………………
对于热衷于技术、过于看重技术的人来说,此问题就更为严重。
总结:先打基础,再赶时髦:摆弄新工具之前,先把手艺学好。
直接操作实际数据
OperationsAgainst Actual Data
OOppeerraattiioonnssAAggaaiinnsstt AAccttuuaall DDaattaa
许多开发者喜欢建立临时工作表(temporaryworktable),把后续处理使用的大量数据放入其中,
然后开始“正式”工作。这种方法广受质疑,反映了“跳出业务流程细节考虑问题”的能力不足。
记住,永久表(permanent table)可以设置非常复杂的存储选项(在第5章将讨论一些存储选项
的设置),而临时表不能。临时表的索引(如果有的话)可能不是最优的,因此,查询临时表的
语句效率比永久表的差。另外,查询之前必然先为临时表填入数据,这自然也多了一笔额外的
开销。
就算使用临时表有充足理由,若数据量大,也绝不能把永久表当作临时工作表来用。问题之一
在于统计信息的自动收集:若没有实时收集要求,DBMS通常会在不活动或活动少时进行统计
信息收集,而这时作为临时工作表可能为空,从而使优化器收到了完全错误的信息。这些不正
确且有偏差的统计信息可能造成执行计划(execution plan)完全不合理,导致性能下降。所以,
如果一定要用临时表,应确保数据库知道哪些表是临时的。
总结:暂时工作表意味着以不太合理的方式存储更多信息。
SQL
用SSQQLL处理集合
Set Processing in SQL
SSeett PPrroocceessssiinngg iinn SSQQLL
SQL 完全基于集合(Set)来处理数据。对大部分更新或删除操作而言 —— 如果不是针对整
个表的话 —— 你必须先精确定义出要处理的记录的集合。这定义了该处理的粒度
(granularity),可能是对大量记录的粗粒度操作,有可能是只影响少数记录的细粒度操作。
将一次“大批量数据的处理”分割成多次“小块处理”是个坏主意,除非对数据库的修改太昂贵,
否则不要使用,因为这种方法极其低效:
(1)占用过多的空间保存原始数据,以备事务(transaction)回滚(rollback)之需;
(2)万一修改失败,回滚消耗过长的实践。
许多人认为,进行大规模修改操作时,应在操作数据的代码中有规律地多安排些mit命令。
其实,严格从实践角度来讲,“从头开始重做”比“确定失败发生的时间和位置,接着已提交部分
重做”要容易得多、简单得多、也快得多。
处理数据时,应适应数据库的物理实现。考虑事务失败时回滚所需日志的大小,如果要为undo
保存的数据量确实巨大,或许应该考虑数据修改的频率问题。也就是说,将大规模的“每月更新”,
改为规模不大的“每周更新”,甚至改为规模更小的“每日更新”,或许是个有效方案。
总结:几千个语句,借助游标(cursor)不断循环,很慢。换成几个语句,处理同样的数据,
还是较慢。换成一个语句,解决上述问题,最好。
…………………………………………………………Page 11……………………………………………………………
SQL
动作丰富的SSQQLL语句
Action…Packed SQL Statements
AAccttiioonn……PPaacckkeedd SSQQLL SSttaatteemmeennttss
SQL 不是过程性语言(procedurallanguage),尽管也可以将过程逻辑(proce
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!