━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

sqlssqqll语言艺术内容介绍本书分为12๐章,每一章包含许多原则或准则,并通过举例的方式对原则ท进行解释说明。这些例子大多来自于实际案例,对九种sql经典查询场景以及其性能影响讨论,非常便于实践,为你的实际工作提出了具体建议。本书适合sql数据库开者、软件架构师,也适合dba,尤其是数据库应用维护人员阅读。资深sql专家stéphanefa肉lt倾力打造《软件架构设计》作者温昱最新译作巧妙借鉴《孙子兵法》的智慧结晶传授25๓年的sql性能与调校经验深入探讨九种常见查询方案及其性能ม前言过去,“信息技术it”的名字还不如今天这般耀眼,被称为ฦ“电子数据处理”。其实,尽管当今新潮技术层出不穷,数据处理依然处于我们系统的核心地位,而且需管理的数据量的增长度似乎ๆ比处理器的增长度还快。今天,最重要的集团数据都被保存在数据库中,通过sql语言来访问。sql语言虽有缺点,但非常流行,它从1้9๗80年代早期开始被广泛接受,随后就所向无敌了。如今,年轻开者在接受面试时,没有谁不宣称自己能熟练应用sql的。sql作为ฦ数据库访问语言,已成为ฦ任何基础it课程的必备部分。开者宣传自己熟ງ练掌握sql,其实前提是“熟练掌握”的定义是“能够获得功能上正确的结果”。然而,全世界ศ的企业如今都面临数据量的爆炸式增长,所以仅做到“功能正确”是不够的,还必须ี足够快,所以数据库性能ม成了许多公司头疼的问题。有趣的是,尽管每个人都认可性能问题๤源自代码,但普遍接受的事实则是开者的要关注点应该是功能正确。人们认为:为了便于维护,代码中ณ的数据库访问部ຖ分应该尽量简单;“拙劣的sql”应该交给资深的dba去摆弄,他们还会调整几个“有魔力”的数据库参数,于是度就快了——如果数据库还不够快,似乎就该升级硬件了。往往就是这样,那些所谓的“常识”和“可靠方แ法”最终却是极端有害的。先写低效的代码、后由专家调优,这种做法实际上是自找麻烦。本书认为,先要关注性能的就是开者,而且sql问题๤绝不仅仅只包含正确编写几个查询这么简单。开者角度看到的性能问题和dba从调优角度看到的大相径庭。对dbaທ而言,他尽量从现有的硬件如处理器和存储子系统和特定版本的dbms获得最高性能,他可能有些sql技能ม并能调优一个性能极差的sql语句。但对开者而言,他编写的代码可能要运行5๓到10年,这些代码将经历一代代的硬件,以及dbms各种重要版

----ๅ---ๅ----ๅ--ๅ-----ๅ-ๅ--ๅ-ๅ-ๅpage2๐-ๅ--ๅ-----ๅ----ๅ-------ๅ----ๅ

本升级例如支持互联网访问、支持网格,不一而足。所以,代码必须ี从一开始就快、健全。很多开者仅仅是“知道”sql而已,他们没有深刻理解sql及关系理论,实在令人遗憾。为何写作本书sql书主要分为ฦ三种类型:讲授具体sql方แ言的逻辑和语法的书、讲授高级技术及解决问题方แ法的书、专家与资深dbຘa所需的性能和调优的书。一方面,书๰籍要讲述如何写sql代码;另一方面,要讲如何诊断ษ和修改拙劣的sql代码。在本书中,我不再为新手从头讲解如何写出优秀的sql代码,而是以越单个sql语句的方式看待sql代码,无疑ທ这更加重要。教授语言使用就够难了,那么本书是怎样讲述如何高效使用sql语言的呢?sql的简单性具有欺骗性,它能支持的情况组合的数目几乎是无限的。最初,我觉得sql和国际象棋很相似,后来,我悟到明国际象棋是为了教授战争之道。于是,每当出现sql性能难题的时候,我都自然而然地将之视为ฦ要和一行行数据组成的军队作战。最终,我找到了向开者传授如何有效使用数据库的方法,这就像教军官如何指挥战争。知识、技能、天赋缺一不可。天赋不能传授,只能培养。从写就了《孙子兵法》的孙子到如今的将军,绝大多数战略๓家都相信这一点,于是他们尽量以简单的格言或规则的方式表达沙场经验,并希望这样能指导真实的战争。我将这种方แ法用于战争之ใ外的许多领域,本书借鉴了孙子兵法的方法和书的题目。许多知名it专家冠以科学家称号,而我认为ฦ“艺术”比“科学”更能反映it活动所需的才能、经验和创造力注1。很可能ม是由于我偏爱“艺术”的原因,“科学”派并不赞成我的观点,他们声称每个ฐsql问题都可通过严格分析和参考丰富的经验数据来解决。然而,我不认为这两ä种观点有什么不一致。明确的科学方แ法有助于摆脱单个具体问题的限制,毕竟,sql开必须考虑数据的变化,其中有很大的不确定性。某些表的数据量出乎意料地增长将会如何?同时,用户数量也๣倍增了,又将会如何?希望数据在线保存好几年将会如何?如此一来,运行在硬件之上的这些程序的行为是否会完全不同?架构级的选择是在赌未来,当然需要明确可靠的理论知识——但这是进一步运用艺术的先决条件。第一次世界ศ大战联军总司令ferdinandfo9๗checນolesupérieuredeguerre的一次演讲中说:战争的艺术和其他艺术一样,有它的历史和原则——否则,就不能ม成其为艺术。本书不是cookbook,不会列出一串问题然后给出“处方”。本书的目标重在帮助开者和他们的经理提出犀利的问题。阅读和理解了本书之ใ后,你并不是永不再写出丑陋缓慢的查询了——有时这是必须ี的——但希๶望你是故意而为之、且有充足的理由á。目标读者本书的目标读者是:有丰富经验的sql数据库开者他们的经理数据库占重要地位的系统的软件架构师我希望一些dbຘa、尤其是数据库应用维护人员也๣能喜欢本书。不过,他们不是本书的主ว要目标读者。本书假定

