为什么运筹学软件当中总是提示广州 兼职 数据输入入不正确?

天骄2活力版WIN7 问题,工具和数据库都正确安装成功,但是启动后总是提示图形验证码不正确!望高手解答!_百度知道
天骄2活力版WIN7 问题,工具和数据库都正确安装成功,但是启动后总是提示图形验证码不正确!望高手解答!
但是可以装,也没有报错。会不会和这个有关系WIN7 我装的是SQL 2000,版本不符,安装之前有个提示
我有更好的答案
按默认排序
microsoft sql 2000 sp4安装了没有
其他类似问题
天骄2的相关知识
等待您来回答
下载知道APP
随时随地咨询
出门在外也不愁SQL语言艺术.pdf - 下载频道
- CSDN.NET
&&&&SQL语言艺术.pdf
SQL语言艺术.pdf
SQL SQL SQL 语言艺术
本书分为 12 章,每一章包含许多原则或准则,并通过举例的方式对原则进行解释说明。这些例
子大多来自于实际案例,对九种 SQL 经典查询场景以及其性能影响讨论,非常便于实践,为你
的实际工作提出了具体建议。本书适合 SQL 数据库开发者、软件架构师,也适合 DBA ,尤其是
数据库应用维护人员阅读。
资深 SQL 专家 St é phane Faroult 倾力打造
《软件架构设计》作者温昱最新译作
巧妙借鉴《孙子兵法》的智慧结晶
传授 25 年的 SQL 性能与调校经验
深入探讨九种常见查询方案及其性能
过去, “ 信息技术( IT ) ” 的名字还不如今天这般耀眼,被称为 “ 电子数据处理 ” 。其实,尽管当
今新潮技术层出不穷,数据处理依然处于我们系统的核心地位,而且需管理的数据量的增长速
度似乎比处理器的增长速度还快。今天,最重要的集团数据都被保存在数据库中,通过 SQL 语
言来访问。 SQL 语言虽有缺点,但非常流行,它从 1980 年代早期开始被广泛接受,随后就所向
如今,年轻开发者在接受面试时,没有谁不宣称自己能熟练应用 SQL 的。 SQL 作为数据库访问
语言,已成为任何基础 IT 课程的必备部分。开发者宣传自己熟练掌握 SQL ,其实前提是 “ 熟练 掌
握 ” 的定义是 “ 能够获得功能上正确的结果 ” 。然而,全世界的企业如今都面临数据量的爆炸式 增
长,所以仅做到 “ 功能正确 ” 是不够的,还必须足够快,所以数据库性能成了许多公司头疼的问
题。有趣的是,尽管每个人都认可性能问题源自代码,但普遍接受的事实则是开发者的首要关
注点应该是功能正确。人们认为:为了便于维护,代码中的数据库访问部分应该尽量简单; “ 拙
劣的 SQL ” 应该交给资深的 DBA 去摆弄,他们还会调整几个 “ 有魔力 ” 的数据库参数,于是速度 就
快了 —— 如果数据库还不够快,似乎就该升级硬件了。
往往就是这样,那些所谓的 “ 常识 ” 和 “ 可靠方法 ” 最终却是极端有害的。先写低效的代码、后由
专家调优,这种做法实际上是自找麻烦。本书认为,首先要关注性能的就是开发者,而且 SQL
问题绝不仅仅只包含正确编写几个查询这么简单。开发者角度看到的性能问题和 DBA 从调优角
度看到的大相径庭。对 DBA 而言,他尽量从现有的硬件(如处理器和存储子系统)和特定版本
的 DBMS 获得最高性能,他可能有些 SQL 技能并能调优一个性能极差的 SQL 语句。但对开发者 而
言,他编写的代码可能要运行 5 到 10 年,这些代码将经历一代代的硬件,以及 DBMS 各种 重要 版本升级(例如支持互联网访问、支持网格,不一而足)。所以,代码必须从一开始就快速、健 全 。
很多开发者仅仅是 “ 知道 ” SQL 而已,他们没有深刻理解 SQL 及关系理论,实在令人遗憾。
为何写作本书
SQL 书主要分为三种类型:讲授具体 SQL 方言的逻辑和语法的书、讲授高级技术及解决问题方
法的书、专家与资深 DBA 所需的性能和调优的书。一方面,书籍要讲述如何写 SQL 代码;另一
方面,要讲如何诊断和修改拙劣的 SQL 代码。在本书中,我不再为新手从头讲解如何写出优秀
的 SQL 代码,而是以超越单个 SQL 语句的方式看待 SQL 代码,无疑这更加重要。
教授语言使用就够难了,那么本书是怎样讲述如何高效使用 SQL 语言的呢? SQL 的简单性具有
欺骗性,它能支持的情况组合的数目几乎是无限的。最初,我觉得 SQL 和国际象棋很相似,后
来,我悟到发明国际象棋是为了教授战争之道。于是,每当出现 SQL 性能难题的时候,我都自
然而然地将之视为要和一行行数据组成的军队作战。最终,我找到了向开发者传授如何有效使
用数据库的方法,这就像教军官如何指挥战争。知识、技能、天赋缺一不可。天赋不能传授,
只能培养。从写就了《孙子兵法》的孙子到如今的将军,绝大多数战略家都相信这一点,于是
他们尽量以 简单的格言或规则的方式表达沙场经验,并希望这样能指导真实的战争。我将这种
方法用于战争之外的许多领域,本书借鉴了孙子兵法的方法和书的题目。许多知名 IT 专家冠以
科学家称号,而我认为 “ 艺术 ” 比 “ 科学 ” 更能反映 IT 活动所需的才能、经验和创造力(注 1 )。很
可能是由于我偏爱 “ 艺术 ” 的原因, “ 科学 ” 派并不赞成我的观点,他们声称每个 SQL 问题都可通
过严格分析和参考丰富的经验数据来解决。然而,我不认为这两种观点有什么不一致。明确的
科学方法有助于摆脱单个具体问题的限制,毕竟, SQL 开发必须考虑数据的变化,其中有很大
的不确定性 。某些表的数据量出乎意料地增长将会如何?同时,用户数量也倍增了,又将会如
何?希望数据在线保存好几年将会如何?如此一来,运行在硬件之上的这些程序的行为是否会
完全不同?架构级的选择是在赌未来,当然需要明确可靠的理论知识 —— 但这是进一步运用艺
术的先决条件。第一次世界大战联军总司令 Ferdinand Foch ,在 1900 年 French Ecole Sup é rieure d e
Guerre 的一次演讲中说:
战争的艺术和其他艺术一样,有它的历史和原则 —— 否则,就不能成其为艺术。
本书不是 cookbook ,不会列出一串问题然后给出 “ 处方 ” 。本书的目标重在帮助开发者(和他们
的经理)提出犀利的问题。阅读和理解了本书之后,你并不是永不再写出丑陋缓慢的查询了 — —
有时这是必须的 —— 但希望你是故意而为之、且有充足的理由。
本书的目标读者是:
有丰富经验的 SQL 数据库开发者
他们的经理
数据库占重要地位的系统的软件架构师
我希望一些 DBA 、尤其是数据库应用维护人员也能喜欢本书。不过,他们不是本书的主要目标
本书假定本书假定你已精通 SQL 语言。这里所说的 “ 精通 ” 不是指在你大学里学了 SQL 101 并拿来 A+ 的成
绩,当然也并非指你是国际公认的 SQL 专家,而是指你必须具有使用 SQL 开发数据库应用的经
验、必须考虑索引、必须不把 5000 行的表当大表。本书的目标不是讲解连接、外连接、索引的
基础知识,阅读本书过程中,如果你觉得某个 SQL 结构还显神秘 , 并影响了整段代码的理解,可
先阅读几本其他 SQL 书。另外,我假定读者至少熟悉一种编程语言 , 并了解计算机程序设计的基
本原则。性能已很差、用户已抱怨、你已在解决性能问题的前线,这就是本书的假 定。
我发现 SQL 和战争如此相像,以至于我几乎沿用了《孙子兵法》的大纲 , 并保持了大部分题目名
称(注 2 )。本书分为 12 章,每一章包含许多原则或准则,并通过举例的方式对原则进行解释说
明,这些例子大多来自于实际案例。
第 1 章,制定计划:为性能而设计
讨论如何设计高性能数据库
第 2 章,发动战争:高效访问数据库
解释如何进行程序设计才能高效访问数据库
第 3 章,战术部署:建立索引
揭示为何建立索引,如何建立索引
第 4 章,机动灵活:思考 SQL 语句
解释如何设计 SQL 语句
第 5 章,了如指掌:理解物理实现
揭示物理实现如何影响性能
第 6 章,锦囊妙计:认识经典 SQL 模式
包括经典的 SQL 模式、以及如何处理
第 7 章,变换战术:处理层次结构
说明如何处理层次数据
第 8 章,孰优孰劣:认识困难,处理困难
指出如何认识和处理比较棘手的情况
第 9 章,多条战线:处理并发
讲解如何处理并发
第 10 章,集中兵力:应付大数据量
讲解如何应付大数据量
第 11 章,精于计谋:挽救响应时间
分享一些技巧,以挽救设计糟糕的数据库的性能
第 12 章,明察秋毫:监控性能
收尾,解释如何定义和监控性能
本书使用了如下印刷惯例:
等宽( Courier )
表示 SQL 及编程语言的关键字,或表示 table 、索引或字段的名称,抑或表示函数、代码及命令输出。
等宽黑体( Courier )
表示必须由用户逐字键入的命令等文本。此风格仅用于同时包含输入、输出的代码示例。
等宽斜体( Courier )
表示这些文本,应该被用户提供的值代替。
总结:箴言,概括重要的 SQL 原则。
提示、建议、一般性注解。为相关主题提供有用的附加信息。
本书是为了帮助你完成工作的。总的来说,你可以将本书的代码用于你的程序和文档,但是,
若要大规模复制代码,则必须联系 O'Reilly 申请授权。例如:编程当中用了本书的几段代码, 无
需授权;但出售或分发 O'Reilly 书籍中案例的 CD-ROM 光盘,需要授权。再如:回答问题时, 引
用了本书或其中的代码示例,无需授权;但在你的产品文档中大量使用本书代码,需要授权。
O'Reilly 公司感谢但不强制归属声明。归属声明通常包括书名、作者、出版商、 ISBN 。例如 “ T he Art
of SQL by St é phane Faroult with Peter Robson. Copyright (C) 2006 O'Reilly Media, 0-596-00894-5 ” 。
如果你对代码示例的使用超出了上述范围,请通过
联系出版商。
评论与提问
我们已尽力核验本书所提供的信息,尽管如此,仍不能保证本书完全没有瑕疵,而网络世界的
变化之快,也使得本书永不过时的保证成为不可能。如果读者发现本书内容上的错误,不管是
赘字、错字、语意不清,甚至是技术错误,我们都竭诚虚心接受读者指教。如果您有任何问题 ,
请按照以下的联系方式与我们联系。
O'Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472
(800) 998-9938 (in the U.S. or Canada)
(707) 829-0515 (international or local)
(707) 829-0104 (fax)
本书原版用英语写作,英语既不是我的家乡话,又不是我所在国家的语言,所以写这样一本书
要求极度乐观(回想起来几近疯狂)。幸运的是, Peter Robson 不仅为本书贡献了他在 SQL 和 数
据库设计方面的知识,也贡献了持续的热情来修改我冗长的句子、调整副词位置、斟酌替换词
汇。 Peter Robson 和我在好几个大会上都碰过面,我们都是演讲者。
Jonathan Gennick 担任本书编辑,这有点让人受宠若惊, Jonathan Gennick 是 O'Reilly 出版的
SQL Pocket Guide 等畅销名著的作者。 Jonathan 是个非常尊重作者的编辑。由于他的专业、 他
对细节的关注、他的犀利视角,使本书的质量大大提升。同时, Jonathan 也使本书的语言更具 “ 中
大西洋 ” 风味( Peter 和我发现,虽然我们保证按美国英语拼写,但还远远不够)。我还要感谢很多人,他们来自三个不同的大陆,阅读了本书全部或部分草稿并坦诚地提出意见 。
他们是: Philippe Bertolino 、 Rachel Carmichael 、 Sunil CS 、 Larry Elkins 、 Tim Gorman 、 Je an-
Paul Martin 、 Sanjay Mishra 、 Anthony Molinaro 、 Tiong Soo Hua 。我特别感激 Larry ,因为本
书的思想最初来自于我们的 E-mail 讨论。
我也要感谢 O'Reilly 的许多人,他们使本书得以出版。他们是: Marcia Friedman 、 Rob Roma no 、
Jamie Peppard 、 Mike Kohnke 、 Ron Bilodeau 、 Jessamyn Read 、 Andrew Savikas 。感谢 Na ncy
Reinhardt 卓越的手稿编辑工作。
特别感谢 Yann-Arzel Durelle-Marc 慷慨提供第 12 章用到的图片。感谢 Paul McWhorter 授权我们
将他的战争图用于第 6 章。
最后,感谢 Roger Manser 和 Steel Business Briefing 的职员,他们为 Peter 和我提供了位于伦敦
的办公室(还有大量咖啡)。感谢 Qian Lena (Ashley) 提供了本书开始引用的《孙子兵法》的中
St é phane Faroult 从 1983 年开始接触关系数据库。 Oracle 法国成立早期他即加入(此前是短暂的
IBM 经历和渥太华大学任教生涯),并在不久之后对性能和调优产生了兴趣。 1988 年他离开了
Oracle ,此后一年间,他进行调整,并研究过运筹学。之后,他重操旧业,一直从事数据库咨
询工作,并于 1998 年创办了 RoughSea 公司( http://www .
St é phane Faroult 出版了 Fortran Structur é et M é thodes Num é riques 一书(法语, Dunod 出版社, 198 6,
与 Didier Simon 合作),并在 Oracle Scene 和 Select (分别为英国和北美 Oracle 用户组杂志)以及
Oracle 杂志在线版上发表了许多文章。他还是美国、英国、挪威等众多用户组大会的演讲者。
Peter Robson 毕业于达拉谟大学地质专业( 1968 年),然后在爱丁堡大学任教,并于 1975 年获得
地质学研究型硕士学位。在希腊度过了一段地质学家生涯之后,他开始在纽卡斯尔大学专攻地
质和医学数据库。
他使用数据库始于 1977 年, 1981 年开始使用关系数据库, 1985 年开始使用 Oracle ,这期间担任
过开发工程师、数据架构师、数据库管理员等角色。 1980 年, Peter 参加了英国地质普查,负责
指导使用关系数据库管理系统。他擅长 SQL 系统,以及从组织级到部门级的数据建模。 Peter 多
次出席英国、欧洲、北美的 Oracle 数据库大会,在许多数据库专业杂志上发表过文章。他现任
英国 Oracle 用户组委员会主任,可通过 peter. 联系他。
查询的识别有经验的朋友都知道,把关键系统从开发环境切换到生产环境是一场战役,一场甚嚣尘上的战
役。通常,在 “ 攻击发起日( D-Day ) ” 的前几周,性能测试会显示新系统达不到预期要求。于 是 ,
找专家,调优 SQL 语句,召集数据库管理员和系统管理员不断开会讨论对策。最后,性能总算
与以前的系统大致相当了(尽管新系统用的是价格翻倍的硬件)。
人们常常使用战术,而忽略了战略。战略要求从大局上把握整个架构与设计。和战争一样,战
略的基本原则并不多,且经常被忽视。架构错误的代价非常高, SQL 程序员必须准备充分, 明
确目标,了解如何实现目标。在本章中,我们讨论编写高效访问数据库的程序需要实现哪些关
查询的识别
Query Query Query Identification Identification Identification
数个世纪以来,将军通过辨别军装颜色和旗帜等来判断各部队的位置,以此检查激战中部队行
进情况。同样,当一些进程消耗了过多的 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 Stable Stable Database Database Database Connections Connections Connections
建立一个新的数据库连接,既快又方便,但这其中往往掩藏着重复建立数据库连接带来的巨大
开销。所以,管理数据库连接必须非常小心。允许多重连接 —— 可能就藏在你的应用中 —— 的
后果可能很严重,下面即是一例。不久前,我遇到一个应用,要处理很多小的文本文件。这些文本文件最大的也不超过一百行,
每一行包含要加载的数据及数据库等信息。此例中固然只有一个数据库实例,但即使有上百个 ,
这里所说明的原理也是适用的。
处理每个文件的代码如下:
上述处理工作令人满意,但当大量小文件都在极短的时间内到达时,可能应用程序来不及处理 ,
于是积压大量待处理文件,花费时间相当可观。
我用 C 语言编了个简单的程序来模拟上述情况,以说明频繁的数据库连接和中断所造成的系
统性能下降问题。表 2-1 列出了模拟的结果。
产生表 2-1 结果的程序使用了常规的 insert 语句。顺便提一下,直接加载( direct-loading )的技
术会更快。
表 2-1 :连接/中断性能测试结果
Open the file
Until the end of file is reached
Read a row
Connect to the server specified by the row
Insert the data
Disconnect
Close the file
测 试 结 果
依次对每一行作连接/中断 7.4 行 / 秒
连接一次,所有行逐个插入 1 681 行 / 秒
连接一次,以 10 行为一数组插入 5 914 行 / 秒
连接一次,以 100 行为一数组插入 9 190 行 / 秒此例说明了尽量减少分别连接数据库次数的重要性。对比表中前后两次针对相同数据库的插入
操作,明显发现性能有显著提升。其实还可以做进一步的优化。因为数据库实例的数量势必有
限,所以可以建立一组处理程序( handler )分别负责一个数据库连接,每个数据库只连接一 次 ,
使性能进一步提高。正如表 2-1 所示,仅连接数据库一次(或很少次)的简单技巧,再加上 一
点额外工作,就能让效率提升 200 倍以上。
当然,在上述改进的基础上,再将欲更新的数据填入数组,这样就尽可能减少了程序和数据库
核心间的交互次数,从而使性能产生了另一次飞跃。这种每次插入几行数据的做法,可以使数
据的总处理能力又增加了 5 倍。表 2-1 中的结果显示改进后的性能几乎是最初的 1 200 倍。
为何有如此大的性能提升?
第一个原因,也是最大的原因,在于数据库连接是很 “ 重 ” 的操作,消耗资源很多。
在常见的客户 / 服务器模式中(现在仍广为使用),简单的连接操作背后潜藏着如下事实:首先 ,
客户端与远程服务器的监听程序( listener program )建立联系;接着,监听程序要么创建一 个
进程或线程来执行数据库核心程序,要么直接或间接地把客户请求传递给已存在的服务器进程 ,
这取决于此服务器是否为共享服务器。
除了这些系统操作(创建进程或线程并开始执行)之外,数据库系统还必须为每
次 session 建立新环境,以跟踪它的行为。建立新 session 前, DBMS 还要检查密码是否与保存
的加密的账户密码相符。或许, DBMS 还要执行登录触发器( logon trigger ),还要初始化存储
过程和程序包(如果它们是第一次被调用)。上面这些还不包括客户端进程和服务器进程之间 要
完成的握手协议。正因为如此,连接池( connection pooling )等保持永久数据库连接的技术 对
性能才如此重要。
第二个原因,你的程序(甚至包括存储过程)和数据库之间的交互也有开销。
即使数据库连结已经建立且仍未中断,程序和 DBMS 核心之间的上下文切换( context switc h )
也有代价。因此,如果 DBMS 支持数据通过数组传递,应毫不犹豫地使用它。如果该数组接
口是隐式的( API 内部使用,但你不能使用),那么明智的做法是检查它的默认大小并根据具体
需要修改它。当然,任何逐行处理的方式都面临上下文切换的问题,并对性能产生严重影响 —
— 本章后面还会多次涉及此问题。
总结:数据库连接和交互好似万里长城 —— 长度越长,传递消息越耗时。
战略优先于战术
Strategy Strategy Strategy Before Before Before Tactics Tactics Tactics
战略决定战术,反之则谬也。思考如何处理数据时,有经验的开发者不会着眼于细微步骤,而
是着眼于最终结果。要获得想要的结果,最显而易见的方法是按照业务规则规定的顺序按部就
班地处理,但这不是最有效的方法 —— 接下来的例子将显示,刻意关注业务处理流程可能会使我们错失最有效的解决方案。
几年前,有人给了我一个存储过程,让我 “ 尝试 ” 着进行一下优化。为什么说是 “ 尝试 ” 呢?因为
该存储过程已经被优化两次了,一次是由原开发者,另一次是由一个自称 Oracle 专家的人。 但
尽管如此,这个存储过程的执行仍会花上 20 分钟,使用者无法接受。
此存储过程的目的,是根据现有库存和各地订单,计算出总厂需要订购的原料数量。大体上,
它就是把不同数据源的几个相同的表聚合( aggregate )到一个主表( master table )中。首先,
将每个数据源的数据插入主表;接着,对主表中的各项数据进行合计并更新;最后,将与合计
结果无关的数据从表中删除。针对每个数据源,重复执行上述步骤。所有 SQL 语句都不是特
别复杂,也没有哪个单独的 SQL 语句特别低效。
为了理解这个存储过程,我花了大半天时间,终于发现了问题:为什么该过程要用这么多步骤
呢?在 from 子句中加上包含 union 的子查询,就能得到所有数据源的聚合( aggregation )。一 条
select 语句,只需一步就得到了结果集,而之前要通过插入目标表( target table )得到结果集。
优化后,性能的提升非常惊人 —— 从 20 分钟减至 20 秒;当然,之后我花了一些时间验证了
结果集,与未优化前完全相同。
想要获得上述的大幅提高性能,无需特别技能,仅要求站在局外思考( think outside the box ) 的
能力。之前两次优化因 “ 太关注问题本身 ” 而收到了干扰。我们需要大胆的思维,站得远一些,
试着从大局的角度看待问题。要问自己一些关键的问题:写存储过程之前,我们已有哪些数据 ?
我们希望存储过程返回什么结果?再辅以大胆的思维,思考这些问题的答案,就能得到一个性
能大幅提升的处理方式了。
总结:考虑解决方案的细节之前,先站得远一些,把握大局。
先定义问题,再解决问题
Problem Problem Problem Definition Definition Definition Before Before Before Solution Solution Solution
一知半解是危险的。人们常在听说了新技术或特殊技术之后 —— 有时的确很吸引人 —— 试图采
用它作为新的解决方案。普通开发者和设计师通常会立即采纳这些新 “ 解决方案 ” ,直到后来才
发现它们会产生许多后续问题。
现成的解决方案中,非规范化设计引人注目。设计伊始,非规范化设计的拥护者就提出此方案 ,
为了寻求 “ 性能 ” 而无视最终将会面临的升级恶魔 —— 而事实上,在开发周期早期,改进设计( 或
学习如何使用 join )也是一个不错的选择。作为非规范化设计的一种手段,物化视图( materiali zed
view )常被认为是灵丹妙药。物化视图有时被称为快照( snapshot ),这个更加平常的词更形象
地反映了可悲的事实:物化视图是某时间点的数据副本。在没有其他办法时,这个理论上遭到
质疑的技术也未尝不值得一试,借用卡夫卡( Franz Ka fka )的一句名言: “ 逻辑诚可贵,生存 价
然而,绝大部分问题都可借助传统技术巧妙解决。首先,应学会充分利用简单、传统的技术。
只有完全掌握了这些技术,才能正确评价它们的局限性,最终发现它相当于新技术的潜在优势
(如果有的话)。
所有技术方案,都只是我们达到目标的手段。没有经验的开发者误把新技术本身当成了目标。对于热衷于技术、过于看重技术的人来说,此问题就更为严重。
总结:先打基础,再赶时髦:摆弄新工具之前,先把手艺学好。
直接操作实际数据
Operations Operations Operations Against Against Against Actual Actual Actual Data Data Data
许多开发者喜欢建立临时工作表( temporary work table ),把后续处理使用的大量数据放入其中 ,
然后开始 “ 正式 ” 工作。这种方法广受质疑,反映了 “ 跳出业务流程细节考虑问题 ” 的能力不足。
记住,永久表( permanent table )可以设置非常复杂的存储选项(在第 5 章将讨论一些存储选项
的设置),而临时表不能。临时表的索引(如果有的话)可能不是最优的,因此,查询临时表 的
语句效率比永久表的差。另外,查询之前必然先为临时表填入数据,这自然也多了一笔额外的
就算使用临时表有充足理由,若数据量大,也绝不能把永久表当作临时工作表来用。问题之一
在于统计信息的自动收集:若没有实时收集要求, DBMS 通常会在不活动或活动少时进行统计
信息收集,而这时作为临时工作表可能为空,从而使优化器收到了完全错误的信息。这些不正
确且有偏差的统计信息可能造成执行计划( execution plan )完全不合理,导致性能下降。所以 ,
如果一定要用临时表,应确保数据库知道哪些表是临时的。
总结:暂时工作表意味着以不太合理的方式存储更多信息。
用 SQL SQL SQL 处理集合
Set Set Set Processing Processing Processing in in in SQL SQL SQL
SQL 完全基于集合( Set )来处理数据。对大部分更新或删除操作而言 —— 如果不是针对整
个表的话 —— 你必须先精确定义出要处理的记录的集合。这定义了该处理的粒度
( granularity ),可能是对大量记录的粗粒度操作,有可能是只影响少数记录的细粒度操作。
将一次 “ 大批量数据的处理 ” 分割成多次 “ 小块处理 ” 是个坏主意,除非对数据库的修改太昂贵,
否则不要使用,因为这种方法极其低效:
(1) 占用过多的空间保存原始数据,以备事务( transaction )回滚( rollback )之需;
(2) 万一修改失败,回滚消耗过长的实践。
许多人认为,进行大规模修改操作时,应在操作数据的代码中有规律地多安排些 commit 命令。
其实,严格从实践角度来讲, “ 从头开始重做 ” 比 “ 确定失败发生的时间和位置,接着已提交部 分
重做 ” 要容易得多、简单得多、也快得多。
处理数据时,应适应数据库的物理实现。考虑事务失败时回滚所需日志的大小,如果要为 undo
保存的数据量确实巨大,或许应该考虑数据修改的频率问题。也就是说,将大规模的 “ 每月更 新 ” ,
改为规模不大的 “ 每周更新 ” ,甚至改为规模更小的 “ 每日更新 ” ,或许是个有效方案。
总结:几千个语句,借助游标( cursor )不断循环,很慢。换成几个语句,处理同样的数据,
还是较慢。换成一个语句,解决上述问题,最好。动作丰富的 SQL SQL SQL 语句
Action-Packed Action-Packed Action-Packed SQL SQL SQL Statements Statements Statements
SQL 不是过程性语言( procedural language ),尽管也可以将过程逻辑( procedural logic )用于 S QL ,
但必须小心。混淆声明性处理( declarative processing )和过程逻辑,最常见的例子出现在需要
从数据库中提取数据、然后处理数据、然后再插入到数据库时。在一个程序(或程序中的一个
函数)接收到特定输入值后,如下情况太常见了:用输入值从数据库中检索到一个或多个另外
的数据值,然后,借助循环或条件逻辑(通常是 if ... then ... else )将一些语句组织起来 ,对数
据库进行操作。大多数情况下,造成上述错误做法的原因有三:根深蒂固的坏习惯、 SQL 知识
的缺乏、盲从功能需求规格说明。其实,许多复杂操作往往可由一条 SQL 语句完成。因此,
如果用户提供了一些数据值,尽量不要将操作分解为多条提取中间结果的语句。
避免在 SQL 中引入 “ 过程逻辑( procedural logic ) ” 的主要原因有二。
数据库访问,总会跨多个软件层,甚至包括网络访问。
即使没有网络访问,也会涉及进程间通讯;额外的存取访问意味着更多的函数调用、更大的带
宽,以及更长的等待时间。一旦这些调用要重复多次,其对性能的影响就非常可观了。
在 SQL 中引入过程逻辑,意味着性能和维护问题应由你的程序承担。
大多数据库系统都提供了成熟的算法,来处理 join 等操作,来优化查询以获得更高的效率。基 于
开销的优化器( cost-based optimizer , CBO )是很复杂的软件,它早已不像刚推出时那样没什 么
用了,而在大部分情况下都是非常出色的成熟产品了,优秀的 CBO 查询优化的效率极高。然 而 ,
CBO 所能改变的只有 SQL 语句。如果在一条单独的 SQL 语句中完成尽可能多的操作,那么 性
能优化可以还由 DBMS 核心负责,你的程序可以充分利用 DBMS 的所有升级。也就是说,未
来大部分维护工作从程序间接转移给了 DBMS 供 货商。
当然, “ 避免在 SQL 中引入过程逻辑 ” 规则也有例外。有时过程逻辑确实能加快处理速度,庞
大的 SQL 语句未必总是高效。然而,过程逻辑及其之后的处理相同数据的语句,可以编写到一
个单独的 SQL 语句中, CBO 就是这么做的,从而获得最高效的执行方式。
总结:尽可能多地把事情交给数据库优化器来处理。
充分利用每次数据库访问
Profitable Profitable Profitable Database Database Database Accesses Accesses Accesses
如果计划逛好几家商店,你会首先决定在每家店买哪些东西。从这一刻起,就要计划按何种顺
序购物才能少走冤枉路。每逛一家店,计划东西购买完毕,才逛下一家。这是常识,但其中蕴
含的道理许多数据库应用却不懂得。
要从一个表中提取多段信息时,采用多次数据库访问的做法非常糟糕,即使多段信息看似 “ 无 关 ”
(但事实上往往并非如此)。例如,如果需要多个字段的数据,千万不要逐个字段地提取,而 应一次操作全部完成。
很不幸,面向对象( OO )的最佳实践提倡为每个属性定义一个 get 方法。不要把 OO 方法与关
系数据库处理混为一谈。混淆关系和面向对象的概念,以及将表等同于类、字段等同于属性,
都是致命的错误。
总结:在合理范围内,利用每次数据库访问完成尽量多的工作。
接近 DBMS DBMS DBMS 核心
Closeness Closeness Closeness to to to the the the DBMS DBMS DBMS Kernel Kernel Kernel
代码的执行越接近 DBMS 核心,则执行速度越快。数据库真正强大之处就在于此,例如,有 些
数据库管理产品支持扩展,你可以用C等较底层的语言为它编写新功能。用含有指针操作的底
层语言有个缺点,即一旦指针处理出错会影响内存。仅影响到一个用户已很糟糕,何况数据库
服务器(就像 “ 服务器 ” 名字所指的一样)出了问题会影响众多 “ 用户 ”—— 服务器内存出了问题 ,
所有使用这些数据的无辜的应用程序都会受影响。因此, DBMS 核心采取了负责任的做法, 在
沙箱( sandbox )环境中执行程序代码,这样,即使出了问题也不会影响到数据。例如, O racle 在
外部函数( external function )和它自身之间实现了一套复杂的通信机制,此机制在某些方面很
像控制数据库连结的方法,以管理两个(或多个)服务器上的数据库实例之间的通信。到底采
用 PL/SQL 存储过程还是外部 C 函数,应综合比较后决定。如果精心编写外部 C 函数获得的
好处超过了建立外部环境和上下文切换( context-switching )的成本,就应采用外部函数。但 需
要处理一个大数据量的表的每一行时,不要使用外部函数。这需要平衡考虑,解决问题时应完
全了解备选策略的后果。
如要使用函数,始终应首选 DBMS 自带的函数。这不仅仅是为了避免无谓的重复劳动,还因为
自带函数在执行时比任何第三方开发的代码更接近数据库核心,相应地其效率也会高出许多。
下面这个简单例子是用 Oracle SQL 编写的,显示了 使用 Oracle 函数所获得的效率。假设手工
输入的文本数据可能包含多个相邻的 “ 空格 ” ,我们需要一个函数将多个空格
替换为一个空格。如果不采用 Oracle Database 10g 开始提供的正规表达式( regular expression ),
函数代码将会是这样:
create or replace function squeeze1(p_string in varchar2)
return varchar2
v_string varchar2(512) := '';
c_char char(1);
n_len number := length(p_string);
i binary_integer := 1;
while (i &= n_len)上述代码中的 'X' 在内层循环中被串接到字符串上,以避免超出字符串长度的测试。
还有别的方法消除多个空格,可以使用 Oracle 提供的字符串函数。以下为替代方案:
c_char := substr(p_string, i, 1);
v_string := v_string || c_
if (c_char = ' ')
j := i + 1;
while (substr(p_string || 'X', j, 1) = ' ')
j := j + 1;
i := i + 1;
create or replace function squeeze2(p_string in varchar2)
return varchar2
v_string varchar2(512) := p_
i binary_integer := 1;
i := instr(v_string, ' ');
while (i & 0)
v_string := substr(v_string, 1, i)
|| ltrim(substr(v_string, i + 1));
i := instr(v_string, ' ');
/还有第三种方法:
用一个简单的例子对上述三种方法进行测试,每个函数都能正确工作,且没有明显的性能差异 :
create or replace function squeeze3(p_string in varchar2)
return varchar2
v_string varchar2(512) := p_
len1 := length(p_string);
v_string := replace(p_string, ' ', ' ');
len2 := length(v_string);
while (len2 & len1)
len1 := len2;
v_string := replace(v_string, ' ', ' ');
len2 := length(v_string);
SQL& select squeeze1('azeryt hgfrdt r')
2 from dual
azeryt hgfrdt r
Elapsed: 00:00:00.00
SQL& select squeeze2('azeryt hgfrdt r')
2 from dual
azeryt hgfrdt r
Elapsed: 00:00:00.01
SQL& select squeeze3('azeryt hgfrdt r')
2 from dual
azeryt hgfrdt r
Elapsed: 00:00:00.00那么,如果每天要调用该空格替换操作几千次呢?我们构造一个接近现实负载的环境,下面的
代码将建立一个用于测试的表并填入随机数据,已检测上面三个函数是否有性能差异:
create table squeezable(random_text varchar2(50))
v_string varchar2(50);
for i in 1 .. 10000
j := dbms_random.value(1, 100);
v_string := dbms_random.string('U', 50);
while (j & length(v_string))
k := dbms_random.value(1, 3);
v_string := substr(substr(v_string, 1, j) || rpad(' ', k)
|| substr(v_string, j + 1), 1, 50);
j := dbms_random.value(1, 100);
insert into squeezable
values(v_string);
上面的脚本在测试表中建立了 10 000 条记录(决定 SQL 语句要执行多少次时,这是数字比较 适
中)。要执行该测试,运行下列语句:
我运行这个测试时,关闭了所有头信息( headers )和屏幕的显示。禁止输出可确保结果反映的
是替换空格算法所花费的时间,而不是显示结果所花费的时间。这些语句会执行多次,以确保
不受缓存( caching )的影响。
表 2-2 显示了在测试机上的运行结果。
表 2-2 :处理 10 000 条记录中空格所花的时间
select squeeze_func(random_text)
函数 机制 时间
用 PL/SQL 循环处理字符 0.86 秒尽管都在 1 秒内完成了 10 000 次调用,但 squeeze2 的速度是 squeeze1 的 1.8 倍,而 squeeze 3
则是它的 2.2 倍。为什么呢?原因很简单,因为 SQL 函数比 PL/SQL “ 离核心更近 ” 。当函数只 偶
尔执行一次时,性能差异微乎其微,但在批处理程序或高负载的 OLTP 服务器中性能差异就 非
总结:代码喜欢 SQL 内核 —— 离核心越近,它就运行得越快。
只做必须做的
Doing Doing Doing Only Only Only What What What Is Is Is Required Required Required
开发者使用 count(*) 往往只是为了测试 “ 是否存在 ” 。这通常是由以下的需求说明引起的:
如果存在满足某条件的记录
那么处理这些记录
用代码直接实现就是:
当然,在 90% 的情况下, count(*) 是完全不必要的,正如上面的例子。要对多项记录进行操
作,直接做即可,不必用 count(*) 。即使一个操作对任何记录都没有影响,也没有关系,不用
count(*) 没有什么不好。而且,即使要对未知的记录进行复杂处理,也能通过第一个操作就确 定
并返回受影响的记录 —— 要么通过特殊的 API (例如 PHP 中的 mysql_affected_rows() ),要 么
采用系统变量( Transact-SQL 中为 @@ROWCOUNT , PL/SQL 中为 SQL%ROWCOUNT ),若 使
用内嵌式 SQL ,则使用 SQL 通讯区( SQL Communication Area , SQLCA )的特殊字段。有时,
squeeze2 Instr() + ltrim()
squeeze3 循环调用 replace()
select count(*)
into counter
from table_name
where &certain_condition&
if (counter & 0) then可以通过函数访问数据库然后直接返回要处理的记录数,例如 JDBC 的 executeUpdate() 方法。
总之,统计记录数极可能意味着重复全部搜索,因为它对相同数据处理了两次。
此外,如果是为了更新或插入记录(常使用 count 检查键是否已经存在),一些数据库系统会提
供专用的语句(例如 Oracle 9i 提供 MERGE 语句),其执行效率要比使用 count 高得多。
总结:没必要编程实现那些数据库隐含实现的功能。
SQL SQL SQL 语句反映业务逻辑
SQL Statements Mirror Business Logic
大多数数据库系统都提供监控功能,我们可以借此查看当前正在执行的语句及其执行的次数。
同时,必须对有多少个 “ 业务单元( business units ) ” 正在执行心里有数 —— 例如待处理的订单、
需处理的请求、需结账的客户,或者业务管理者了解的任何事情。我们应检查上述语句活动和
业务活动的数量关系是否合理(并不要求绝对精确)。换言之,如果客户数量一定,那么数据 库
初始化活动的数量是否与之相同?如果查询 customers 表的次数比同一时间正在处理的客户量
多 20 倍,那一定是某个地方出了问题,或许该查询对表中相同记录做了 重复(而且多余)的
访问,而不是一次就从表中找出了所需信息。
总结:检查数据库活动,看它是否与当时正进行的业务活动保持合理的一致性。
把逻辑放到查询中
Program Logic into Queries
在数据库应用程序中实现过程逻辑( procedural logic )的方法有几种。 SQL 语句内部可实现某
种程度上的过程逻辑(尽管 SQL 语句应该说明做什么,而不是怎么做)。即便内嵌式 SQL 的宿 主
语言( host language )非常完善,依然推荐尽量将上述过程逻辑放在 SQL 语句当中,而不是 宿
主语言当中,因为前一种做法效率更高。过程性语言( Procedural language )的特点在于拥 有
执行迭代(循环)和条件( if ... then ... else 结构)逻辑的能力。 SQL 不需要循环能力,因为 它
本质上是 在操作集合, SQL 只需要执行条件逻辑的能力。
条件逻辑包含两部分 —— IF 和 ELSE 。要实现 IF 的效果相当容易 —— where 子句可以胜任,困难
的是实现 ELSE 逻辑。例如,要取出一些记录,然后对其分组,每组进行不同的转换。 case 表
达式( Oracle 早已在 decode() (注 1 )中提供了功能等效的操作符)可以容易地模拟 ELSE 逻 辑 :
根据每条记录值的不同,返回具有不同值的结果集。下面用伪代码( pseudocode )表达 case 结
构的使用(注 2 ):
WHEN condition THEN &return something to the result set&
WHEN condition THEN &return something else&
WHEN condition THEN &return still something else&
ELSE &fall back on this value&数值或日期的比较则简单明了。操作字符串可以用 Oracle 的 greatest() 或 least() ,或者 MySQ L
的 strcmp() 。有时,可以为 insert 语句增加过程逻辑,具体办法是多重 insert 及条件 insert (注 3 ) ,
并借助 merge 语句。如果 DBMS 提供了这样语句,毫不犹豫地使用它。也就是说,有许多
逻辑可以放入 SQL 语句中;虽然仅执行多条语句中的一条这种逻辑价值不大,但如果设法利
用 case 、 merge 或类似功能将多条语句合并成一条,价值可就大了。
总结:只要有可能,应尽量把条件逻辑放到 SQL 语句中,而不是 SQL 的宿主语言中。
一次完成多个更新
Multiple Multiple Multiple Updates Updates Updates at at at Once Once Once
我的基本主张是:如果每次更新的是彼此无关的记录,对一张表连续进行多次 update 操作还可
以接受;否则,就应该把它们合并成一个 update 操作。例如,下面是来自实际应用的一些代码
(注 4 ):
两个连续的更新是对同一个表进行的。但它们是否将访问相同的记录呢?不得而知。问题是,
搜索条件的效率有多高?任何名为 type 或 status 的字段,其值的分布通常是杂乱无章的,所以上
面两个 update 语句极可能对同一个表连续进行两次完整扫描:一个 update 有效地利用了索引, 而
第二个 update 不可避免地进行全表扫描;或者,幸运
的话,两次 update 都有效地利用了索引。无论如何,把这两个 update 合并到一起,几乎不会有 损
失,只会有好处:
update tbo_invoice_extractor
set pga_status = 0
where pga_status in (1,3)
and inv_type = 0;
update tbo_invoice_extractor
set rd_status = 0
where rd_status in (1,3)
and inv_type = 0;
update tbo_invoice_extractor
set pga_status = (case pga_status
when 1 then 0
when 3 then 0
else pga_status上例中,可能出现重复更新相同字段为相同内容的情况,这的确增加了一小点儿开销。但在多
数情况下,一个 update 会比多个 update 快得多。注意上例中的 “ 逻辑( logic ) ” ,我们通过 case 语
句实现了隐式的条件逻辑( implicit conditional logic ),来处理那些符合更新条件的数据记录, 并
且更新条件可以有多条。
总结:有可能的话,用一个语句处理多个更新;尽量减少对同一个表的重复访问。
慎用自定义函数
Careful Use of User-Written Functions
将自定义函数( User-Written Function )嵌到 SQL 语句后,它可能被调用相当多次。如果在 sel ect
语句的选出项列表中使用自定义函数,则每返回一行数据就会调用一次该函数。如果自定义函
数出现在 where 子句中,则每一行数据要成功通过过滤条件都会调用一次该函数;如果此时
其他过滤条件的筛选能力不够强,自定义函数被调用的次数就非常可观了。
如果自定义函数内部还要执行一个查询,会发生什么情况呢?每次函数调用都将执行此内部查
询。实际上,这和关联子查询( correlated subquery )效果相同,只不过自定义函数的方式阻
碍了基于开销的优化器( cost-based optimizer , CBO )对整个查询的优化效果,因为 “ 子查询 ”
隐藏在函数中,数据库优化器鞭长莫及。
下面举例说明将 SQL 语句隐藏在自定义函数中的危险性。表 flights 描述商务航班,有航班号、 起
飞时间、到达时间及机场 IATA 代码(注 5 )等字段。 IATA 代码均为三个字母,有 9 000 多个,
它们的解释保存在参照表中,包含城市名称(若一个城市有多个机场则应为机场名称)、国家 名
称等。显然,显示航班信息时,应该包含目的城市的机场名称,而不是简单的 IATA 代码。
在此就遇到了现代软件工程中的矛盾之一。被认为是 “ 优良传统 ” 的模块化编程一般情况下非常
适用,但对数据库编程而言,代码是开发者和数据库引擎的共享活动( shared activity ),模块
化要求并不明确。例如,我们可以遵循模块化原则编写一个小函数来查找 IATA 代码,并返回
完整的机场名称:
rd_status = (case rd_status
when 1 then 0
when 3 then 0
else rd_status
where (pga_status in (1,3)
or rd_status in (1, 3))
and inv_type = 0;
create or replace function airport_city(iata_code in char)
return varchar2
is对于不熟悉 Oracle 语法的读者,在此做个说明,以下查询中 trunc(sysdate) 的返回值为 “ 今天 的
00:00 a.m. ” ,日期计算以天为单位;所以起飞时间的条件是指今天 8:30 a.m. 至 4:00 p.m. 之
间。调用 airport_city 函数的查询可以非常简单,例如:
这个查询的执行速度令人满意;在我机器上的随机样本中,返回 77 行数据只用了 0.18 秒(多 次
执行的平均值),用户对这样的速度肯定满意(统计数据表明,此处理访问了
303 个数据块, 53 个是从磁盘读出的 —— 而且每行数据有个递归调用)。
我们还可以用 join 来重写这段代码,作为查找函数的替代方案,当然它看起来会稍微复杂些:
city_name varchar2(50);
select city
into city_name
from iata_airport_codes
where code = iata_
return(city_name);
select flight_number,
to_char(departure_time, 'HH24:MI') DEPARTURE,
airport_city(arrival) &TO&
from flights
where departure_time between trunc(sysdate) + 17/48
and trunc(sysdate) + 16/24
order by departure_time
select f.flight_number,
to_char(f.departure_time, 'HH24:MI') DEPARTURE,
a.city &TO&
from flights f,
iata_airport_codes a
where a.code = f.arrival
and departure_time between trunc(sysdate) + 17/48
and trunc(sysdate) + 16/24
order by departure_time
/这个查询只用了 0.05 秒(统计数据同前,但没有递归调用)。对于执行时间不到 0.2 秒的查
询来说,速度快了 3 倍似乎无关紧要,但在大型系统中,这些查询每天经常执行数十万次 —— 假
设以上查询每天只执行五万次,于是查询的总耗时为 2.5 小时。若不使用上述查找函数( loo kup
function )则只需要不到 42 分钟,速度提高超过 300% ,这对大数据量的系统意义重大,最 终
带来经济上的节约。通常,使用查找函数会使批处理程序的性能极差。而且查询时间的增加,
会使同一台机器支持的并发用户数减少,我们将在第 9 章对此 展开讨论。
总结:优化器对自定义函数的代码无能为力。
简洁的 SQL SQL SQL
Succinct SQL
熟练的开发者使用尽可能少的 SQL 语句完成尽可能多的事情。相反,拙劣的开发者则倾向于 严
格遵循已制订好的各功能步骤,下面是个真实的例子:
就算速度可以接受,这也是段极糟的代码。很不幸,性能专家经常遇到这种糟糕的代码。既然
两个值来自于同一表,为什么要分别用两个不同的语句呢?下面用 Oracle 的 bulk collect 子句,
一次性将两个值放到数组中,这很容易实现,关键在于对 rslt_period 进行 order by 操作,如下 所
-- Get the start of the accounting period
select closure_date
into dtPerSta
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period='1' || to_char(Param_dtAcc,'MM');
-- Get the end of the period out of closure
select closure_date
into dtPerClosure
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period='9' || to_char(Param_dtAcc,'MM');
select closure_date
bulk collect into dtPerStaArray
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),于是,这两个日期被分别保存在数组的第一个和第二个位置。其中, bulk collect 是 PL/SQL 语
言特有的,但任何支持显式或隐式数组提取的语言都可如法炮制。
其实甚至数组都是不必要的,用以下的小技巧(注 6 ),这两个值就可以被提取到两个变量中:
在这个例子中,预期返回值为两行数据,所以问题是:如何把原本属于一个字段的两行数据,
以一行数据两个字段的方式检索出来(正如数组提取的例子一样)。为此,我们
检查 rslt_period 字段,两行数据的 rslt_period 字段有不同值;如果找到需要的记录,就返回要 找
的日期;否则,就返回一个在任何情况下都远比我们所需日期要早的日期(此处选了哈斯丁之
役( battle of Hastings )的日期)。只要每次取出最大值,就可以确保获得需要的日期。这是 个
非常实用的技巧,也可以应用在字符或数值数据,第 11 章会有更详细的说明。
总结: SQL 是声明性语言( declarative language ),所以设法使你的代码超越业务过程的规格
SQL SQL SQL 的进攻式编程
Offensive Coding with SQL
一般的建议是进行防御式编程( code defensively ),在开始处理之前先检查所有参数的合法性 。
但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。
有个很好的例子:进行一连串检查,每当其中一个检查所要求的条件不符时就产生异常。信用
卡付款的处理中就涉及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两者是
否匹配;检查信用卡是否过期;最后,检查当前的支付额是否超过了信用额度。如果通过了所
'9' || to_char(Param_dtAcc,'MM'))
order by rslt_
select max(decode(substr(rslt_period, 1, 1), -- Check the first character
'1', closure_date,
-- If it's '1' return the date we want
to_date('14/10/1066', 'DD/MM/YYYY'))),
-- Otherwise something old
max(decode(substr(rslt_period, 1, 1),
'9', closure_date, -- The date we want
to_date('14/10/1066', 'DD/MM/YYYY'))),
into dtPerSta, dtPerClosure
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),
'9' || to_char(Param_dtAcc,'MM'));有检查,支付操作才继续进行。
为了完成上述功能,不熟练的开发者会写出下列语句,并检查其返回结果:
接下来,他会做类似的工作,并再一次检查错误代码:
之后,他才会处理金融交易。
相反,熟练的开发者更喜欢像下面这样编写代码(假设 today() 返当前日期):
接着,检查被更新的行数。如果结果为 0 ,只需执行下面的一个操作即可判断出错原因:
如果此查询没有返回数据,则可断定 customer_id 的值是错的;如果 card_num 是 null ,则 可
断定卡号是错的;等等。其实,多数情况下此查询无需被执行。
你是否注意到,上述第一段代码中使用了 count(*) 呢?这是个 count(*) 被误用于存在性检测的绝
“ 进攻式编程 ” 的本质特征是:以合理的可能性( reasonable probabilities )为基础。例如,检 查
select count(*)
from customers
where customer_id = provided_id
select card_num, expiry_date, credit_limit
from accounts
where customer_id = provided_id
update accounts
set balance = balance - purchased_amount
where balance &= purchased_amount
and credit_limit &= purchased_amount
and expiry_date & today()
and customer_id = provided_id
and card_num = provided_cardnum
select c.customer_id, a.card_num, a.expiry_date,
a.credit_limit, a.balance
from customers c
left outer join accounts a
on a.customer_id = c.customer_id
and a.card_num = provided_cardnum
where c.customer_id = provided_id客户是否存在是毫无意义的 —— 因为既然该客户不存在,那么他的记录根本就不在数据库中!
所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方(而且只在那个地方)采
取相应措施。有趣的是,这种方法很像一些数据库系统中采用的 “ 乐观并发控制( optimistic
concurrency control ) ” ,后者会假设 update 冲突不会发生,只在冲突真的发生时才进行控制处 理 。
结果,乐观方法比悲观方法的吞吐量高得多。
总结:以概论为基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理
简洁的 SQL SQL SQL
Succinct SQL
熟练的开发者使用尽可能少的 SQL 语句完成尽可能多的事情。相反,拙劣的开发者则倾向于 严
格遵循已制订好的各功能步骤,下面是个真实的例子:
就算速度可以接受,这也是段极糟的代码。很不幸,性能专家经常遇到这种糟糕的代码。既然
两个值来自于同一表,为什么要分别用两个不同的语句呢?下面用 Oracle 的 bulk collect 子句,
一次性将两个值放到数组中,这很容易实现,关键在于对 rslt_period 进行 order by 操作,如下 所
-- Get the start of the accounting period
select closure_date
into dtPerSta
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period='1' || to_char(Param_dtAcc,'MM');
-- Get the end of the period out of closure
select closure_date
into dtPerClosure
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period='9' || to_char(Param_dtAcc,'MM');
select closure_date
bulk collect into dtPerStaArray
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),
'9' || to_char(Param_dtAcc,'MM'))
order by rslt_于是,这两个日期被分别保存在数组的第一个和第二个位置。其中, bulk collect 是 PL/SQL 语
言特有的,但任何支持显式或隐式数组提取的语言都可如法炮制。
其实甚至数组都是不必要的,用以下的小技巧(注 6 ),这两个值就可以被提取到两个变量中:
在这个例子中,预期返回值为两行数据,所以问题是:如何把原本属于一个字段的两行数据,
以一行数据两个字段的方式检索出来(正如数组提取的例子一样)。为此,我们
检查 rslt_period 字段,两行数据的 rslt_period 字段有不同值;如果找到需要的记录,就返回要 找
的日期;否则,就返回一个在任何情况下都远比我们所需日期要早的日期(此处选了哈斯丁之
役( battle of Hastings )的日期)。只要每次取出最大值,就可以确保获得需要的日期。这是 个
非常实用的技巧,也可以应用在字符或数值数据,第 11 章会有更详细的说明。
总结: SQL 是声明性语言( declarative language ),所以设法使你的代码超越业务过程的规格
SQL SQL SQL 的进攻式编程
Offensive Coding with SQL
一般的建议是进行防御式编程( code defensively ),在开始处理之前先检查所有参数的合法性 。
但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。
有个很好的例子:进行一连串检查,每当其中一个检查所要求的条件不符时就产生异常。信用
select max(decode(substr(rslt_period, 1, 1), -- Check the first character
'1', closure_date,
-- If it's '1' return the date we want
to_date('14/10/1066', 'DD/MM/YYYY'))),
-- Otherwise something old
max(decode(substr(rslt_period, 1, 1),
'9', closure_date, -- The date we want
to_date('14/10/1066', 'DD/MM/YYYY'))),
into dtPerSta, dtPerClosure
from tperrslt
where fiscal_year=to_char(Param_dtAcc,'YYYY')
and rslt_period in ('1' || to_char(Param_dtAcc,'MM'),
'9' || to_char(Param_dtAcc,'MM'));卡付款的处理中就涉及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两者是
否匹配;检查信用卡是否过期;最后,检查当前的支付额是否超过了信用额度。如果通过了所
有检查,支付操作才继续进行。
为了完成上述功能,不熟练的开发者会写出下列语句,并检查其返回结果:
接下来,他会做类似的工作,并再一次检查错误代码:
之后,他才会处理金融交易。
相反,熟练的开发者更喜欢像下面这样编写代码(假设 today() 返当前日期):
接着,检查被更新的行数。如果结果为 0 ,只需执行下面的一个操作即可判断出错原因:
如果此查询没有返回数据,则可断定 customer_id 的值是错的;如果 card_num 是 null ,则 可
断定卡号是错的;等等。其实,多数情况下此查询无需被执行。
你是否注意到,上述第一段代码中使用了 count(*) 呢?这是个 count(*) 被误用于存在性检测的绝
select count(*)
from customers
where customer_id = provided_id
select card_num, expiry_date, credit_limit
from accounts
where customer_id = provided_id
update accounts
set balance = balance - purchased_amount
where balance &= purchased_amount
and credit_limit &= purchased_amount
and expiry_date & today()
and customer_id = provided_id
and card_num = provided_cardnum
select c.customer_id, a.card_num, a.expiry_date,
a.credit_limit, a.balance
from customers c
left outer join accounts a
on a.customer_id = c.customer_id
and a.card_num = provided_cardnum
where c.customer_id = provided_id佳例子。
“ 进攻式编程 ” 的本质特征是:以合理的可能性( reasonable probabilities )为基础。例如,检 查
客户是否存在是毫无意义的 —— 因为既然该客户不存在,那么他的记录根本就不在数据库中!
所以,应该先假设没有事情会出错;但如果出错了,就在出错的地方(而且只在那个地方)采
取相应措施。有趣的是,这种方法很像一些数据库系统中采用的 “ 乐观并发控制( optimistic
concurrency control ) ” ,后者会假设 update 冲突不会发生,只在冲突真的发生时才进行控制处 理 。
结果,乐观方法比悲观方法的 吞吐量高得多。
总结:以概论为基础进行编程。假设最可能的结果;不是的确必要,不要采用异常捕捉的处理
精明地使用异常( Exceptions Exceptions Exceptions )
Discerning Use of Exceptions
勇敢与鲁莽的界线很模糊,我建议进攻式编程,但并不是要你模仿轻步兵旅在 Balaclava 的自 杀
性冲锋(注 7 )。针对异常编程,最终可能落得虚张声势的愚蠢结果,但自负的开发者还是对它 “ 推
崇备至( go for it ) ” ,并坚信检查和处理异常能使他们完成任务。
正如其名字所暗示的,异常应该是那些例外情况。对数据库编程的具体情况而言,不是所有异
常都要求同样的处理方式 —— 这是理解异常的使用是否明智的关键点。有些是 “ 好 ” 异常,应预
先抛出;有些是 “ 坏 ” 异常,仅当真正的灾害发生时才抛出。
例如,以主键为条件进行查询时,如果没有结果返回则开销极少,因为只需检查索引即可判断 。
然而,如果查询无法使用索引,就必须搜索整个表 —— 当此表数据量很大,所在机器又正在接
近满负荷工作时,可能造成灾难。
有些异常的处理代价高昂,即使是在最佳情况下也不例外,例如重复键( duplicate key )的探
测 。 “ 唯一性( uniqueness ) ” 如何保证呢?我们几乎总是建立一个唯一性索引,每次向该索引增
加一个键时,都要检查是否违反了该唯一性索引的约束。然而,建立索引项需要记录物理地址 ,
于是就要求先将记录插入表,后将索引项插入索引。如果违反此约束,数据库会取消不完全的
插入,并返回违反约束的错误信息。上述这些操作开销巨大。但最大的问题是,整个处理必须
围绕个别异常展开,于是我们必须 “ 从个别记录的角度进行思考 ” ,而不是 “ 从 数据集出发进行思
考 ” ,这与关系数据库理论完全背道而驰。多次违反此约束会导致性能严重下降。
来看一个 Oracle 的例子。假设在两家公司合并后,电子邮件地址定为 &Initial&&Name& 的标 准
格式,最多 12 个字符,所有空格或引号以下划线代替。
如果新的 employee 表已经建好,并包含 3 000 条从 employee_old 表中提取并进行标准化处理 的
电子邮件地址。我们希望每个员工的电子邮件地址具有唯一性,于是 Fernando Lopez 的地址 为
flopez ,而 Francisco Lopez 的地址为 flopez2 。实际上,我们实际测试的数据中有 33 个潜在的
重复项,所以我们需要做如下测试:
SQL& insert into employees(emp_num, emp_name,
emp_firstname, emp_email)
2 select emp_num,3 000 条数据中重复 33 条,比率大约是 1% ,所以,或许可以心安理得地处理符合标准的
99% ,并用异常来处理其余部分。毕竟, 1% 的不符标准数据带来的异常处理开销应该不大。
但这个异常处理的开销到底在哪里呢?让我们先从测试数据中剔除 “ 问题记录 ” ,然后再执行相
同的测试,比较发现:这次测试的总运行时间,与上次几乎相同,都是 18 秒。然而,从测试 数
据中剔除 “ 问题记录 ” 之后再执行前面第一段 insert...select 语句时,速度明显比循环快:最终发
现采用 “ 一次处理一行 ” 的方式导致耗时增加了近 50% 。那么,在此例中可以不用 “ 一次处理一
行 ” 的方式吗?可以,但要首先避免使用异常。正是这个通过异常处理解决 “ 问题记录 ” 问题决 定 ,
迫使我们采用循序方式的。
另外,由于发生冲突的电子邮件地址可能不止一个,可以为它们指定某个数字获得唯一性。
很容易判断有多少个数据记录发生了冲突,增加 一个 group by 子句就可以了。但在分配数字时 ,
如果不使用主数据库系统提供的分析功能,恐怕比较困难。( Oracle 称为分析功能( analytical
function ), DB2 则称在线分析处理( online analytical processing , OLAP ), SQL Server 称之为 排
名功能( ranking function )。)纯粹从 SQL 角度来看,探索此问题的解决方案很有意义。
重复的电子邮件地址都可以被赋予一个具唯一性的数字: 1 赋给年纪最大的员工, 2 赋给年纪 次
之的的员工 …… 依次类推。为此,可以编写一个子查询,如果是 group 中的第一个电子邮件地 址
就不作操作,而该 group 中的后续电子邮件地址则加上序号。代码如下:
嵌到我的页面
<input type="text" readonly="true" value="">
若举报审核通过,可奖励20下载分
被举报人:
举报的资源分:
请选择类型
资源无法下载
资源无法使用
标题与实际内容不符
含有危害国家安全内容
含有反动色情等内容
含广告内容
版权问题,侵犯个人或公司的版权
*详细原因:
您可能还需要
课程资源下载排行}

我要回帖

更多关于 广州 兼职 数据输入 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信