----------ๅ-----ๅ-ๅ-------page3---ๅ----ๅ-ๅ-------ๅ--------ๅ

本书假定你已精通sql语言。这里所说的“精通”不是指在你大学里学了sql10่1并拿来aທ+的成绩,当然也并非指你是国际公认的sql专家,而是指你必须具有使用sql开数据库应用的经验、必须考虑索引、必须不把5000行的表当大表。本书๰的目标不是讲解连接、外连接、索引的基础知识,阅读本书过程中,如果你觉得某个ฐsql结构还显神秘,并影响了整段代码的理解,可先阅读几本其他sql书。另外,我假定读者至少熟悉一种编程语言,并了解计算机程序设计的基本原则。性能ม已很差ๆ、用户已๐抱怨、你已在解决性能问题的前线,这就是本书的假定。本书内容我现sql和战争如此相像,以至于我几乎沿用了《孙子兵法》的大纲,并保持了大部分题目名称注2๐。本书分为ฦ1้2章,每一章包含许多原则ท或准则,并通过举ะ例的方式对原则ท进行解释说明,这些例子大多来自于实际案例。第1章,制定计划:为性能ม而设计讨论如何设计高性能ม数据库第2章,动战争:高效访问数据库解释如何进行程序设计才能高效访问数据库第3章,战术部署:建立索引揭示为何建立索ิ引,如何建立索ิ引第4章,机动灵活:思考sql语句解释如何设计sql语句第5章,了如指掌:理解物理实现揭示物理实现如何影响性能第6章,锦囊妙计:认识经典sql模式包括经典的sql模式、以及如何处理第7章,变换战术:处理层次结构说明如何处理层次数据第8章,孰优孰劣:认识困难,处理困难指出如何认识和处理比较棘手的情况第9章,多条战线:处理并讲解如何处理并第10章,集中兵力:应付大数据量讲解如何应付大数据量第11章,精于计谋:挽救响应时间分享一些技巧,以挽救设计糟糕的数据库的性能第12章,明察秋毫:监控性能收尾,解释如何定义和监控性能ม本书约定本书使用了如下印刷็惯例:等宽courier表示sql及编程语言的关键字,或表示table、索引或字段的名称,抑或表示函数、代码及命令

-----ๅ--------ๅ---ๅ------ๅ-paທge4-ๅ--ๅ-------ๅ-----------ๅ--ๅ

输出。等宽黑体courier表示ิ必须ี由用户逐字键入的命令等文本。此风格仅用于同时包含输入、输出的代码示ิ例。等宽斜ฒ体cນourier表示这些文本,应该被用户的值代替。总结:箴言,概括重要的sql原则。注意提示、建议、一般性注解。为相关主ว题有用的附加信息。代码示ิ例本书๰是为了帮助你完成工ื作的。总的来说,你可以将本书的代码用于你的程序和文档,但是,若要大规模复制代码,则必须联系o'reilly申ã请授权。例如:编程当中ณ用了本书的几段代码,无需授权;但出售或分o'reilly书籍中案例的cd-rom光盘,需要授权。再如:回答问题๤时,引用了本书或其中的代码示例,无需授权;但在你的产品文档中大量使用本书代码,需要授权。o'reilly公司感谢但不强制ๆ归属声明。归属声明通常包括书名、作者、出版商、isbn。例如“theartofsqlbystéphanefa肉lt9ithpeterrobsoncນopyrightc20่06o'ูreillymediaທ,ไ0-59๗6๔-00894-5”。如果你对代码示例的使用出了上述范围,请通过permissions@oreilly联系出版商。评论与提问我们已尽力核验本书所的信息,尽管如此,仍不能保证本书๰完全没有瑕疵,而网络世界ศ的变化之ใ快,也使得本书๰永不过时的保证成为不可能。如果读者现本书内容上的错误,不管是赘字、错字、语意不清,甚至是技术错误,我们都竭诚虚心接受读者指教。如果您有任何问题,请按照以下的联系方式与我们联系。o'reillymedia,in9high9๗aynorth色bastopol,ไ9theusor9ternationalorlocal707๕829-ๅ01้04๒fax致谢本书原版用英语写作,英语既不是我的家乡๥话,又不是我所在国家的语言,所以写这样一本书要求极度乐观回想起来几近疯狂。幸运的是,peterrobson不仅为ฦ本书贡献了他在sql和数据库设计方面的知识,也๣贡献了持续的热情来修改我冗长的句子、调整副词位置、斟酌替换词汇。peterrobຘson和我在好几个大会上都碰过面,我们都是演讲者。jonaທthaທngenni9athangennick是o'reilly出版的sqlpo9athan是个非常尊重作者的编辑。由于他的专业、他对细节的关注、他的犀利视角,使本书的质量大大提升。同时,jonathan也使本书的语言更具“中大西洋”风味peter和我现,虽然我们保证按美国英语拼写,但还远远不够。

--ๅ-ๅ-----ๅ----------ๅ---ๅ--page5--ๅ----ๅ--ๅ-----ๅ-ๅ----ๅ-----

我还要感谢很多人,他们来自三个不同的大陆,阅读了本书全部ຖ或部分草稿并坦诚地提出意见。他们是:philippebertolino、ra9il9๗s、timgorman、jeaທn-paulmartin、sanjaທymishra、anthony摸linaທro、tiongsoohua。我特别感激laທrry,因为本书的思想最初来自于我们的e-ๅmail讨论。我也๣要感谢o'reilly的许多人,他们使本书得以出版。他们是:mar9o、jamiepeppard、mikekohnke、ron逼lodeau、jessamynread、andre9๗savikaທs。感谢naທn9haທrdt卓越的手稿编辑工作。特别ี感谢艳n-ๅarzeldurelle-marc慷慨第12章用到的图片。感谢paulmcນ9horter授权我们将他的战争图用于第6章。最后,感谢rogermaທn色r和steelbusinessiefing的职员,他们为peter和我了位于伦敦的办公室还有大量咖啡。感谢qianlenaashley了本书๰开始引用的《孙子兵法》的中文原文。作者介绍stéphaທnefa肉lt从1้98๖3年开始接触关系数据库。oracle法国成立早期他即加入此前是短暂的ibm经历和渥太华大学任教生涯,并在不久之ใ后对性能和调优产生了兴趣。19๗88年他离开了oracle,此后一年间,他进行调整,并研究过运筹学。之后,他重操旧业,一直从事数据库咨询工作,并于199๗8年创น办了肉gh色a公司肉gh色a。stéphaທnefaທ肉lt出版了fortraທnstru9umériques一书๰法语,dunod出版社ุ,198๖6,与didiersi摸n合作,并在ora9e和色lect分别为英国和北美oracle用户组杂志以及oracle杂志在线版上表了许多文章。他还是美国、英国、挪威等众多用户组大会的演讲者。peterrobson毕业于达拉谟大学地质专业1้968年,然后在爱丁堡大学任教,并于1975๓年获得地质学研究型硕士学位。在希腊ຘ度过了一段地质学家生涯之后,他开始在纽卡斯尔大学专攻地质和医学数据库。他使用数据库始于1้9๗77年,1981年开始使用关系数据库,1985年开始使用oraທcle,这期间担任过开工程师、数据架构师๲、数据库管理员等角色。1้980่年,peter参加了英国地质普查,负责指导使用关系数据库管理系统。他擅长sql系统,以及从组织级到部门级的数据建模。peter多次出席英国、欧洲、北美的oracle数据库大会,在许多数据库专业杂志上表过文章。他现任英国oracle用户组委员会主任,可通过peterrobson@justsql联系他。查询的识别

----ๅ----ๅ----ๅ----ๅ-ๅ------ๅpage6----ๅ--ๅ-ๅ-ๅ----ๅ-ๅ----ๅ------

有经验的朋友都知道,把关键系统从开环境切换到生产环境是一场战役,一场甚嚣尘上的战役。通常,在“攻击起日຅d-day”的前几周,性能测试会显示新系统达不到เ预期要求。于是,找专家,调优sql语句,召集数据库管理员和系统管理员不断开会讨论对策。最后,性能ม总算与以前的系统大致相当了尽管新系统用的是价格翻倍的硬件。人们常常使用战术,而忽略了战略๓。战略要求从大局上把握整个架构与设计。和战争一样,战略的基本原则并不多,且经常被忽视。架构错误的代价非常高,sql程序员必须准备充分,明确目标,了解如何实现目标。在本章中ณ,我们讨论编写高效访问数据库的程序需要实现哪些关键目标。查询的识别queryidentifi9๗qquueerryyiiddeennttiiffiiaaທttiioonn数个世纪以来,将军通过辨别军装颜๨色和旗帜等来判断各部队的位置,以此检查激战中部队行进情况。同样,当一些进程消耗了过多的cpu资源时,通常也可以确定是由á哪些正被执行的sql语句造成的。但是,要确定是应用的哪部ຖ分提交了这些sql语句却困难得多,特别是复杂的大型系统包含动态建立的查询的时候。尽管许多产品良好的监控工具,但要确定一小段sql语句与整个ฐ系统的关系,有时却非常困难。因此,要养成为程序和关键模块加注释的习๤惯,在sql中ณ插入注释有助于辨别ี查询在程序中的位置。例如:9色lectblah这些注释在查错时非常有用。另外,注释也有助于判断单独应用对服务器造成的负载有多大;例如我们希望本地应用承担更多工作,需要判断ษ当前硬件是否能承受突高负载,这时注释特别有用。有些产品还了专门的记录功能registrationfaທcilities,将你从“为每个ฐ语句加注释”的乏味工作中解放出来。例如oraທ9_info包,它支持4๒8个字符的模块名称摸dulename、3๑2个字符的动作名称aທ9ame和64个字符的客户信息,这些字段的内容可由á我们定制。在oracle环境下,你可以利用这个ฐ程序包记录哪个应用正在执行,以及它在何时正在做什么。因为应用是通过“oraclev$动态视图”能ม显示目前内存中生的情况向程序包传递信息的,于是我们可以轻易地掌握这些信息。总结:易识别的语句有助于定位性能问题。保持数据库连接稳定stabledataba色9sssttaabbຘlleeddaaທttaທabbaas色eoonnnneettiioonnss建立一个新的数据库连接,既快又方便,但这其中ณ往往掩藏着重复建立数据库连接带来的巨大开销。所以,管理数据库连接必须ี非常小心。允许多重连接——可能ม就藏在你的应用中ณ——的后果可能很严å重,下面即是一例。

----ๅ---ๅ-----ๅ----ๅ----ๅ---paທge7๕----ๅ--ๅ-----------ๅ------

不久前,我遇到一个应用,要处理很多小的文本文件。这些文本文件最大的也不过一百行,每一行包含要加载的数据及数据库等信息。此例中ณ固然只有一个数据库实例,但即使有上百个,这里所说明的原理也๣是适用的。处理每个文件的代码如下:openthefileuntiltheendoffileisreachedreadaro99ecttothe色rverspe9色rtthedaທtadis9ectclo色thefile上述处理工作令人满意,但当大量小文件都在极短的时间内到达时,可能应用程序来不及处理,于是积压大量待处理文件,花费时间相当可观。我用c语言编了个简单的程序来模拟上述情况,以说明频繁的数据库连接和中断所造成的系统性能ม下降问题。表2-1列ต出了模拟的结果。注意产生表2-1结果的程序使用了常规的in色rt语句。顺便提一下,直接加载dire9g的技术会更快。表2-1้:连接/中断ษ性能测试结果测试结果依次对每一行作连接/中ณ断74行秒连接一次,所有行逐个ฐ插入1้681行秒连接一次,以10行为一数组插入5๓914๒行秒连接一次,以1้00่行为一数组插入919๗0行秒

----ๅ---ๅ---ๅ---ๅ--ๅ----ๅ---ๅ-page8---ๅ----ๅ-----ๅ-------ๅ----ๅ

此例说明了尽量减少分别连接数据库次数的重要性。对比表中前后两ä次针ฤ对相同数据库的插入操作,明显现性能有显着提升。其实还可以做进一步的优化。因为ฦ数据库实例的数量势必有限,所以可以建立一组处理程序handler分别负责一个数据库连接,每个数据库只连接一次,使性能进一步提高。正如表2-1้所示,仅连接数据库一次或很少次的简单技巧ู,再加上一点额外工作,就能让效率提升200倍以上。当然,在上述改进的基础上,再将欲更新า的数据填入数组,这样就尽可能减少了程序和数据库核心间的交互次数,从而使性能产生了另一次飞跃。这种每次插入几行数据的做法,可以使数据的总处理能力又增加了5倍。表2-1中ณ的结果显示改进后的性能几乎是最初ม的1200่倍。为何有如此大的性能ม提升?第一个原因,也是最大的原因,在于数据库连接是很“重”的操作,消耗资源很多。在常见的客户服务器模式中现在仍广为使用,简单的连接操作背后潜藏着如下事实:先,客户端与远程服务器的监听程序listenerprogram建立联系;接着,监听程序要么创建一个进程或线程来执行数据库核心程序,要么เ直接或间接地把客户请求传递给已存在的服务器进程,这取决于此服务器是否为ฦ共享服务器。除了这些系统操作创น建进程或线程并开始执行之外,数据库系统还必须为每次色ssion建立新环境,以跟踪它的行为ฦ。建立新色ssion前๩,dbms还要检查密码是否与保存的加密的账户密码相符。或许,dbຘms还要执行登录触器logontrigger,还要初始化存储过程和程序包如果它们是第一次被调用。上面这些还不包括客户端进程和服务器进程之间要完成的握手协议。正因为ฦ如此,连接池9g等保持永久数据库连接的技术对性能才如此重要。第二个原因,你的程序甚至包括存储过程和数据库之间的交互也有开销。即使数据库连结已๐经建立且仍未中断ษ,程序和dbms核心之间的上下文切换9text9๗itcນh也๣有代价。因此,如果dbms支持数据通过数组传递,应毫不犹豫地使用它。如果该数组接口是隐式的aທpi内部使用,但你不能使用,那么明智的做法是检查它的默认大小并根据具体需要修改它。当然,任何逐行处理的方式都面临上下文切换的问题๤,并对性能ม产生严重影响——本章后面还会多次涉及此问题。总结:数据库连接和交互好似万里长城——长度越长,传递消息越耗时。战略๓优先于战术strategybeforetacticsssttrraatteeggyybຘbeeffoorreettaattiiss战略决定战术,反之则谬也๣。思考如何处理数据时,有经验的开者不会着眼于细微步骤,而是着眼于最终结果。要获得想要的结果,最显而易见的方法是按照业务规则规定的顺序按部就班地处理,但这不是最有效的方แ法——接下来的例子将显示,刻意关注业务处理流程可能ม会使

--ๅ--ๅ----ๅ---ๅ---------ๅ---page9๗-ๅ--------ๅ-------ๅ--ๅ-ๅ-ๅ---

我们错失最有效的解决方案。几年前,有人给了我一个存储过程,让我“尝试”着进行一下优化。为什么说是“尝试”呢?因为该存储过程已经被优化两次了,一次是由原开者,另一次是由一个自称oracນle专家的人。但尽管如此,这个存储过程的执行仍会花上20分钟,使用者无法接受。此存储过程的目的,是根据现有库存和各地订单,计算出总厂需要订购的原料数量。大体上,它就是把不同数据源的几个ฐ相同的表聚合aggregate到เ一个ฐ主表mastertaທble中ณ。先,将每个数据源的数据插入主表;接着,对主ว表中的各项数据进行合计并更新;最后,将与合计结果无关的数据从表中删除。针ฤ对每个数据源,重复执行上述步骤。所有sql语句都不是特别复杂,也没有哪个ฐ单独的sql语句特别ี低效。为了理解这个ฐ存储过程,我花了大半天时间,终于现了问题:为什么该过程要用这么เ多步骤呢?在from子句中加上包含union的子查询,就能得到所有数据源的聚合aggregation。一条色lect语句,只需一步就得到了结果集,而之前๩要通过插入目标表targettable得到结果集。优化后,性能的提升非常惊人——从20分钟็减至2๐0秒;当然,之ใ后我花了一些时间验证了结果集,与未优化前完全相同。想要获得上述的大幅提高性能,无需特别ี技能,仅要求站在局外思考thinkoutsidethebox的能力。之ใ前两ä次优化因“太关注问题本身”而收到了干扰。我们需要大胆的思维,站得远一些,试着从大局的角度看待问题๤。要问自己一些关键的问题:写存储过程之前,我们已有哪些数据?我们希๶望存储过程返回什么เ结果?再辅以大胆的思维,思考这些问题的答案,就能得到一个ฐ性能大幅提升的处理方式了。总结:考虑解决方案的细节之ใ前๩,先站得远一些,把握大局。先定义问题,再解决问题๤probຘlemdefinitionbeforesolutionpprroobຘblleemmddeeffiinniittiioonnbbeeffoorreessoolluuttiioonn一知半解是危险的。人们常在听说了新า技术或特殊技术之后——有时的确很吸引人——试图采用它作为新的解决方แ案。普通开者和设计师通常会立即采纳这些新“解决方แ案”,直到后来才现它们会产生许多后续问题。现成的解决方案中,非规范化设计引人注目。设计伊始,非规范化设计的拥护者就提出此方แ案,为了寻求“性能”而无视最终将会面临的升级恶魔——而事实上,在开周期早ຉ期,改进设计或学习如何使用join也是一个不错的选择。作为非规范化设计的一种手段,物化视图materiaທlizedvie9常被认为是灵丹ล妙药。物化视图有时被称为快照snapshot,这个更加平常的词更形象地反映了可悲的事实:物化视图是某时间点的数据副本。在没有其他办法时,这个理论上遭到质疑ທ的技术也未尝不值得一试,借用卡夫卡franzkaທfka的一句名言:“逻辑诚可贵,生存价更高。”然而,绝大部分问题都可借助传统技术巧妙解决。先,应学会充分利用简单、传统的技术。只有完全掌握了这些技术,才能ม正确评价它们的局限性,最终现它相当于新技术的潜在优势如果有的话。所有技术方案,都只是我们达到目标的手段。没有经验的开者误把新技术本身当成了目标。

---ๅ---------ๅ---ๅ------ๅ--page1้0-ๅ------ๅ------------ๅ--ๅ--

对于热衷于技术、过于看重技术的人来说,此问题就更为严å重。总结:先打基础,再赶时髦:摆弄新工具之前๩,先把手艺学好。直接操作实际数据operationsaທgainstaທctuaທldataooppeerraattiioonnssaaggaaiinnssttaທattuuaallddaທattaa许多开者喜欢建立临时工作表temporary9๗orktable,把后续处理使用的大量数据放入其中ณ,然后开始“正式”工ื作。这种方法广受质疑,反映了“跳出业务流程细节考虑问题”的能力不足。记住,永久表permaທnenttable可以设置非常复杂的存储选项ำ在第5๓章将讨论一些存储选项的设置,而临时表不能。临时表的索引如果有的话可能不是最优的,因此,查询临时表的语句效率比永久表的差ๆ。另外,查询之前๩必然先为ฦ临时表填入数据,这自然也多了一笔额外的开销。就算使用临时表有充足理由,若数据量大,也๣绝不能把永久ื表当作临时工作表来用。问题之一在于统计信息的自动收集:若没有实时收集要求,dbms通常会在不活动或活动少时进行统计信息收集,而这时作为临时工作表可能为空,从而使优化器收到了完全错误的信息。这些不正确且有偏差的统计信息可能造成执行计划exe9๗完全不合理,导致性能下降。所以,如果一定要用临时表,应确保数据库知道哪些表是临时的。总结:暂时工ื作表意味着以不太合理的方แ式存储更多信息。sql用ssqqll处理集合色tpro9sqls色ettpprrooeessssiinnggiinnssqqllsql完全基于集合色t来处理数据。对大部ຖ分更新或删除操作而言——如果不是针对整个表的话——你必须先精确定义แ出要处理的记录的集合。这定义了该处理的粒度granularity,可能ม是对大量记录的粗粒度操作,有可能ม是只影响少数记录的细粒度操作。将一次“大批量数据的处理”分割成多次“小块处理”是个坏主意,除非对数据库的修改太昂贵,否则不要使用,因为这种方法极其低效:1占用过多的空间保存原始数据,以备事务transa9๗回滚rollback之ใ需;2万一修改失败,回滚消耗过长的实践。许多人认为,进行大规模修改操作时,应在操作数据的代码中有规律地多安排些mit命令。其实,严格从实践角度来讲,“从头开始重做”比“确定失败生的时间和位置,接着已๐提交部分重做”要容易得多、简单得多、也๣快得多。处理数据时,应适应数据库的物理实现。考虑事务失败时回滚所需日志的大小,如果要为undo保存的数据量确实巨大,或许应该考虑数据修改的频率问题。也就是说,将大规模的“每月更新”,改为ฦ规模不大的“每周更新”,甚至改为规模更小的“每日຅更新”,或许是个有效方案。总结:几千个语句,借助游标cursor不断循环,很慢。换成几个语句,处理同样的数据,还是较慢。换成一个语句,解决上述问题,最好。

--------ๅ-----ๅ----ๅ---ๅ---page11้--ๅ----ๅ--ๅ----ๅ---ๅ-----ๅ---

sql动作丰富的ssqqll语句aທ9-pa9tsaattiioonn--ppaທakkeeddssqqllssttaatteemmeennttsssql不是过程性语言pro9guaທge,尽管也๣可以将过程逻辑procedurallogic用于sql,但必须小心。混淆声明性处理de9๗g和过程逻辑,最常见的例子出现在需要从数据库中提取数据、然后处理数据、然后再插入到数据库时。在一个程序或程序中的一个ฐ函数接收到特定输入值后,如下情况太常见了:用输入值从数据库中ณ检索到一个ฐ或多个另外的数据值,然后,借助循环或条件逻辑通常是ifthenel色将一些语句组织起来,对数据库进行操作。大多数情况下,造成上述错误做法的原因有三:根深蒂固的坏习惯、sql知识的缺乏、盲从功能需求规格说明。其实,许多复杂操作往往可由一条sql语句完成。因此,如果用户了一些数据值,尽量不要将操作分解为多条提取中间结果的语句。避免在sql中引入“过程逻辑procedurallogic”的主要原因有二。数据库访问,总会跨多个软件层,甚至包括网络访问。即使没有网络访问,也会涉及进程间通讯;额๩外的存取访问意味着更多的函数调用、更大的带宽,以及更长的等待时间。一旦这些调用要重复多次,其对性能的影响就非常可观了。在sql中引入过程逻辑,意味着性能和维护问题๤应由你的程序承担。大多数据库系统都了成熟ງ的算法,来处理join等操作,来优化查询以获得更高的效率。基于开销的优化器cost-bຘa色doptimizer,cbຘo是很复杂的软件,它早已不像刚推出时那样没什么用了,而在大部ຖ分情况下都是非常出色的成熟产品了,优秀的cbo查询优化的效率极高。然而,cbຘo所能改变的只有sql语句。如果在一条单独的sql语句中ณ完成尽可能多的操作,那么เ性能优化可以还由dbms核心负责,你的程序可以充分利用dbms的所有升级。也就是说,未来大部分维护工作从程序间接转移给了dbms供货商。当然,“避免在sql中引入过程逻辑”规则ท也有例外。有时过程逻辑确实能ม加快处理度,庞大的sql语句未必总是高效。然而,过程逻辑及其之后的处理相同数据的语句,可以编写到เ一个单独的sql语句中,cbo就是这么做的,从而获得最高效的执行方式。总结:尽可能多地把事情交给数据库优化器来处理。充分利用每次数据库访问profitabledataba色aທes色spprrooffiittaທaທbຘblleeddaທattaabbaas色eaaeesss色ess如果计划逛好几家商店,你会先决定在每家店买哪些东西。从这一刻起,就要计划按何种顺序购物才能少走冤枉路。每逛一家店,计划东西购买完毕,才逛下一家。这是常识,但其中蕴含的道理许多数据库应用却不懂得。要从一个表中提取多段信息时,采用多次数据库访问的做法非常糟糕,即使多段信息看似“无关”但事实上往往并非如此。例如,如果需要多个ฐ字段的数据,千万不要逐个ฐ字段地提取,而应

---ๅ--ๅ----------ๅ--------ๅpaທge12๐------ๅ----ๅ--ๅ-------ๅ----

一次操作全部ຖ完成。很不幸,面向对象oo的最佳实践提倡为ฦ每个ฐ属性定义一个get方法。不要把oo方法与关系数据库处理混为一谈。混淆关系和面向对象的概念,以及将表等同于类、字段等同于属性,都是致命的错误。总结:在合理范围内,利ำ用每次数据库访问完成尽量多的工作。dbms接近ddbbຘmmss核心9ellloos色enneessssttootthheeddbbmmskeerrnneell代码的执行越接近dbms核心,则执行度越快。数据库真正强大之处就在于此,例如,有些数据库管理产品支持扩展,你可以用c等较底层的语言为它编写新功能。用含有指针操作的底层语言有个缺点,即一旦指针处理出错会影响内存。仅影响到一个用户已很糟糕,何况数据库服务器就像“服务器”名字所指的一样出了问题会影响众多“用户”——服务器内存出了问题,所有使用这些数据的无辜的应用程序都会受影响。因此,dbms核心采取了负责任的做法,在沙箱sandbox环境中执行程序代码,这样,即使出了问题也๣不会影响到数据。例如,ora99๗和它自身之间实现了一套复杂的通信机制,此机制在某些方แ面很像控制数据库连结的方แ法,以管理两ä个或多个ฐ服务器上的数据库实例之ใ间的通信。到底采用plsql存储过程还是外部c函数,应综合比较后决定。如果精心编写外部c函数获得的好处过了建立外部ຖ环境和上下文切换9๗g的成本,就应采用外部函数。但需要处理一个ฐ大数据量的表的每一行时,不要使用外部ຖ函数。这需要平衡考虑,解决问题๤时应完全了解备选策略的后果。如要使用函数,始终应选dbms自带的函数。这不仅仅是为了避免无຀谓的重复劳动,还因为自带函数在执行时比任何第三方开的代码更接近数据库核心,相应地其效率也๣会高出许多。下面这个简单例子是用oracນlesql编写的,显示了使用oracle函数所获得的效率。假设手工输入的文本数据可能包含多个ฐ相邻的“空格”,我们需要一个函数将多个ฐ空格替换为一个ฐ空格。如果不采用oraທcນledaທtaທbaທ色10g开始的正规表达式regularexpression,函数代码将会是这样:99gi女ar9๗var9๗gvar9number:๘=lengthp_string;i逼nary_integer:=1้;j逼nary_integer;begin9hilei0loopv_string:=substrv_string,1,ไi||ltrimsubstrv_string,i+1;๙i:๘=instrv_string,ไ'';endloop;retur女_ຕstring;end;

----ๅ--------ๅ-----ๅ----ๅ--page14--ๅ--ๅ-ๅ-----ๅ--ๅ----ๅ---ๅ----

还有第三种方法:99๗gi女ar9varcນhar2isv_stringvar9g;len1number;๙len2๐numbຘer;bຘeginlen1:=lengthp_string;v_ຕstring:=replaທ9g,'',ไ''ู;len2:=lengthv_string;9hilelen2色lectsqueeze1้'azerythgfrdtr'2๐fromdual3azerythgfrdtrelap色d:00:00:0่000่sql色lectsqueeze2'ูazerythgfrdtr'ู2fromdual3๑azerythgfrdtrelaທp色d:00่:0่0:00่01sql色lectsqueeze3'azerythgfrdtr'ู2fromdual3azerythgfrdtrelap色d:0่0:๘00:000่0

---------ๅ--ๅ-------ๅ-----paທge15๓-ๅ-----------ๅ----ๅ-ๅ------

那么,如果每天要调用该空格替换操作几千次呢?我们构造一个接近现实负载的环境,下面的代码将建立一个ฐ用于测试的表并填入随机数据,已๐检测上面三个函数是否有性能差异:createtaທblesqueezablerandom_ຕtextvarchar2๐50de9ary_integer;j逼nary_ຕinteger;k逼nary_integer;v_stringvare1้,100่;v_ຕstring:=dbຘms_randomstring'ูu',5๓0;9๗hilej9๗hen9๗stillsomethingel色el色

-----------ๅ-----ๅ-----ๅ--page1้9---ๅ----ๅ-ๅ-------ๅ--------ๅ

end数值或日期的比较则简单明了。操作字符串ธ可以用oracle的greatest或least,或者mysql的str9色rt语句增加过程逻辑,具体办法是多重in色rt及条件in色rt注3,并借助merge语句。如果dbms了这样语句,毫不犹豫地使用它。也就是说,有许多逻辑可以放入sql语句中;虽然仅执行多条语句中的一条这种逻辑价值不大,但如果设法利用caທ色、merge或类似功能ม将多条语句合并成一条,价值可就大了。总结:只要有可能,应尽量把条件逻辑放到sql语句中,而不是sql的宿主ว语言中。一次完成多个ฐ更新multipleupdatesatoncemmuullttiipplleeuuppddaatteessaattoonnee我的基本主张是:如果每次更新的是彼此无关的记录,对一张表连续进行多次update操作还可以接受;否则,就应该把它们合并成一个ฐupdaທte操作。例如,下面是来自实际应用的一些代码注4๒:updatetbຘo_i女oice_extracນtor色tpgaທ_ຕstaທtus=0่pdatetbo_i女oice_ຕextractor色trd_status=09hererd_staທtusin1,3andi女_type=0;๙两个连续的更新是对同一个表进行的。但它们是否将访问相同的记录呢?不得而知。问题是,搜索条件的效率有多高?任何名为ฦtype或status的字段,其值的分布通常是杂乱ກ无章的,所以上面两个updaທte语句极可能对同一个表连续进行两次完整扫描:一个ฐupdate有效地利用了索ิ引,而第二个update不可避免地进行全表扫描;或者,幸运的话,两ä次update都有效地利用了索引。无论如何,把这两个update合并到เ一起,几乎不会有损失,只会有好处:updatetbo_ຕi女oice_extraທctor色tpgaທ_staທtus=๡9๗1then09๗hen3then0el色pga_ຕstatus

--------ๅ---ๅ---ๅ------ๅ---page20--ๅ-ๅ----ๅ-----ๅ---ๅ----ๅ--ๅ--

end,rd_staທtus=๡91then09hen3then0el色rd_statusendsin1้,3๑andi女_type=0;上例中ณ,可能出现重复更新相同字段为相同内容的情况,这的确增加了一小点儿开销。但在多数情况下,一个ฐupdate会比多个update快得多。注意上例中的“逻辑logic”,我们通过ca色语句实现了隐式的条件逻辑impli9๗allogic,来处理那ว些符合更新条件的数据记录,并且更新า条件可以有多条。总结:有可能的话,用一个语句处理多个更新;尽量减少对同一个表的重复访问。慎用自定义แ函数carefulu色ofu色r-9๗rittenfun9s将自定义แ函数u色r-9๗rittenfun9嵌到sql语句后,它可能ม被调用相当多次。如果在色lect语句的选出项列表中使用自定义函数,则每返回一行数据就会调用一次该函数。如果自定义函数出现在9here子句中ณ,则每一行数据要成功通过过滤条件都会调用一次该函数;如果此时其他过滤条件的筛选能力不够强,自定义函数被调用的次数就非常可观了。如果自定义函数内部ຖ还要执行一个查询,会生什么情况呢?每次函数调用都将执行此内部查询。实际上,这和关联子查询cນorrelaທtedsubquery效果相同,只不过自定义函数的方式阻碍了基于开销的优化器cost-ๅba色doptimizer,cbo对整个查询的优化效果,因为“子查询”隐藏在函数中ณ,数据库优化器鞭长莫及。下面举例说明将sql语句隐藏在自定义函数中的危险性。表flights描述商务航班,有航班号、起飞时间、到เ达时间及机场iata代码注5等字段。iaທta代码均为ฦ三个字母,有9000่多个ฐ,它们的解释保存在参照ั表中,包含城市๦名称若一个城市有多个机场则应为ฦ机场名称、国家名称等。显然,显示航班信息时,应该包含目的城市的机场名称,而不是简单的iaທta代码。在此就遇到เ了现代软件工程中的矛盾之ใ一。被认为是“优良传统”的模块化编程一般情况下非常适用,但对数据库编程而言,代码是开者和数据库引擎的共享活动sharedactivity,模块化要求并不明确。例如,我们可以遵循模块化原则编写一个小函数来查找iaທta代码,并返回完整的机场名称:99๗airport_9๗9vaທrchaທr2๐is

--ๅ----ๅ--------ๅ--------ๅ-page21้--ๅ-------ๅ---ๅ---------ๅ--

9amevar9๗amefromiata_aທirport_codes9herecode=iata_ຕ9ame;๙end;๙对于不熟悉oracle语法的读者,在此做个说明,以下查询中ณtruncsysdate的返回值为“今天的00:00aທm”,日期计算以天为单位;所以起飞时间的条件是指今天8๖:3๑0am至4:0่0pm之间。调用aທirport_city函数的查询可以非常简单,例如:色le9umber,to_ຕchardeparture_time,'ูhh24๒:mi'departure,airport_cityaທrrival"to"fromflights9heredeparture_ຕtimebet9eentrunre_ຕtime这个ฐ查询的执行度令人满意;在我机器上的随机样本中,返回77๕行数据只用了018๖秒多次执行的平均值,用户对这样的度肯定满意统计数据表明,此处理访问了30่3个ฐ数据块,53๑个ฐ是从磁盘读出的——而且每行数据有个递归调用。我们还可以用join来重写这段代码,作为查找函数的替代方案,当然它看起来会稍微复杂些:色le9umbຘer,ไto_ຕcharfdeparture_time,'hh24:mi'depaທrture,acity"ิto"fromflightsf,iata_airport_codesa9herea9ddeparture_timebet9eentrunre_time

---ๅ-ๅ-----------ๅ----ๅ----ๅpage2๐2------ๅ----ๅ-ๅ--------ๅ----

这个查询只用了005๓秒统计数据同前๩,但没有递归调用。对于执行时间不到02秒的查询来说,度快了3倍似乎无关紧ู要,但在大型系统中,这些查询每天经常执行数十万次——假设以上查询每天只执行五万次,于是查询的总耗时为25๓小时。若不使用上述查找函数lookupfun9则只需要不到42分钟,度提高过300%,这对大数据量的系统意义แ重大,最终带来经济上的节约。通常,使用查找函数会使批处理程序的性能极差ๆ。而且查询时间的增加,会使同一台机器支持的并用户数减少,我们将在第9章对此展开讨论。总结:优化器对自定义函数的代码无能ม为力。sql简洁的ssqqllsuinctsql熟练的开者使用尽可能少的sql语句完成尽可能多的事情。相反,拙劣的开者则ท倾向于严å格遵循已๐制ๆ订好的各功能步骤,下面是个真实的例子:--getthestartoftheaountingperiod色le9๗todtperstafromtperrslt9herefiscal_year=to_charparam_dta,ไ'ูyyyy'andrslt_period='1้'ู||to_chaທrparam_dta,'mm';--gettheendoftheperiodoutofcນlosure色le9todtperclosurefromtperrslt9herefiscal_ຕyear=๡to_cນharparam_dta,ไ'yyyy'aທndrslt_ຕperiod='ู9'||to_charparam_dtaທ,'mm';就算度可以接受,这也๣是段极糟的代码。很不幸,性能专家经常遇到这种糟糕的代码。既然两个ฐ值来自于同一表,为什么要分别用两个不同的语句呢?下面用oraທcle的bulkcollecນt子句,一次性将两个值放到数组中,这很容易实现,关键在于对rslt_period进行orderby操作,如下所示ิ:色lectclosure_datebulk9todtperstaທarrayfromtperrslt9๗herefiscal_year=to_charparaທm_dta,ไ'yyyy'ูandrslt_periodin'1้'||to_ຕcharpaທram_dta,'mm'ู,

-----ๅ----ๅ---ๅ--ๅ-------ๅ--paທge23----ๅ----ๅ----ๅ-ๅ-----ๅ-----

'9'ู||to_charparam_dta,'ูmm'orderbyrslt_period;于是,这两个ฐ日期被分别保存在数组的第一个和第二个ฐ位置。其中,bulkcollect是plsql语言特有的,但任何支持显式或隐式数组提取的语言都可如法炮制。其实甚至数组都是不必要的,用以下的小技巧注6,这两ä个值就可以被提取到两个变量中ณ:色lectmaxdecodesubຘstrrslt_period,1้,ไ1,--checນkthefirstcharacter'1้',closure_daທte,-ๅ-ifit's'1'returnthedate9e9aທntto_date'14๒1010่66๔','ddmmyyyy',--other9i色somethingoldmaxdecodesubstrrslt_period,ไ1้,1,'9',cນlosure_ຕdate,--thedaທte9e9antto_date'14101066'ู,'ddmmyyyy'ู,intodtpersta,dtperclosurefromtperrslt9herefiscaທl_ຕyear=to_charpaທraທm_dta,'yyyy'andrslt_periodin'1้'||to_charparaທm_dta,'mm'ู,'9'||to_chaທrparam_dta,ไ'ูmm';在这个例子中,预ไ期返回值为两ä行数据,所以问题๤是:如何把原本属于一个字段的两行数据,以一行数据两ä个字段的方式检索出来正如数组提取的例子一样。为此,我们检查rslt_period字段,两行数据的rslt_period字段有不同值;如果找到เ需要的记录,就返回要找的日期;否则ท,就返回一个ฐ在任何情况下都远比我们所需日期要早的日期此处选了哈斯丁之役battleofhastings的日期。只要每次取出最大值,就可以确保获得需要的日期。这是个ฐ非常实用的技巧,也可以应用在字符或数值数据,第1้1้章会有更详细的说明。总结:sql是声明性语言de9guage,所以设法使你的代码越业务过程的规格说明。sqlssqqll的进攻式编程offensive9g9ithsql一般的建议是进行防御式编程9sively,在开始处理之前先检查所有参数的合法性。但实际上,对数据库编程而言,尽量同时做几件事情的进攻式编程有切实的优势。有个很好的例子:进行一连串ธ检查,每当其中一个检查所要求的条件不符时就产生异常。信用卡付款的处理中就涉及类似步骤。例如,检查所提交的客户身份和卡号是否有效,以及两ä者是否匹配;检查信用卡是否过期;最后,检查当前的支付额是否过了信用额度。如果通过了所

----------ๅ--ๅ-ๅ--------ๅ--page2๐4---ๅ-ๅ----------ๅ-----ๅ----

有检查,支付操作才继续进行。为了完成上述功能ม,不熟练的开者会写出下列ต语句,并检查其返回结果:色le9๗tfromcustomers9herecustomer_ຕid=provided_ຕid接