DB2 数据库管理最佳实践


内 容 简 介 IBM DB2 作为业界主流的数据库产品,广泛应用于金融、通信、烟草等行业。本书侧重于 DB2 数据 库管理,以实战为主要目标,内容涵盖软件安装配置、数据库环境搭建、存储规划、数据迁移、备份恢 复、锁、性能监控调优和常见的问题诊断等。通过循序渐进、深入浅出的讲解,力求让读者亲自动手实 验,结合实际案例,快速掌握 DB2 知识,独立完成日常运行维护管理工作。本书作者均有 IBM 原厂的工 作经历,实战经验非常丰富,本书将和大家分享他们的 DB2 数据库管理的最佳实践经验。 本书主要面向 DB2 DBA 和数据架构师。适用于具备一定数据库基础,有志于从事 DB2 DBA,或希 望考取 DB2 认证,或从其他数据库转向 DB2 的读者。 未经许可,不得以任何方式复制或抄袭本书之部分或全部内容。 版权所有,侵权必究。 图书在版编目(CIP)数据 DB2 数据库管理最佳实践 / 徐明伟,王涛编著. —北京:电子工业出版社,2011.9 ISBN 978-7-121-14485-1 Ⅰ. ①D… Ⅱ. ①徐… ②王… Ⅲ. ①关系数据库-数据库管理系统,DB2 Ⅳ. ①TP311.138 中国版本图书馆 CIP 数据核字(2011)第 175740 号 策划编辑:张春雨 责任编辑:李利健 特约编辑:赵树刚 印 刷:涿州市京南印刷厂 装 订:涿州市桃园装订有限公司 出版发行:电子工业出版社 北京市海淀区万寿路 173 信箱 邮编 100036 开 本:787×1092 1/16 印张:35.251 字数:909 千字 印 次:2011 年 9 第 1 次印刷 印 数:4000 册 定价:89.00 元 凡所购买电子工业出版社图书有缺损问题,请向购买书店调换。若书店售缺,请与本社发行部联系, 联系及邮购电话:(010)88254888。 质量投诉请发邮件至 zlts@phei.com.cn,盗版侵权举报请发邮件至 dbqq@phei.com.cn。 服务热线:(010)88258888。 VII 前 言 写作背景 随着信息技术的发展,企业对 IT 的依赖不断增长,数据规模不断扩大,对高可用、高性能 也提出了更高的要求,这些都对 DBA 提出了更高的挑战。与此同时,随着投入的增大,IT 成本 控制也成为每个公司的迫切需求。现在企业对 DBA 的要求已经不仅仅是单纯的维护,还要求从 整体架构、存储规划、应用设计和性能优化等方面提供咨询建议,以减少数据库系统的软硬件 支出,提高企业竞争力。 DB2 数据库是 IBM 信息管理家族的核心产品,与 Oracle、SQL Server 一起占据着国内关系 数据库领域的大部分市场份额,广泛应用在金融、通信、电力、烟草等行业。但与其他数据库 相比,DB2 相对封闭,学习曲线较陡,市场的书籍也不多,导致用户出现问题时很难独立诊断 和解决。 这些我是深有感触的,在 IBM 原厂工作的 5 年到目前 DB2 独立咨询,我一直战斗在一线, 曾经帮助很多银行、通信、电力、烟草、高速等客户进行 DB2 运维支持、性能调优和问题诊断, 并为几十家企业提供 DB2 企业内训。在这个过程中,与很多 IT 部门经理、DBA 进行过沟通和 交流,大家的普遍感觉是 DB2 比较稳定,在海量数据处理方面性能很好,但 DB2 的公开资料及 懂 DB2 的工程师太少,导致运维成本相对较高。特别希望市场上能多出现一些 DB2 实战的书籍。 信息技术的发展日新月异,留给 DBA 学习的时间越来越短,独立承担运维管理任务并快速 解决问题是对每个 DBA 的基本要求。为了让一些初学者少走弯路,让中级水平的人快速提升, 高手也能有所收获,决定写一本 DB2 数据库运维管理的书,将自己多年的实战经验毫无保留地 奉献给大家,也算为 DB2 的推广做一点微不足道的贡献。 有了想法,真正写起来却是比较孤寂和艰辛的,因为白天还要为客户做一些性能调优、问 题诊断和培训工作,只能利用晚上的宝贵时间。当我邀请我的好朋友王涛一起来写时,立即得 到了他的积极响应,当晚我们就完成了该书的大概框架。以后的进展就比较顺利了,我们将每 天写的部分互相审阅,交流意见和想法,最大程度地保证本书的质量。 本书在创作之初就定位为“面向实践”,前前后后共串联了几十个命令和工具,每个重要的 命令和工具均配有相关实例演示,便于实战操作。在写作过程中,我们也随时补充在客户现场 遇到的一些调优和问题诊断案例,并积极听取客户和培训学员的一些宝贵意见和建议。 由于 DB2 的知识点太多,要想在一本书里兼顾所有几乎是不可能的,因此,决定将一些相 对高级但又比较实用的特性,如数据库分区、表分区、压缩、MQT、高级优化器、HADR、DB2 联邦和 Q 复制等集成到《DB2 数据库高级管理》一书,这样大家可以根据自己的需要来选择。 DB2 数据库管理最佳实践 VIII 本书组织结构 第一部分(第 1、2 章)DB2 概述 本部分共分两章,第 1 章概要介绍了 DB2 的产品发展和演变历史,第 2 章概述 DB2 体系结 构,从静态和动态两个方面介绍了 DB2 的对象层次关系和执行流程。该部分起到提纲挈领、统 领全局的作用,更有助于后续章节的把握。 第二部分(第 3~7 章)DB2 部署和规划 本部分共分 5 章,详细介绍了从产品安装到实例创建、数据库创建、表空间规划管理、表 创建到访问数据的每一个步骤。存储规划是数据库系统上线前最重要的环节,如果规划不好会 对以后的性能造成很严重的影响,并且系统一旦上线就很难改动。我们会与大家一起分享部署 规划的最佳实践。 第三部分(第 8~11 章)DB2 运维管理 本部分共分 4 章,数据迁移章节介绍了数据迁移的方法和几个最常用的命令:export、import、 load、db2move、db2look、db2dart 等,针对最常遇到的问题,我们用问答的方式演示了解决方 案。日志原理较难理解,也是最容易出问题的地方,我们用最简洁、易懂的语言向大家阐述。 作为一名 DBA,确保数据安全和可用是最重要的任务(注意:没有之一),在备份和恢复章节, 通过十几个案例让你对备份恢复技术烂熟于心。 本部分是核心内容,信息量大,知识点多,需要读者多实践、多操作才能融会贯通。 第四部分(第 12~16 章)DB2 监控和调优 监控和调优本身是作为运维管理的一部分,但由于监控调优的内容相对高级和深入,因此 另成体系。本部分共包含 5 章,DB2 通过锁来实现多用户并发情况下数据的一致性,了解和掌 握 DB2 进程模型和内存模型对于调优和问题诊断都有重要的意义,在监控章节,重点介绍了几 个命令:snapshot 快照、db2pd、db2top 等,并重点介绍一些常用的 KPI。在调优章节,通过几 个真实案例介绍性能分析和调优的思路和方法。 第五部分(第 17、18 章)DB2 问题诊断及安全 本部分包含两章内容,在实际操作和运维中,几乎每天都不可避免地遇到各类问题,有些 问题可能会对系统造成严重影响,第 17 章介绍了几种问题诊断的方法,学会这些方法和工具将 大大加快问题诊断和处理的速度。第 18 章介绍了数据库安全,可以保证数据库安全稳定的运行, 为企业和个人减小损失。 致初学者 学习 DB2 有前途吗? DB2 好找工作吗? 没有环境,能学好 DB2 吗? 前 言 IX 学 DB2 好还是 Oracle 好? 这些几乎是每个初学者都曾经遇到的问题。对于前两个问题,我们所能够回答的就是只要 你学得好,工作就好找,就会有前途,现在很多公司在热招 DB2 职位,只要机会来的时候能够 准备好、把握住就可以了。 对于第三个问题,有人可能会说,现在绝大多数的生产都是 UNIX 系统,没有环境。其实 Linux 上的 DB2 命令和 UNIX 上几乎完全一样,我们完全可以轻松地利用 Linux 虚拟机开始学 习,包括一些问题的模拟、性能优化和了解仓库特性,都可以在 Linux 中实现。 对于第四个问题,这是个见仁见智的问题。如果是从来没有接触过数据库的读者,并且有 志于在数据库领域发展,我们建议直接学 DB2,毕竟先入为主,比从 Oracle 转过来要轻松许多。 这个市场上懂 Oracle 的人太多了,高手如云,想出人头地做出成绩太难了。 因此,对于初学者的建议就是抓紧行动,去除浮躁,踏踏实实地把基础打牢,待机会来临 的时候抓住。 致谢 本书在写作过程中得到了很多朋友的支持、鼓励和帮助。特别感谢我的好兄弟管伟(北京 快通高速路有限公司首席 DBA)、王敬东(北京电讯盈科首席 DBA)和孙云峰(深圳市共济 科技有限公司产品经理),他们几乎逐字审阅了本书内容,从用户的视角提出了很多宝贵意见 和建议。 感谢加拿大丰业银行 DB2 性能优化架构师唐迅,IBM 加拿大实验室的专家 Arthur Chen、 Deliang Han、Angela Yang 和 Shen Li,他们从专家视角审核了本书的大部分章节。 最后,感谢我的家人和朋友在背后的默默支持,是他们激励着我不断前进。 徐明伟 2011 年 7 月 III 序 一 炎炎夏日,泡上一杯香浓的咖啡,望着熙熙攘攘的人群来往于黄昏时分的大型超市、货柜 车有条不紊地进出物流中心的停车场、遍布全市的银行网点仍在持续地运作……这就是我们数 据库管理员的生活。这些大企业的背后是 IT 系统维系着业务的运行,而我们的数据库管理员则 正维护着它们的核心系统。 在我看来,数据库管理员所从事的工作,挑战和高薪并存,令人自豪。我经常见到数据库 管理员享受着不菲的待遇,特别是 DB2 管理员。尽管数据库管理员受人尊敬,大有前途,但是 成为一名高水平的数据库管理员却并非易事。从国内最大的 DB2 社区 db2china 的反馈来看,缺 乏高质量的 DB2 书籍是重要原因之一。 由于我的工作性质,我对数据库书籍非常关注。2011 年以前,书店里的 DB2 书籍相比 Oracle 书籍来讲,可谓寥寥无几。不过,这种情况在 2011 年有了很大改变,因为有一批 DB2 技术书 籍将会陆续上市,这对有志成为高水平数据库管理员的读者来讲是一件幸事。 本书由徐明伟和王涛编写,注重实用,内容由浅及深,涵盖 DB2 的管理、运行和维护, 将大量一线的实际服务和培训案例融入其中,从而将一系列相关的分散知识点真正形成了一个 知识面。 今天,国内已有越来越多的技术人员在使用 DB2,相信这本书能对学习和使用 DB2 提供较 大帮助,希望它能成为数据库管理员的良师益友,为您答疑解惑,点亮前进之路。 王飞鹏(IBM 中国开发实验室 DB2 资深顾问) 2011 年 7 月 IV 序 二 关于本书作者 近些年,我主要负责人民银行部分全国大集中系统的数据库设计工作,其中多有接触 IBM 厂商的产品及相关技术专家。回想第一次与明伟接触是在 2007 年,当时明伟作为 IBM 专家, 负责国库系统的数据库设计和支持,他对技术的炽热和求真态度,以及丰富的 DB2 实战经验给 我留下了深刻印象。后来明伟离开 IBM 公司,从事独立的 DB2 咨询,为很多金融、烟草、通信 运营商、钢铁等行业客户提供 DB2 培训和性能调优、咨询业务,获得了很好的口碑。近日,明 伟嘱我为其与王涛合作的新作作序,在仔细阅读了部分章节之后,我欣然应允。 关于本书 很多朋友在谈及一本书时,时常会问 “这是不是一本好书?”之类的问题,现在,借我几 年前读过的一本关于数据库性能优化的书籍的阅读感受来跟大家分享一下: 2004-03-04 新书到手,彻夜通读,阅后收获颇丰。 2005-01-08 再读一年前所购之书,感觉的确是技术方面的好书,尤其针对实验指导、原理 解析颇为透彻。 2006-01-14 至今日,再读性能优化相关书籍,备感自身“胸无点墨”,虽案牍颇丰,然皆为 众家之言。 2007-06-07 纲举目张方能借左右而言“他物”(遍寻 DB2 优化器相关资料,无果,最后只 能参考《Database Management Systems, Third Edition》)。 2008-05-01 同一位读者,不同时期读同一本书,感悟有所不同。 上述为我近些年在阅读相关书籍时的一个心路历程的缩影,单就本书而言,作为一本运维管理 实践的技术书籍,涵盖了系统上线前规划、安装配置和上线后的运维管理、性能优化和问题诊断。 本书不仅仅是从一个产品的视角来描述,更是以一个“从实践中来,到实践中去的”理念来与大家 分享作者多年的工作积累与心得,虽部分章节只言片语一带而过,但细细品味,仍有值得回味之处。 作者本着严谨、务实和求真的心态,一切案例从工作中提炼,由浅入深娓娓道来,但限于篇章及本 书的受众,只好根据当前所描述的场景及专门论述的领域做一分支专题的论述。 最佳实践从来都不是一件独立而绝对的事情,要想在系统架构设计过程中设计出高性能、高 并发并能满足用户需求的系统,必须在综合业务需求、专业技术特性、开发规范等众多系统建设 参照物的前提下,开展系统规划、数据库架构、应用架构、存储规划等设计工作。而本书正是借 助作者多年的故障诊断、性能优化、企业培训等诸多方面的丰富的经验,向大家展示了 DB2 在当 前系统信息化建设过程中的最佳设计理念。理论与实践相结合是最好的学习方法,而本书正是希 望通过这样的论述方式将精彩的实战内容展示给读者。但有道是“纸上得来终觉浅,绝知此事要 躬行”,希望大家在“为伊消得人憔悴”的征途上,“衣带渐宽终不悔”地勇往前行。 明伟、王涛他们睿智、勤奋并乐于分享,经过深思熟虑奉献给了大家一本技术与实践并重 的实战指导书籍,让我们在阅读的过程中也一并感受到了作者的严谨与新颖的阐述问题的方式, 同时,我们也透过作者对技术的分析,领略了他们对技术的热爱与乐于分享的激情。 希望本书能成为您数据库征途上的挚友,成为您起航前往罗马的灯塔。 李小平(人民银行金融电子化公司数据架构师) V 序 三 2005 年夏天,我在 IBM 多伦多实验室认识王涛,作为“前辈”,在他加入 IBM 后我有幸成 为他的指导者(mentor),与他共同工作过一段时间。 与王涛的最初接触中,他就给我留下了很深的印象。作为刚刚从大学毕业的新人, 他对计算机技术与概念的领悟之快令人惊讶,很多基本的原理只需要简单解释一遍,他 就可以从中推论出许多相对复杂的内部实现机制与设计初衷。同时,王涛是我见过最有 天赋的 C 程序员之一,尽管技术支持的工作不需要亲自书写代码,但是我们需要阅读并 理解大量可能隐藏着设计缺陷的代码,并从中找出发生问题的部分。王涛可以从 DB2 众 多模块的复杂逻辑中找到隐藏极深的 bug,并在最短时间内提出有效的解决方案。 王涛于 2011 年初加入了 DB2 三线高级问题诊断小组(Advanced Problem Determination, APD),该小组在全球只有不到 15 人,由世界各地顶尖的 DB2 问题分析专家组成(亚太地区 4 人,分别在日本、韩国与澳大利亚),算是 IBM DB2 领域的“最后一道防线”。该小组负责 分析世界各地最为紧急与复杂的问题。当 DB2 引擎的开发人员无法判定问题出现的模块,或 者需要帮助理清复杂问题的思路时,通常会寻求高级问题诊断小组的帮助。而很多时候,作为 提供问题解决方案的技术负责人,王涛需要协调各个不同模块与产品之间的开发人员,精确定 位每一个模块各自所需研究的方向。 在短短的几年中,我欣喜地看到王涛从一个大学毕业的新人一步步成长为 DB2 世界级顶 尖的专家,成功地为无数家全球顶尖企业解决过各种各样的疑难杂症,其中包括美国国防部与 军方、波音、可口可乐、雀巢、SAP、HP、丰业银行、中国电信、美洲银行、汇丰银行、三 星等知名机构与企业。 如今我作为数据库性能架构师,同样在不断寻找着市面上理论与实践并重的资料。王涛与 徐明伟合写的这一本书在高层面用简单的语言阐述了许多 DB2 中苦涩难懂的模块,又能够结 合王涛在多年的技术支持生涯中解决的经典案例,为读者带来实际的问题分析思路。 在阅读本书后,我个人的感觉是,本书并没有像很多其他的资料一样,如同“参考手册”般 用严谨并枯燥的语言尝试覆盖每一个模块的所有细枝末节(比如各种参数与语法的使用),而是着 重于最重要的“概念”的剖析,以及阐述“为什么”一个产品或者应用需要这样或者那样的设计。 客观地讲,本书并不是一种“随用随查”类型的参考资料。由于作者使用了大量的篇幅进 行原理的剖析与案例的讲解,并没有在细节的知识点上做过多的纠缠。读者在需要某些特定的 知识点时,可以参考 IBM 的信息中心得到语法信息。本书的目的是为了给读者一个高层面的 理解,知道每一个模块“为什么这样设计”,以及对不同类型问题“应该如何思考”的启发。 在读者阅读案例时,我建议并不要过于专注每一个案例的解决方法,而是要理清问题诊断的思 路。同样现象的问题可能会有 100 种不同的原因,而作者在本书中并没有尝试列出所有可能发 生问题的方面,而是给出一系列一步步的思考过程,让读者在日常工作中尽量能够做到真正的 “问题诊断”,而不是“凭经验尝试”。 相信本书能够为真正希望了解 DB2 的读者打开一扇大门,在知道不同语法命令的同时,能够 深入地理解产品本身的设计思路与问题诊断的思考过程,成为您在成长道路上的一个朋友。 唐 迅(数据库性能架构师) 2011 年 5 月于多伦多 VI 序 四 从 2005 年至今,我主要负责中国移动通信集团海南有限公司 BOSS 系统和经营分析系统的 数据库管理、维护和优化工作,也负责相关项目的架构设计。正如不能把所有的鸡蛋都放在一 个篮子一样,企业在选型的时候一般会平衡几家厂商,数据库产品也是如此。目前,在通信行 业,BOSS 计费系统一般架构在 Oracle 数据库上,而一些仓库类系统选择 DB2 的比较普遍,当 然,这几年一些交易型系统也开始越来越多地选用 DB2 数据库。 作为同时维护 Oracle 和 DB2 的一线 DBA,我与 IBM、Oracle 厂商或第三方的相关技术专 家有过频繁接触,给我印象较深的是徐明伟,当时他负责我公司经营分析系统数据仓库的性能 调优工作,帮助我们解决了困扰很长时间的日报和月报表运行时间慢的问题。他在 DB2 数据库 优化工作中视野开阔、手段多样、实战经验丰富,这与他多年的实战经验积累是分不开的。 听闻他要和另外一位顶级 DB2 专家王涛写书的消息,我感觉这对一线 DBA 是个福音。回 想自己当初自学 DB2 时,可供参考的资料和书籍少之又少,大大增加了学习曲线。 作为 DBA,需要的是实际操作和动手能力,而非一些枯燥的理论分析。徐明伟和王涛合著 的这本书用朴素的语言无私地与我们分享了他们多年来积累的实践经验。这些实践涵盖从系统 上线前的规划、上线的安装配置和上线后的运行维护管理、性能优化和问题诊断等多方面的丰 富经验。在案例的分析上,着重思路、方法和手段,而非单纯的结论。 对于一线 DBA 和即将步入这个行业的工程师,本书可以作为一本绝佳的参考书,相信每个 人都必将有所收获。同时也非常期待两位作者的高级数据库管理著作。 吉训尊(海南移动通信公司 首席 DBA) 2011 年 7 月 X 目 录 第一部分 DB2 概述 第 1 章 DB2 产品介绍 ························································································ 1 1.1 数据模型 ······························································································ 1 1.2 DB2 历史 ······························································································ 2 1.3 DB2 版本 ······························································································ 3 1.4 DB2 9 主要功能增强 ················································································ 5 1.5 DB2 认证 ······························································································ 8 1.6 DBA 的任务和职责 ················································································· 8 1.7 IBM 信息管理产品概述 ············································································ 9 1.8 小结 ··································································································· 10 1.9 判断题 ································································································ 11 1.10 参考文献 ···························································································· 11 第 2 章 DB2 体系结构 ······················································································· 12 2.1 DB2 体系结构简介 ················································································· 12 2.2 对象层次关系 ······················································································· 15 2.3 数据访问过程 ······················································································· 16 2.4 数据库工具 ·························································································· 18 2.5 小结 ··································································································· 19 2.6 判断题 ································································································ 19 2.7 参考文档 ····························································································· 20 第二部分 DB2 部置和规划 第 3 章 安装 DB2 软件 ······················································································· 21 3.1 软件安装 ····························································································· 21 3.1.1 软件获取 ····················································································· 22 3.1.2 安装前检查 ·················································································· 22 3.1.3 安装 ··························································································· 23 3.1.4 补丁升级 ····················································································· 25 3.1.5 版本升级 ····················································································· 28 3.2 小结 ··································································································· 30 3.3 判断题 ································································································ 31 3.4 参考文档 ····························································································· 31 目 录 XI 第 4 章 实例管理 ······························································································ 33 4.1 什么是实例 ·························································································· 33 4.2 创建实例 ····························································································· 34 4.2.1 在 Windows 平台下创建实例 ···························································· 34 4.2.2 在 UNIX/Linux 平台下创建实例 ························································ 35 4.3 启动/停止/列出实例 ················································································ 37 4.4 更新实例 ····························································································· 38 4.5 删除实例 ····························································································· 39 4.6 实例参数 ····························································································· 39 4.7 管理服务器(DAS) ·············································································· 40 4.8 小结 ··································································································· 41 4.9 判断题 ································································································ 41 第 5 章 数据库创建和存储管理 ············································································ 43 5.1 数据库结构 ·························································································· 43 5.2 建库、表空间 ······················································································· 45 5.3 表空间维护管理 ···················································································· 50 5.3.1 表空间监控 ·················································································· 50 5.3.2 表空间更改 ·················································································· 52 5.3.3 表空间状态 ·················································································· 55 5.3.4 表空间高水位 ··············································································· 59 5.3.5 深入 DMS 表空间 ·········································································· 65 5.4 存储设计最佳实践 ················································································· 67 5.5 小结 ··································································································· 71 5.6 判断题 ································································································ 71 第 6 章 数据库连接 ··························································································· 73 6.1 远程连接概述 ······················································································· 73 6.2 节点和数据库编目 ················································································· 74 6.3 常见的数据库连接问题 ··········································································· 76 6.4 小结 ··································································································· 78 6.5 判断题 ································································································ 79 第 7 章 数据库对象 ··························································································· 80 7.1 模式 ··································································································· 81 7.2 表 ······································································································ 81 7.2.1 表约束 ························································································ 84 7.2.2 表状态 ························································································ 85 7.2.3 表压缩 ························································································ 86 Contents XII 7.2.4 表分区 ························································································ 87 7.3 索引 ··································································································· 88 7.4 视图 ··································································································· 94 7.5 昵称 ··································································································· 94 7.6 序列(Sequence) ·················································································· 94 7.7 自增字段 ····························································································· 96 7.8 大对象(LOB) ···················································································· 98 7.9 函数 ································································································· 101 7.10 触发器 ····························································································· 102 7.11 存储过程 ·························································································· 103 7.12 小结 ································································································ 109 7.13 判断题 ····························································································· 109 第三部分 DB2 运维管理 第 8 章 数据迁移 ···························································································· 110 8.1 数据迁移概述 ························································································ 111 8.2 文件格式 ···························································································· 111 8.2.1 DEL 格式 ··················································································· 111 8.2.2 ASC 格式 ··················································································· 112 8.2.3 PC/IXF ······················································································· 112 8.2.4 Cursor ························································································ 112 8.3 export ································································································· 112 8.4 import ································································································ 113 8.5 load ··································································································· 115 8.5.1 load 步骤及原理 ··········································································· 115 8.5.2 load 表状态 ················································································ 118 8.5.3 load 的 copy 选项 ·········································································· 119 8.5.4 set integrity 完整性检查 ································································· 125 8.6 12 个怎么办 ························································································ 129 8.6.1 出现了 load pending 了怎么办 ························································· 129 8.6.2 在客户端 load 问题 ······································································ 130 8.6.3 要加载的数据是 Excel 格式怎么办 ··················································· 131 8.6.4 要导出/加载的数据不是逗号/双引号分隔怎么办 ································· 131 8.6.5 文件中的列比要导入的表中的字段多怎么办 ······································ 133 8.6.6 文件中的列比要导入的表中的字段少怎么办 ······································ 133 8.6.7 要导入/导出大字段(LOB)怎么办 ················································· 134 8.6.8 sequence 数据怎么办 ···································································· 135 8.6.9 导入 identity 数据怎么办 ······························································· 136 目 录 XIII 8.6.10 要加载的数据有换行符怎么办 ······················································ 139 8.6.11 迁移出现乱码怎么办 ··································································· 141 8.6.12 表数据从一个表空间迁移到另外一个表空间怎么办 ··························· 143 8.7 db2look/db2move ················································································· 146 8.7.1 db2move 工具介绍 ······································································· 146 8.7.2 db2look 工具介绍 ········································································ 146 8.7.3 db2look+db2move 迁移案例 ··························································· 147 8.8 db2dart ······························································································ 151 8.9 小结 ································································································· 153 8.10 判断题 ····························································································· 153 第 9 章 备份恢复 ···························································································· 155 9.1 备份恢复概述 ····················································································· 155 9.2 DB2 日志 ··························································································· 158 9.2.1 日志机制和原理 ·········································································· 158 9.2.2 日志参数配置最佳实践 ································································· 163 9.2.3 日志监控和维护管理 ···································································· 168 9.2.4 其他日志相关的考虑 ···································································· 171 9.2.5 经常遇到的日志问题 ···································································· 172 9.3 备份 ································································································· 176 9.3.1 离线备份 ··················································································· 178 9.3.2 在线备份 ··················································································· 178 9.3.3 表空间备份 ················································································ 179 9.3.4 增量备份 ··················································································· 179 9.3.5 备份介质检查 ············································································· 180 9.3.6 备份监控 ··················································································· 183 9.4 恢复 ································································································· 183 9.4.1 崩溃恢复 ··················································································· 183 9.4.2 版本恢复 ··················································································· 184 9.4.3 前滚恢复 ··················································································· 192 9.4.4 删除表恢复(dropped table recovery) ·············································· 196 9.5 常见备份恢复场景及遇到的问题 ····························································· 199 9.5.1 宕机后数据库连接 hang 的处理 ······················································· 199 9.5.2 循环日志模式下的离线备份恢复 ····················································· 200 9.5.3 归档日志模式下的备份恢复 ··························································· 201 9.5.4 归档日志模式下前滚恢复的几个时间戳 ············································ 203 9.5.5 同版本不同实例下的数据库备份恢复(表空间是自动存储管理) ··········· 205 9.5.6 同版本不同实例下的数据库备份恢复(表空间是非自动存储管理) ········ 206 9.5.7 不同版本不同实例下的数据库恢复 ·················································· 206 Contents XIV 9.5.8 从生产库到测试库恢复的案例分析 ·················································· 207 9.5.9 历史文件过大造成数据库停止响应案例分析 ······································ 209 9.5.10 恢复时解压类包问题 ·································································· 210 9.5.11 备份失败问题 ············································································· 211 9.6 小结 ································································································· 212 9.7 判断题 ······························································································ 212 第 10 章 DB2 日常运维 ···················································································· 213 10.1 日常运维工具概述 ·············································································· 213 10.2 Runstats ···························································································· 214 10.2.1 Runstats 原理 ············································································ 214 10.2.2 Runstats 用法 ············································································ 215 10.3 Reorg ······························································································· 217 10.3.1 为什么需要 Reorg ······································································ 217 10.3.2 Reorg 用法 ················································································ 221 10.3.3 Reorg 最佳实践·········································································· 225 10.4 Rebind ····························································································· 226 10.5 获取数据库占用空间的大小 ·································································· 227 10.6 获取某个表空间占用空间大小 ······························································· 228 10.7 获取某个表/索引占用空间的大小 ··························································· 229 10.8 小结 ································································································ 231 10.9 判断题 ····························································································· 232 第 11 章 锁和并发··························································································· 233 11.1 锁和隔离级别概述 ·············································································· 233 11.2 锁的模式和兼容性 ·············································································· 235 11.2.1 表锁模式 ·················································································· 236 11.2.2 行锁模式 ·················································································· 239 11.2.3 表锁和行锁兼容性 ······································································ 243 11.3 锁的各种问题 ···················································································· 245 11.3.1 锁等 ························································································ 245 11.3.2 锁超时 ····················································································· 246 11.3.3 死锁 ························································································ 246 11.3.4 锁升级 ····················································································· 248 11.3.5 锁转换 ····················································································· 249 11.4 锁监控和诊断 ···················································································· 249 11.4.1 锁的分析思路和方法 ··································································· 249 11.4.2 锁升级(lock escalation)的诊断分析 ·················································· 250 11.4.3 锁等(lock wait)的捕获与诊断分析 ·············································· 250 目 录 XV 11.4.4 锁超时(lock timeout)的捕获与诊断分析 ······································· 254 11.4.5 死锁(deadlock)的捕获与诊断分析 ··············································· 259 11.4.6 9.7 锁事件监控器 ······································································· 263 11.5 锁和并发调优 ···················································································· 269 11.6 Currently Committed 机制 ····································································· 270 11.7 小结 ································································································ 273 11.8 判断题 ····························································································· 273 第四部分 DB2 监控和调优 第 12 章 DB2 进程/线程模型 ············································································· 274 12.1 提要 ································································································ 274 12.2 从操作系统看进程和线程 ····································································· 275 12.3 DB2 V8/V9.1 进程模型 ········································································ 278 12.3.1 代理进程 ·················································································· 279 12.3.2 分区内并行··············································································· 280 12.3.3 分区间并行(DPF) ··································································· 281 12.3.4 预取进程(prefetcher) ······························································· 282 12.3.5 页面清理进程(Page Cleaner) ····················································· 284 12.3.6 其他进程 ·················································································· 285 12.3.7 实例 / 数据库启动步骤 ······························································· 287 12.4 DB2 9.5/9.7 线程模型 ·········································································· 289 12.5 小结 ································································································ 291 12.6 判断题 ····························································································· 291 第 13 章 DB2 内存模型 ···················································································· 292 13.1 从操作系统看内存 ·············································································· 292 13.2 DB2 8/9.1 内存模型············································································· 294 13.2.1 实例共享内存段 ········································································ 295 13.2.2 数据库共享内存 ········································································ 296 13.2.3 应用程序组共享内存 ·································································· 299 13.2.4 私有内存 ·················································································· 300 13.3 DB2 9.5/9.7 内存模型 ·········································································· 301 13.3.1 实例内存 ·················································································· 302 13.3.2 应用程序内存············································································ 302 13.3.3 自动内存调节(Self Tuning Memory Management,STMM) ················ 303 13.4 内存监控 ·························································································· 305 13.4.1 db2mtrk ··················································································· 305 13.4.2 db2pd -dbptnmem ······································································· 306 13.4.3 db2pd -memset / db2pd -mempool ···················································· 307 Contents XVI 13.5 小结 ································································································ 310 13.6 判断题 ····························································································· 310 第 14 章 DB2 监控工具 ···················································································· 312 14.1 snapshot 命令行监控 ············································································ 313 14.2 snapshot 管理视图 ··············································································· 314 14.3 db2pd ······························································································ 315 14.4 db2top ······························································································ 328 14.4.1 实时监测 ·················································································· 329 14.4.2 历史信息收集 ············································································ 330 14.4.3 子窗口 ····················································································· 331 14.5 DB2 事件监控器 ················································································ 340 14.6 小结 ································································································ 341 14.7 判断题 ····························································································· 341 第 15 章 性能监控和分析方法 ··········································································· 343 15.1 收集数据 ·························································································· 343 15.1.1 操作系统级别性能监控 ······························································· 344 15.1.2 数据库级别性能监控 ·································································· 354 15.1.3 数据收集的频度········································································· 387 15.1.4 小结 ························································································ 389 15.2 分析数据 ·························································································· 389 15.2.1 瓶颈分类与原理介绍 ·································································· 389 15.2.2 性能分析思路 ············································································ 397 15.2.3 性能分析案例 ············································································ 403 15.2.4 小结 ························································································ 437 15.3 判断题 ····························································································· 437 第 16 章 优化器与性能调优 ·············································································· 438 16.1 优化器简介 ······················································································· 438 16.2 性能调优简介 ···················································································· 450 16.2.1 索引 ························································································ 457 16.2.2 排序 ························································································ 463 16.3 KPI ································································································· 477 16.3.1 缓冲池命中率(bufferpool hit ratio) ··············································· 477 16.3.2 有效索引读 ··············································································· 479 16.3.3 包缓存命中率 (package cache hit ratio) ········································ 480 16.3.4 平均结果集大小········································································· 481 16.3.5 同步读取比例 ············································································ 482 目 录 XVII 16.3.6 数据、索引页清除 ····································································· 483 16.3.7 脏页偷取(dirty page steal) ························································· 483 16.3.8 缓冲区读写 I/O 响应时间 ····························································· 484 16.3.9 Direct I/O 时间··········································································· 485 16.3.10 直接 I/O 读取(写入)的次数 ····················································· 485 16.3.11 编目缓冲区插入比例 ································································· 486 16.3.12 排序指标 ················································································ 486 16.3.13 基于事务的指标度量 ································································· 487 16.3.14 检测索引页扫描 ······································································· 490 16.3.15 日志写入速度 ·········································································· 491 16.3.16 查询执行速度 ·········································································· 491 16.3.17 实例级性能指标 ······································································· 492 16.3.18 操作系统级指标 ······································································· 492 16.4 小结 ································································································ 494 16.5 判断题 ····························································································· 494 第五部分 DB2 问题诊断 第 17 章 问题诊断 ·························································································· 495 17.1 概述 ································································································ 495 17.2 日志信息错误 ···················································································· 496 17.3 宕机 ································································································ 498 17.4 挂起 ································································································ 503 17.5 错误信息 ·························································································· 506 17.5.1 SQLCODE ················································································ 507 17.5.2 db2trc ······················································································ 513 17.5.3 strace ······················································································· 519 17.6 分析数据收集工具 ·············································································· 522 17.7 IBM 服务支持体系 ············································································· 528 17.8 小结 ································································································ 528 17.9 判断题 ····························································································· 529 第 18 章 数据库安全 ······················································································· 530 18.1 安全概述 ·························································································· 530 18.2 认证机制 ·························································································· 531 18.3 权限控制 ·························································································· 532 18.3.1 管理权限 ·················································································· 532 18.3.2 对象特权 ·················································································· 535 18.3.3 权限设计案例············································································ 537 18.4 审计机制 ·························································································· 540 Contents XVIII 18.5 DB2 安全最佳实践 ············································································· 545 18.6 其他安全技术增强 ·············································································· 545 18.7 小结 ································································································ 545 18.8 判断题 ····························································································· 545 18.9 参考文献 ·························································································· 546 第三部分 DB2 运维管理 数据迁移是 DBA 重要的工作内容之一。广义的数据迁移,既包括平台间数据库迁移,也包 括同一数据库不同表间的数据迁移,还包括数据表与文件之间的数据导入/导出。针对不同的场 景,DBA 要在众多的方案中选择最优的解决方案: 同构平台间的数据迁移,如从生产库系统到测试库,最简单的迁移方法是通过对生产库做 备份,然后在测试机上进行数据库恢复。这部分内容我们将在第 9 章重点讲述。 异构平台间的数据迁移,如从 Windows 平台到 Linux 系统的数据迁移,这种场景不能采用 数据库备份恢复,只能先用 db2look 导出表结构,并将表数据导出来,然后用导入到目标库。 灾难或故障情况下的数据挽回,DB2 提供了 db2dart 工具,可以在实例都无法启动的情况下 将数据导出。该工具一般用于没有进行数据库备份,但发生了日志故障或其他操作故障,而没 有其他机制挽回数据的场景。 本章主要介绍表与文件间的数据导入/导出、表间数据迁移,内容安排如下:  数据迁移概述  常见的数据导入/导出文件格式。  Export、Import、Load 等数据迁移工具。  常见的导入/导出场景。  db2look+db2move 进行数据库迁移。  db2dart 的使用。 第 章 8 数据迁移 第 8 章 数据迁移 111 8.1 数据迁移概述 在日常工作中,经常有数据的导入/导出需求,比如将某些表的数据保存成报表形式,或将 文件数据加载到数据库等。为此,DB2 提供了很多工具供大家选择,如 export、 import、 load、 db2look、db2move 和 db2dart。图 8.1 直观地展现了这几个工具的使用,最上面虚线框部分 是工具支持的导入/导出文件格式,中间虚线框是 DB2 提供的导入/导出工具,最下面是 DB2 数据表。 图 8.1 数据导入/导出工具 8.2 文件格式 首先我们介绍 DB2 支持的文件格式:DEL、ASC、PC/IXF 和 WSF 格式,其中 DEL 和 ASC 格式是文本格式,PC/IXF 格式是 IBM 特有的二进制格式,WSF 格式主要用于和 Lotus 1-2-3 进 行数据导入/导出,新版本中将不再支持。 8.2.1 DEL 格式 定界 ASCII 格式(DEL)是 DB2 用于数据交换的最常用格式。这种格式包含 ASCII 数 据,使用字符分隔符分隔列值,分隔符用来标识数据元素的起始和结束,最主要的分隔符有 以下几种。 112 DB2 数据库管理最佳实践  字符分隔符:界定字符字段的起始。在默认情况下,用双引号(“”)作为字符分隔符。  列分隔符:界定列的结束。默认用逗号(,)作为列分隔符。  行分隔符:用来标识一行或一个记录的结束。默认用换行符作为分隔符。 以下是 DEL 格式的例子: 330,"Burke",66,"Clerk",1,+49988.00,+00055.50 340,"Edwards",84,"Sales",7,+67844.00,+01285.00 350,"Gafney",84,"Clerk",5,+43030.50,+00188.00 8.2.2 ASC 格式 定长 ASCII(ASC)格式,顾名思义,这种文件类型包含定长 ASCII 数据,每个数据长度 与列定义相同,不足的用空格补齐,行与行之间通过换行符分隔,例子如下: 330 Burke 66 Clerk1 +49988.00 +00055.50 340 Edwards 84 Sales7 +67844.00 +01285.00 350 Gafney 84 Clerk5 +43030.50 +00188.00 8.2.3 PC/IXF PC/IXF(IXF)是 IBM 特定的二进制格式,适用于在异构平台间进行数据迁移,IXF 的优 点是数据占用空间小,而且包含表结构的定义,可以通过 IXF 文件重建表。 8.2.4 Cursor 游标(Cursor)是非常重要的一个概念,它提供了一种对从表中检索出的数据进行操作的 灵活手段。可以把游标想象为一个指针,刚开始指向结果集中的第一条数据,当第一条数据读 取完成后,游标会自动跳转到下一条数据。 如果在两张表之间进行数据迁移,最容易想到的方法是先将数据从一张表导出来,存到一 个文件中,然后将这个文件的数据导入到另外一张表。而采用游标,数据不需落地,效率比较 高,因此比较适于表间数据迁移。只有 LOAD 支持游标,其余几种工具不支持。 下面是使用 Cursor 进行表间迁移的例子: DECLARE mycursor CURSOR FOR SELECT col1, col2, col3 FROM tab1; LOAD FROM mycursor OF CURSOR INSERT INTO newtab; 8.3 export export 用于将表里的数据导出到文件中,在这里我们只介绍常用的几个选项,命令的详细语 第 8 章 数据迁移 113 法请参考 IBM 信息中心。 export to filename 用来指定将导出的数据放在文件中,of filetype 指定导出文件的 格式,如 IXF、DEL 或 WSF 等。messages 用来保存导出过程中发生的错误或警告信息。 select-statement 通过 SQL 语句指定导出的数据。 lobs to 指定大对象数据存放的目录,该目录下生成的文件将保存大对象数据,在默 认情况下,该文件的命名规则是:.<00x>.lob。如果该表只有一个 LOB 字段,则 x=1, 如果有多个 LOB 字段,则顺序编号,为每个 LOB 字段生成一个文件。如果不想按默认的文件 命名,可通过 lobfile 指定 LOB 文件名字。 如果希望控制导出数据的格式,比如日期的格式、字符串、分隔符等格式,这时可通过 modified by 文件修饰符定制文件格式。比如,CHARDELx 用于指定字符串定界符,COLDELx 用于指定字段定界符,x 是定界符符号。TIMESTAMPFORMAT 用于指定日期字段的时间表达格 式。用户需要确保导出的数据中不包含分隔符,否则在导入时会出现异常错误。 export 例子:  将 employee 表数据导出到 employee.del 文件中,并记录消息,代码如下: db2 "export to employee.del of del messages emp.msg select * from employee"  导出 employee 表数据,导出字符串用“' '”分隔,而不是默认的“" "”,代码如下: db2 "export to employee.del of del modified by CHARDEL'' messages emp.msg select * from employee"  导出 emp_resume 表数据,并将大对象数据存放到 lobs to 指定的目录下,通过指定 modified by lobsinfile,大对象字段的所有值都保存到一个文件中,代码如下: db2 "export to emp_resume.del of del lobs to d:\temp\lobs modified by lobsinfile select * from emp_resume"  导出 emp_resume 表数据,并将大对象数据存放到 lobs to 指定的目录下,通过指定 modified by lobsinsepfiles,大对象字段的每个值都存在一个独立的文件中,代码如下: db2 "export to emp_resume.del of del lobs to d:\temp\lobs modified by lobsinsepfiles select * from emp_resume" 8.4 import Import 用来将文件里的数据导入到表中,是 export 操作的逆向过程。在这里我们只介绍常 用的几个选项,详细命令语法请参考 IBM 信息中心。 import from 指定导入文件的位置和名字,of 指定输入文件的类型,如 114 DB2 数据库管理最佳实践 DEL、IXF 或 ASC 等。messages 用来记录导入过程中发生的错误或警告信息。 INTO table-name 用来指定导入数据的几种方式,目前支持 insert、insert_update、 replace、replace_create 和 create。9.7 版本以后 replace_create 和 create 将不再支持。  insert 用于追加,不改变表中已有数据。  insert_update 用于表中有主键的情况,如果导入数据与表中数据主键匹配,则 update, 否则 insert 追加。  replace 首先删除表数据,然后插入输入文件数据。由于 replace 会首先清空数据,所以 建议先做好备份。 import 命令还包含很多有趣的选项,如 commitcount 和 restartcount。 commitcount 参数的主要目的是避免事务日志满和锁升级。commitcount 表示 每导入 N 行数据后提交一次,而不是等所有数据都导入才提交。automatic 表示 DB2 内部会自动 计算什么时候提交,默认情况下,DB2 会自动使用 automatic 选项。 restartcount/skipcount N 表示跳过文件前 N 行数据,而从第 N+1 开始继续导入。该选项一般 用于导入过程中出现了错误,有些数据已经提交入库了,重新导入时就可以忽略这些已经入库 的数据。 在默认情况下,import 会在目标表加 X 锁(排他锁),不允许其他应用访问(allow no access)。 如果 import 允许其他应用读和写,可指定 allow write access 选项,这时 import 会在目标表上加 IX 锁,但该选项只能用于 insert 或 insert_update 操作。 import 例子:  将 employee.del 文件数据导入到 employee 表中,并记录消息,代码如下: db2 "import from employee.del of del messages emp.msg insert into employee"  将 employee.del 文件数据导入到 employee 表中,在文件中,字符串用“' '”分隔,而 不是默认的“" "”。allow write access 允许其他应用读或写,commitcount automatic 允 许 import 命令自动选择何时提交,代码如下: db2 "import from employee.del of del modified by CHARDEL'' allow write access commitcount automatic insert into employee"  将大对象数据导入到表中 lobs from 指定大对象数据的位置,代码如下: db2 "import from emp_resume.del of del lobs from d:\temp\lobs modified by lobsinfile insert into emp_resume" 第 8 章 数据迁移 115 8.5 load 8.5.1 load 步骤及原理 从原理上说,import 实际上还是会执行 insert、update、delete 等操作,处理每一行数据都要 通过 DB2 引擎,验证各种约束和触发器,并通过事务日志记录变化。 对于大数量的导入,比如一些仓库或分析类系统, import 就无法满足性能需求,这时 load 就可以大显身手了。load 不是一行一行地处理数据,而是对输入数据按照 DB2 物理存储方式进 行格式化,并将格式化的数据页直接写到数据库中,记录的日志很少,也不会检查 check 约束 和参照完整性约束,不触发触发器,因此特别适合大数据量的导入。 load 将文件或游标数据导入到表中,load 命令包含很多参数,在这里我们只介绍常用的几 个选项,详细命令语法请参考 IBM 信息中心。 LOAD FROM input_source OF input_type MESSAGES message_file [ INSERT | REPLACE | TERMINATE | RESTART ] INTO target_tablename load from 指定输入源,可能是文件或游标。of input_type 指定输入格式,如 DEL、ASC、PC/IXF 或 Cursor。在开始 load 之前,目标表必须已经存在。使用 messages 选项 可以捕获 load 期间遇到的错误、警告和相关信息。 load 支持以下 4 种动作:  insert 用于追加,不改变表中已有数据。  replace 首先删除表数据,然后插入输入文件数据。由于 replace 会首先清空数据,所以 建议先做好备份。  terminate 将终止 load 操作,并将数据恢复到 load 开始时的状态。注意:如果在 load … replace 时出现了 load pending,采用 load … terminate 会清空表。  restart 用于重启被中断的 load 命令。restart 会使用之前 load 时产生的临时文件,并从 最近的一点开始加载。因此,千万不要手动删除 load 所生成的任何临时文件。一 旦 load 成功完成,这些临时文件将自动被删除。在默认情况下,这些临时文件是在当前的工 作目录中创建的,可以使用 TEMPFILES PATH 选项指定存放临时文件的目录。 注意:replace 动作会先删除表中已有数据,如果在 load 过程中出现异常,则通过 load … terminate 将表清空。因此,对于重要的表,建议在 replace 之前先做好备份。 一个 load 操作包括装载、构建、删除和索引复制 4 个阶段。如果目标表不包含唯一性索引, 则仅会进行装载阶段;否则装载阶段后,会进行构建、删除与索引复制的另外 3 个阶段。可通 过 db2diag.log 观察每个阶段的执行情况。 116 DB2 数据库管理最佳实践 1)装载阶段(load) 装载阶段将源文件解析成数据物理存储的格式,直接装入到页中,而不通过 DB2 引擎。在 装入过程中,会收集索引键和表统计信息,并记录一致点。当数据不符合表定义时,这些数据 就被当做无效数据,无效数据是不会装载到表中的,但可以放在转储文件(dump file)中,并 记录在 message 消息文件中。可以使用 modified by dumpfile 修饰符来指定转储文件名和路径。 2)构建索引阶段(build) 如果加载的表上有索引的话,构建阶段基于装载阶段收集到的键创建索引。 3)删除重复值阶段(delete) 如果表上有主键或唯一性索引,此阶段将删除违反唯一键的行。注意,此阶段只检查违背 了唯一性约束的行,而不会检查 check 约束和参考完整性约束。可以创建一个异常表(exception table)来存储被删除的行,这样在 load 结束后可以查看异常表,并决定如何处理它们。如果没 有指定异常表,那么重复行将被删除。后面将更详细地讨论异常表。 4)索引复制阶段(index copy) 如果 load 指定了 allow read access 和 use tablespace 选项,那么此阶段会将索引数据从系统 临时表空间中复制到索引表空间中。 异常表需要用户事先创建,它的前 N 个字段具有与目标表相同的列定义。可在表的最后增 加两个列,一个是用于记录一个行何时被插入的时间戳列,另一个是用于存放一个行之所以被 当做异常的原因的 CLOB 列。异常表的定义如下: CREATE EMPEXP LIKE EMPLOYEE; ALTER TABLE EMPEXP ADD COLUMN TS TIMESTAMP ADD COLUMN MSG CLOB(32K) 我们通过一个例子,演示 load 加载过程,执行过程如图 8.2 所示。 图 8.2 load 过程 第 8 章 数据迁移 117 (1)目标表 t1 结构定义如下,empno 字段上定义了主键,seqno 为数值型,非空。并为 t1 表创建异常表 t1_exp。 db2inst1@dpf1:~> db2 "create table t1 (empno int not null primary key, name char(10), seqno int not null ) " db2inst1@dpf1:~> db2 "create table t1_exp like t1" db2inst1@dpf1:~> db2 "alter table t1_exp ADD COLUMN TS TIMESTAMP ADD COLUMN MSG CLOB(32K)" (2)创建如下数据文件 t1.del,一共 8 行数据,代码如下: db2inst1@dpf1:~> cat t1.del 10,"wang qi",1 20,"zhang san", 30,"xu xin",3 30,"chen yan",4 40,"aaaaa",x 50,"bbbb",6 50,"cccc",7 80,"li si",8 (3)将 t1.del 数据加载到 t1 表中,分析输出结果发现一共读取了 8 行数据,拒绝了 2 行, 删除了 2 行,代码如下: db2inst1@dpf1:~> db2 "load from t1.del OF DEL MODIFIED BY DUMPFILE=/data1/t1.dmp MESSAGES t1.msg INSERT INTO t1 FOR EXCEPTION t1_exp" Number of rows read = 8 Number of rows skipped = 0 Number of rows loaded = 6 Number of rows rejected = 2 Number of rows deleted = 2 Number of rows committed = 8 (4)加载完成后,检查目标表、转储文件、异常表和消息文件,然后决定如何处理被拒绝 的行。 首先检查目标表,发现有 4 条数据插入到表中: db2inst1@dpf1:~> db2 "select * from t1" EMPNO NAME SEQNO --------- ---------- ----------- 10 wang qi 1 30 xu xin 3 50 bbbb 6 80 li si 8 4 record(s) selected. 118 DB2 数据库管理最佳实践 然后检查 dump file,发现 2 行数据违背了表定义。empno=20 的行最后一列为 NULL,违背 了非空定义;empno=40 的最后一列为 x,违背了数值型定义。这个转储文件是在第(1)步装入阶 段完成的: db2inst1@dpf1:/data1> cat t1.dmp.load.000 20,"zhang san", 40,"aaaaa",x 最后检查异常表 t1_exp,发现 empno=30 和 empno=50 这两行违背了唯一性约束。异常表数据 是在第(3)步完成的: db2inst1@dpf1:/data1> db2 "select empno, name, seqno, ts, substr(msg,1,10) as msg from t1_exp" EMPNO NAME SEQNO TS MSG ----------- ---------- -------- -------------------------- ---------- 30 chen yan 4 2011-01-11-20.40.35.426691 00001I0000 50 cccc 7 2011-01-11-20.40.35.426691 00001I0000 2 record(s) selected. 以上通过一个实例为大家演示了 load 的全过程,目的是让大家了解内部执行细节,以便在 出现问题的时候能够诊断错误发生在哪个步骤。在实际操作中,如果能保证数据不违背唯一性 约束,则不需要使用异常表,否则建议使用。 8.5.2 load 表状态 DBA 在运维时,一般都会考虑所做的操作对业务的影响,对于 load 也是如此。默认情况 下, 当 load 表时使用 ALLOW NO ACCESS 模式,即 load 完成之前,不允许其他应用访问该表。 为了提高可用性,load 提供了 ALLOW READ ACCESS 模式,在 load 的时候,允许其它应 用访问 load 之前的原有数据,但不能访问新加载的数据。需要注意的是,load … replace 不支持 ALLOW READ ACCESS,因为 replace 操作会先删除已有表数据。 可以通过 load query 命令检查表状态,在某一时刻一张表可能会同时处于几种状态,只有 当 表处于 normal 状态时,才能对表进行正常的读写操作,当表处于其他状态时,我们要分析可能 的原因和解决方案。表 8.1 总结了 load 时可能出现的几种状态及解决方法。 表 8.1 LOAD 表状态 出现原因 解决方法 normal 正常状态 set integrity pending 如果目标表有 check 约束或 reference 约 束,那么 load 后该表将处于 set integrity pending 状态,表明表有约束还未检查 执行完整性检查: set integrity for immediate checked 第 8 章 数据迁移 119 续 表 LOAD 表状态 出现原因 解决方法 load in progress 正在数据加载过程中 load pending 数据提交前出现了故障,如表空间没有足 够的空间等 通过 load..terminate、load..replace 或 load..restart 解除挂起状态 read access only 目标表数据是可以读的,当 load 时指定了 allow read access,那表就会处于 read access only 状态 unavailable 表可能被删除了或从 backup 中恢复了 unknown 通过 load query 命令无法得知表的状态 8.5.3 load 的 copy 选项 在 load 命令中,有一个 copy 选项,很多 DBA 都吃过这个选项的苦头,有的甚至付出了惨 重代价,使用时必须要小心。copy 可以理解为备份,我们前面介绍过,load 记录的日志很少(在 build 索引、delete 重复值阶段会记录日志),那么在归档日志模式下(第 9 章会介绍循环日志、归 档日志),当恢复数据库并进行前滚时,如何恢复 load 加载的数据呢?如果能将 load 数据备份, 那问题就简单了,这就是 COPY 选项的目的所在。 前面我们介绍的一些命令都没有带 COPY 选项,那是假设我们的数据库是循环日志,COPY 选项只适用于归档日志模式,在循环日志模式下没有意义。 copy 支持 3 种方式:copy no、copy yes 和 nonrecoverable。 copy no 是默认方式,对于可恢复数据库来说,copy no 会将 load 表所属的表空间置于 backup pending 状态,意思就是提示 DBA 要在 load 后对表空间做备份,否则出了问题无法恢复。目标 表可以读,但不能进行增/删/改。而且 load 命令一旦发起,表空间立即处于 backup pending 状态, 即使终止 load 操作,也不能脱离此状态。 举例: (1)创建 sample 数据库,然后创建一个表空间 ts1,并在 ts1 中创建一张表 t3。接着 将 sample 设为归档日志模式。 inst20@db2server:~> db2sampl inst20@db2server:~> db2 connect to sample inst20@db2server:~> db2 "create tablespace ts1 " DB20000I The SQL command completed successfully. inst20@db2server:~> db2 "create table t3 (id int, name char(20)) in ts1" DB20000I The SQL command completed successfully. 120 DB2 数据库管理最佳实践 inst20@db2server:~> db2 update db cfg for sample using logarchmeth1 disk:/home/db2inst1/archlog DB20000I The UPDATE DATABASE CONFIGURATION command completed successfully. inst20@db2server:~> db2 backup db sample to /data1 compress Backup successful. The timestamp for this backup image is : 20110519005636 (2)将一组测试数据 Load 到 t3 表中。 inst20@db2server:~> more t3.del # 生成测试数据 1,'aaa' 2,'bbb' 3,'ccc' inst20@db2server:~> db2 "load from t3.del of del insert into t3" # 将这几条数据 加载到 t3 表 (3)T3 数据可以查看,但无法增删改。 inst20@db2server:~> db2 "select * from t3" ID NAME ----------- -------------------- 1 'aaa' 2 'bbb' 3 'ccc' 3 record(s) selected. inst20@db2server:~> db2 "update t3 set name='newccc' where id=3" DB21034E The command was processed as an SQL statement because it was not a valid Command Line Processor command. During SQL processing it returned: SQL0290N Table space access is not allowed. SQLSTATE=55039 (4)检查表空间状态,发现 ts1 处于 backup-pending。 inst20@db2server:~> db2 list tablespaces show detail Tablespaces for Current Database … Tablespace ID = 6 Name = TS1 Type = Database managed space Contents = All permanent data. Large table space. State = 0x0020 Detailed explanation: Backup pending (5)对 ts1 或整个数据库做备份,问题解决。 inst20@db2server:~> db2 backup db sample to /data1 compress 第 8 章 数据迁移 121 Backup successful. The timestamp for this backup image is : 20110519022834 copy yes 选项会在 load 结束时,自动对表所属的表空间做一次备份,load 结束后,表所在 的表空间不会处于 backup pending 状态,而为正常状态,但由于要备份,所需时间要长些。在 前滚恢复阶段,DB2 会使用这个备份文件恢复 load 过程中加载的数据。 举例: (1)仍然在 sample 数据库,t3 表上 Load 数据,使用 COPY YES 选项。 inst20@db2server:~> more t3.del # 生成测试数据 1,'aaa' 2,'bbb' 3,'ccc' inst20@db2server:~> db2 "load from t3.del of del insert into t3 copy yes to /data1 " SQL3109N The utility is beginning to load data from file "/home/inst20/t3.del" (2)使用 Copy yes 选项时,ts1 表空间不会处于 backup-pending 状态。 inst20@db2server:/data1> db2 "select * from t3" ID NAME ----------- -------------------- 1 'aaa' 2 'bbb' 3 'ccc' 1 'aaa' 2 'bbb' 3 'ccc' 6 record(s) selected. inst20@db2server:/data1/range> db2 list tablespaces Tablespaces for Current Database Tablespace ID = 6 Name = TS1 Type = Database managed space Contents = All permanent data. Large table space. State = 0x0000 Detailed explanation: Normal (3)Load 会产生如下的备份介质,其中备份文件中第二个字段“4”表示 Load 产生的备 份。这份备份将在数据库进行前滚恢复操作时用于重新创建 Load 操作对数据库的修改。以下 通过实例演示了前滚后,T3 表保留了 Load 的数据。 inst20@db2server:~> ls 122 DB2 数据库管理最佳实践 SAMPLE.0.inst20.NODE0000.CATN0000.20110519022834.001 SAMPLE.4.inst20.NODE0000.CATN0000.20110519022955.001 inst20@db2server:/data1> db2 restore db sample taken at 20110519022834 SQL2539W Warning! Restoring to an existing database that is the same as the backup image database. The database files will be deleted. Do you want to continue ? (y/n) y DB20000I The RESTORE DATABASE command completed successfully. inst20@db2server:/data1> inst20@db2server:/data1> db2 rollforward db sample to end of logs and stop Rollforward Status Input database alias = sample Number of nodes have returned status = 1 Node number = 0 Rollforward status = not pending Next log file to be read = Log files processed = S0000002.LOG - S0000002.LOG Last committed transaction = 2011-05-19-02.48.56.000000 Local DB20000I The ROLLFORWARD command completed successfully. inst20@db2server:/data1> inst20@db2server:/data1> db2 connect to sample Database Connection Information Database server = DB2/LINUX 9.5.5 SQL authorization ID = INST20 Local database alias = SAMPLE inst20@db2server:/data1> db2 "select * from t3" ID NAME ----------- -------------------- 1 'aaa' 2 'bbb' 3 'ccc' 1 'aaa' 2 'bbb' 3 'ccc' 6 record(s) selected. 如果想避免在 LOAD 完成后立即备份表空间,又不阻止查询和修改表数据,可采用 nonrecoverable 选项。但要注意的是,nonrecoverable 将表标记为不可恢复,如果以后需要恢复 表空间并且回滚到 nonrecoverable load 选项之后的某个时间点,这个表是不可恢复的,所有 与该 表相关的日志会被忽略,您只能删除并重新创建表。因此,这个方法一般用在表可以被重建的 第 8 章 数据迁移 123 场景中。 举例: (1)仍然在 sample 数据库,t3 表上 Load 数据,使用 NONRECOVERABLE 选项。 inst20@db2server:~> more t3.del # 生成测试数据 1,'aaa' 2,'bbb' 3,'ccc' inst20@db2server:~> db2 "load from t3.del of del insert into t3 nonrecoverable " SQL3109N The utility is beginning to load data from file "/home/inst20/t3.del" (2)当使用 nonrecoverable 选项时,表和 ts1 表空间都可以使用。 inst20@db2server:~> db2 "select * from t3" ID NAME ----------- -------------------- 1 'aaa' 2 'bbb' 3 'ccc' 1 'aaa' 2 'bbb' 3 'ccc' 1 'aaa' 2 'bbb' 3 'ccc' 9 record(s) selected. inst20@db2server:/data1/range> db2 list tablespaces Tablespaces for Current Database Tablespace ID = 6 Name = TS1 Type = Database managed space Contents = All permanent data. Large table space. State = 0x0000 Detailed explanation: Normal (3)如果执行前滚命令恢复数据库时,前滚操作将跳过 Load 的处理,将 Load 的表标记 为无效,对该表的任何操作都不能进行。此时只有将表删除、重新构建,或使用 Load 操作时间 点之后所做的数据库全备份或表空间备份来恢复该表。 inst20@db2server:~> cd /data1 inst20@db2server:/data1> db2 restore db sample taken at 20110519022834 SQL2539W Warning! Restoring to an existing database that is the same as the 124 DB2 数据库管理最佳实践 backup image database. The database files will be deleted. Do you want to continue ? (y/n) y DB20000I The RESTORE DATABASE command completed successfully. inst20@db2server:/data1> db2 rollforward db sample to end of logs and stop Rollforward Status Input database alias = sample Number of nodes have returned status = 1 Node number = 0 Rollforward status = not pending Next log file to be read = Log files processed = S0000002.LOG - S0000003.LOG Last committed transaction = 2011-05-19-04.20.15.000000 Local DB20000I The ROLLFORWARD command completed successfully. inst20@db2server:/data1> db2 connect to sample Database Connection Information Database server = DB2/LINUX 9.5.5 SQL authorization ID = INST20 Local database alias = SAMPLE inst20@db2server:/data1> db2 "select * from t3" ID NAME ----------- -------------------- SQL1477N For table "INST20.T3" an object "4" in table space "6" cannot be accessed. SQLSTATE=55019 在归档日志模式下,各位在选择这几种方法前,需要深入理解它们的优缺点。COPY NO 选 项会将表所在的表空间置于 backup pending,在这个状态中,只能查数据,而无法更改、删除, 因此,不建议在日常交易期间使用该方法;NONRECOVERABLE 选项的使用更要小心,当前滚 时,目标表的数据无法恢复,而必须删除。对于有些系统,用户在做 load 时业务应用程序必须 停止。为了快速完成 load ,用户可以考虑选择 NONRECOVERABLE ,但是在使用 NONRECOVERABLE 方式装载数据后,最好在启动业务应用程序以后对数据库进行在线备份, 确保被加载的表可以被恢复;COPY YES 选项会导致加载的时间变长。 举一个比较典型的例子。笔者的一个朋友在 2009 年底的某一天下午 1 点半左右,突然打电 话给我,说他们的交易系统几乎所有表只能查,不能增、删、改,应用几乎处于停滞。然后我 问他这边表做了什么操作没有,他说刚做了 Load,让他去查表空间状态,已经处于了 backup pending。然后继续问他,load 的命令怎么写的,是否有 copy Yes 等,他说使用的缺省 copy no 选项,并解释说他要加载的表其实是一张与业务无关的表,并且已经在测试机上做过测试,没 有任何问题,才到生产上 load 的。我问他测试机上和生产库上数据库日志的模式,结果测试机 第 8 章 数据迁移 125 上使用的是循环日志,生产上是归档日志模式。现在问题已经很明显了,他立即终止了 load 操 作,但已经无济于事,因为 load 命令一旦发出,表空间就会立即处于 backup pending 状态,必 须对该表空间做备份才能使其脱离此状态。由于表空间很大,大概 150GB,备份花了将近 1 个 半小时才完成,这段期间该交易系统处于停滞,造成了很恶劣的影响。对他最直接的影响就是 写检查,并扣除年终奖。 对于该问题,有两个解决方案:第一种是使用 copy yes to 备份 load 表数据;第二种 是使用一个独立的表空间,将要加载的表放在该表空间,即使备份表空间,也不会影响其他表 和业务。 最后,还是要提醒大家,对命令的使用一定要理解透彻,否则会造成难以挽回的损失。 提醒:在 load 操作前,一定要先检查当前数据库是否为可恢复数据库,即数据库日志是 否为归档日志。 8.5.4 set integrity 完整性检查 相信大家还记得,在第 7 章我们讲了 5 种约束,即 NOT NULL 约束、唯一性约束、主键约 束、参考完整性约束和检查约束。前面讲 load 的时候,我们提到,在装载阶段,不符合表定义 的输入数据不会被装载到表中,如 NOT NULL 约束。在删除阶段,load 会删除违反唯一性约束 的行。那么,对于表中包含了违背参照完整性约束和检查约束的数据,又该如何处理呢?load 程序不会检查这些约束,而是将表置于 set integrity pending 状态,当访问时,会提示以下错误: SQL0668N Operation not allowed for reason code "1" on table "". SQLSTATE=57016 为解除这种状态,需要通过 set integrity 命令检查数据完整性,该命令语法如下: 以下介绍几个重要选项的用法。  OFF:将检查关闭,同时将表处于 set integrity pending 状态。  IMMEDIATE CHECKED:对表立即做完整性检查,并将状态从 set integrity pending 状态 中脱离出来。当将主表从 set integrity pending 状态中脱离时,它的依赖表可能会处于此 126 DB2 数据库管理最佳实践 状态,可通过返回的警告信息观察(SQLSTATE 01586)。  IMMEDIATE UNCHECKED:不对表做检查,但将表从 set integrity pending 状态中脱离 出来。当表数据很大时,做完整性检查需要花费很长时间,因此如果能够确保加载的数 据都是正确的,采用此选项将会大大降低执行时间。 对于处在 set integrity pending 状态下的表,如果包含异常数据,则必须创建异常表,才能使 表脱离此状态。下面我们举例说明。 (1)创建一张表 t2,在该表上增加检查约束,并加载数据,代码如下: db2inst1@dpf1:/data1>$ db2 "create table t2 ( col1 char(10), col2 char(10))" db2inst1@dpf1:/data1>$ db2 "alter table t2 add constraint check1 check( col2 in ('A', 'B', 'C') ) " db2inst1@dpf1:~> cat t2.del "AAA","A" "BBB","B" "CCC","CCC" "DDD","BBB" db2inst1@dpf1:/data1>$ db2 "load from t2.del of del insert into t2" (2)查询表 t2,发现处于 SQL0668N,即 set integrity 状态。检查 t2 的完整性,如果没有 检测到数据异常,则将表脱离 set integrity pending 状态。如果检测到有数据违背完整性,则整个 操作回滚,表仍处于 set integrity pending 状态,代码如下: db2inst1@dpf1:/data1>$ db2 "select * from t2" COL1 COL2 ---------- ---------- SQL0668N Operation not allowed for reason code "1" on table "DB2INST1.T2". SQLSTATE=57016 db2inst1@dpf1:/data1>$ db2 set integrity for db2inst1.t2 immediate checked DB21034E The command was processed as an SQL statement because it was not a valid Command Line Processor command. During SQL processing it returned: SQL3603N Check data processing through the SET INTEGRITY statement has found integrity violation involving a constraint or a unique index with name "DB2INST1.T2.CHECK1". SQLSTATE=23514 (3)创建 t2 异常表,如果有数据违背完整性,则将其放入 T2EXP 异常表中。不管是否有 数据异常,t2 都将脱离 set integrity pending 状态,代码如下: db2inst1@dpf1:/data1>$ db2 "create table t2_exp like t2" DB20000I The SQL command completed successfully. db2inst1@dpf1:/data1>$ db2 "set integrity for db2inst1.t2 immediate checked for exception in t2 use t2_exp " SQL3602W Check data processing found constraint violations and moved them to exception tables. SQLSTATE=01603 db2inst1@dpf1:/data1>$ db2 "select * from t2_exp" 第 8 章 数据迁移 127 COL1 COL2 -------- ---------- CCC CCC DDD BBB 对于数据仓库系统来说,每天晚上都要加载多张表数据,如何查找哪些表处于 set integrity pending 状态呢?以下语句可用来检查表的状态、是否允许访问及表的主外键约束、检查约束等: SELECT TABNAME, STATUS, ACCESS_MODE, SUBSTR(CONST_CHECKED,1,1) AS FK_CHECKED, SUBSTR(CONST_CHECKED,2,1) AS CC_CHECKED FROM SYSCAT.TABLES WHERE STATUS= 'C' 对于参考完整性约束表,在主表上的 set integrity 命令会将其依赖表(迭代)置于 set integrity pending 状态,但如果该表有父表,则父表不会置于此状态。下例中,a 表是 b 表的子表,b 表 是 c 表的子表,c 表是 d 的子表,d 表是 e 表的子表。在 c 表上加载数据后,c 表会处于 set integrity pending 状态,而其他表并不会处于此状态。当在 C 上进行了 set integrity 检查后,它的子表 b 和 a 表会处于此状态。在这种主外键约束比较多的情况下,可能需要执行多次 set integrity 命令。 create table x.a (x int not null primary key); create table x.b (x int not null primary key); create table x.c (x int not null primary key); create table x.d (x int not null primary key); create table x.e (x int not null primary key); alter table x.a add foreign key (x) references x.b; alter table x.b add foreign key (x) references x.c; alter table x.c add foreign key (x) references x.d; alter table x.d add foreign key (x) references x.e; load from /dev/null of ixf replace into x.c; -- 在 c 表上加载数据,c 表应该处于 check pending status set integrity for x.c immediate checked; -- 对 c 表做完整性检查,c 表的依赖表会处于 check pending SQL3601W The statement caused one or more tables to automatically be placed in the check pending state. SQLSTATE=01586 select tabname from syscat.tables where status = 'C' --可通过此命令验证,A 和 B 都 处于此状态 a b 为此,我们编写了如下迭代脚本 set.sh,希望对大家有帮助: db2 connect to db1 db2 -tx +w "with gen(tabname, seq) as( select rtrim(tabschema) || '.' || rtrim(tabname) as tabname, row_number() over (partition by status) as seq from syscat.tables WHERE status='C' ), r(a, seq1) as (select CAST(tabname as VARCHAR(3900)), seq from gen where seq=1 union all select r.a || ','|| rtrim(gen.tabname), gen.seq from gen , r where (r.seq1+1)=gen.seq ), r1 as (select a, seq1 from r) select 'SET INTEGRITY 128 DB2 数据库管理最佳实践 FOR ' || a || ' IMMEDIATE CHECKED;' from r1 where seq1=(select max(seq1) from r1)" > db2FixCheckPending.sql db2 -tvf db2FixCheckPending.sql ./set.sh 执行后,会将 set integrity 表命令拼成一个语句,存到 db2FixCheckPending.sql。接连 执行几次 set.sh 后,会将所有处于 set integrity pending 的表脱离此状态。 下面通过几个实例详细说明 set integrity 的其他用法。 例子 1: 将 T1 表置于 set integrity pending 状态、不允许访问状态,同时将它的依赖表置于 set integrity pending 状态,代码如下: SET INTEGRITY FOR T1 OFF NO ACCESS CASCADE IMMEDIATE 例子 2: 不对 manager 表的外键约束和 employee 的检查约束做检查,并将该表脱离 set integrity pending 状态,代码如下: SET INTEGRITY FOR MANAGER FOREIGN KEY, EMPLOYEE CHECK IMMEDIATE UNCHECKED 例子 3: 在表中已经有数据的情况下增加外键或检查约束,但表中存在违背约束的数据,因此创建 约束不会成功。这时可通过 set integrity 命令先关闭约束检查,待创建完约束后,再进行数据完 整性检查,代码如下: db2inst1@dpf1:/data1>$ db2 "create table emp1 ( empno int not null primary key, empname char(20), demptno int)" db2inst1@dpf1:/data1>$ db2 "create table dept1 ( demptno int, deptname char(20) )" db2inst1@dpf1:/data1>$ db2 "insert into emp1 values(111, 'zhangming', 1)" db2inst1@dpf1:/data1>$ db2 "insert into emp1 values(222, 'ligang', 2)" db2inst1@dpf1:/data1>$ db2 "insert into dept1 values(1, 'dev')" -- dept1 表并 没有包含 deptno=2 的值 db2inst1@dpf1:/data1>$ db2 create table emp1_exp like emp1 db2inst1@dpf1:/data1>$ db2 SET INTEGRITY FOR emp1 OFF db2inst1@dpf1:/data1>$ db2 "ALTER TABLE emp1 ADD FOREIGN KEY (deptno) REFRENCES dept1 (deptno) ON DELETE CASCADE" db2inst1@dpf1:/data1>$ db2 SET INTEGRITY FOR emp1 IMMEDIATE CHECKED FOR EXCEPTION IN emp1 USE emp1_exp SQL3602W Check data processing found constraint violations and moved them to exception tables. SQLSTATE=01603 做 set integrity 的时候可能会遇到日志满的情况。针对于不同的选项,set integrity 记录日志 的方式不同,如 immediate unchecked 选项不会记日志,而 immediate checked 则会检查,当指定 第 8 章 数据迁移 129 异常表,并有异常数据插入到异常表的时候,这些操作就会写日志以便必要的时候回滚。所以, 对日志空间的占用依赖于异常数据的多少,如果是少量数据自然不会成为问题,而如果量很大 的情况,就可能出现日志满的情况。 8.6 12 个怎么办 8.6.1 出现了 load pending 了怎么办 如果在提交数据之前,表上的正在执行的 load 操作被异常终止,那么该表就处于 load pending 状态。若要使该表恢复到正常状态,则需要调用 load terminate、load restart 或 load replace 操作。 让我们来模拟一个 load pending。 首先创建一个大小为 256 页的表空间,然后在其中建一张表,代码如下: db2inst1@dpf1:~> db2 "create bufferpool bp8k size automatic" db2inst1@dpf1:~> db2 "create tablespace ts3 pagesize 8k managed by database using (file '/data1/ts2' 256) bufferpool bp8k" db2inst1@dpf1:~> db2 "create table t1 (id int, name char(50), desc char(50) ) in ts1" 创建如下的存储过程: db2inst1@dpf1:~> cat sp_insert.sql CREATE PROCEDURE sp_insert (IN count int) LANGUAGE SQL BEGIN DECLARE i INTEGER DEFAULT 0; while i db2 -td@ -f sp_insert.sql db2inst1@dpf1:~> db2 "call sp_insert(6500) " db2inst1@dpf1:~> db2 "export to t1.del of del select * from t1" 重建一个大小为 232 页的表空间,并将 t1.del 数据加载,由于无法分配新页,导致 load 出 现异常。通过 load query 命令返回的信息显示,t1 表处于 load pending 状态,查询时报 SQL0668N 130 DB2 数据库管理最佳实践 错误,reason code 为 3,代码如下: db2inst1@dpf1:~> db2 drop tablespace ts1 db2inst1@dpf1:~> db2 "create tablespace ts3 pagesize 8k managed by database using (file '/data1/ts2' 232) bufferpool bp8k" db2inst1@dpf1:~> db2 "create table t1 (id int, name char(50), desc char(50) ) in ts1" db2inst1@dpf1:~> db2 "load from t1.del of del insert into t1" db2inst1@dpf1:~> db2 load query table t1 Tablestate: Load Pending db2inst1@dpf1:~> db2 "select * from t1" ID NAME DESC ----------- ------------------------------------------------------------ SQL0668N Operation not allowed for reason code "3" on table "DB2INST1.T1". SQLSTATE=57016 在执行 load terminate 操作之后,该表就重新处于 normal 状态,代码如下: db2inst1@dpf1:~> db2 "load from /dev/null of del terminate into t1" db2inst1@dpf1:~> db2 "select * from t1" ID NAME DESC ----------- ------------------- ---------------------------------------- 0 record(s) selected. 可通过如下语句查找处于 load pending 状态的表,代码如下: db2inst1@dpf1:~> db2 "select substr(tabschema,1,10) as tabschema,substr(tabname, 1,20) tabname from sysibmadm.admintabinfo where load_status='PENDING'" TABSCHEMA TABNAME ---------- -------------------- DB2INST1 T1 8.6.2 在客户端 load 问题 如果要加载的数据在客户端,可通过 Load client from 语句加载数据。 当从客户端远程 load 数据时,当远程路径无效时,可能会发生 load in progress 状态,阻塞 其他应用对该表的读/写操作,代码如下: D:\temp>db2 "load client from d:\temp\xxxx of del insert into t1" SQL2036N 文件或设备 "d:\temp\xxxx" 的路径无效。 D:\temp>db2 "load client from d:\temp\xxxx.txt of del insert into t1" SQL0668N 不允许对表 "DB2INST1.T1" 执行操作,原因码为 "5"。 SQLSTATE=57016 在服务端执行 load query table 检查状态,发现处于 load in progress 状态,代码如下: db2inst1@dpf1:~> db2 load query table t1 SQL2036N The path for the file or device "d:\temp\xxxx" is not valid. 第 8 章 数据迁移 131 SQL3532I The Load utility is currently in the "UNKNOWN" phase. Tablestate: Load in Progress 在客户端执行 terminate 终止 load 操作,问题解决。 D:\temp>db2 "load client from d:\temp\xxxx.txt of del terminate into t1" SQL3501W 由于禁用数据库正向恢复,因此表所驻留的表空间将不被置于备份暂挂状态。 注意:如果是 insert 操作,通过 terminate 会恢复到初始状态;如果是 replace 操作,出现 刚才的问题就麻烦了,表会变成空表。 8.6.3 要加载的数据是 Excel 格式怎么办 处理 Excel 数据,可将其另存为“CSV(逗号分隔)”格式,然后按照前述方法导入,如 图 8.3 所示。 图 8.3 另存为“CSV(逗号分隔)”格式 8.6.4 要导出/加载的数据不是逗号/双引号分隔怎么办 在默认情况下,对于 DEL 格式,每行数据字段之间用逗号分隔,字符串数据用双引号分隔。 如果提供的数据不是这种格式,如字段间用分号“;”分隔,字符串用单引号“''”分隔,那么可 用 modified by 修饰符来指定,coldelx 用来指定字段间分隔符,chardelx 用来指定字符串分隔符, 代码如下: 132 DB2 数据库管理最佳实践 db2inst1@dpf1:~> cat t1.del 10;'wang qi';1 20;'zhang san';2 db2inst1@dpf1:~> db2 "import from t1.del of del modified by coldel; chardel'' insert into t1" 注意:对于分隔符,有以下限制 :  用户要确保数据中不能包含分隔符,如果存在,会出现各种异常。  分隔符不能是换行符、回车符、0x00 或空格。  在 DBCS 环境下(如中文系统),不能用“|”符号作为分隔符。  如果 DEL 文件通过某个特殊字符分隔,而 import、load、export 命令中通过键盘无法 敲入时,可转换为十六进制表示,如 0x7c 代表“|”。 对于 decimal 值,默认的导出格式是在前面有+号,如果位数不够定义的长度,则用 0 补齐, 代码如下: db2inst1@dpf1:~> db2 "create table t2 (col1 char(10), col2 decimal (7,2) ) " db2inst1@dpf1:~> db2 "insert into t2 values('aaa', 23.4 ) " db2inst1@dpf1:~> db2 "insert into t2 values('bbb', 1.2 ) " db2inst1@dpf1:~> db2 "export to t2.del of del select * from t2" db2inst1@dpf1:~> cat t2.del "aaa ",+00023.40 "bbb ",+00001.20 有些情况下,客户不希望保留+号,或者去掉前面的 0,DB2 提供了两个修饰符选项,一个 是 decplusblank(del plus blank,即+换成空格),另一个是 striplzeros(strip zero,去掉空格)。 db2inst1@dpf1:~> db2 "export to t21.del of del modified by DECPLUSBLANK STRIPLZEROS select * from t2" db2inst1@dpf1:~> cat t21.del "aaa ", 23.40 "bbb ", 1.20 对日期格式的支持也比较灵活,如客户指定了导出的时间格式,那么可以通过 modified by timestampformat 来指定。 db2inst1@dpf1:~> db2 "create table t3 (col1 char(10), col2 timestamp) " db2inst1@dpf1:~> db2 "insert into t3 values('aaaa', current timestamp) " db2inst1@dpf1:~> db2 "export to t3.del of del select * from t3" db2inst1@dpf1:~> cat t3.del "aaaa ","2011-01-12-09.16.07.630919" 如果客户要求导出的时间按照“年/月/日 时:分:秒.毫秒”的格式,可在 export 命令中指定 (注意:YYYY 前面的“\”用于对“"”转义),结果如下: db2inst1@dpf1:~> db2 "export to t3.del of del modified by timestampformat=\"YYYY/MM/DD HH:MM:SS.UUUUUU\" select * from t3" db2inst1@dpf1:~> cat t3.del "aaaa ","2011/01/12 09:16:07.630919" 第 8 章 数据迁移 133 8.6.5 文件中的列比要导入的表中的字段多怎么办 针对这种情况,一般有以下两种处理方式:  修改数据文件,删除多余数据。可以利用 UNIX 的 sed、awk 等工具进行文件内容处理, 但如果文件数据量特别大的情况,处理的时间和工作量会比较大。  对于 DEL 格式,用 import、load 的 method P 选项;对于 ASC 格式,用 Method L 选项; 对于 IXF 格式,用 Method N 选项。我们以 DEL 格式为例,代码如下: db2inst1@dpf1:~> db2 "create table t4 (col1 char(10), col2 int ) " db2inst1@dpf1:~> cat t4.del "aaaa","bbbb",2,"abc",3 "cccc","dddf",4,"aaa",4 "dddd","dda",5,"abcd",5 db2inst1@dpf1:~> db2 "import from t4.del of del method P(1,3) insert into t4" db2inst1@dpf1:~> db2 "select * from t4" COL1 COL2 ---------- ----------- aaaa 2 cccc 4 dddd 5 8.6.6 文件中的列比要导入的表中的字段少怎么办 与前一种情况类似,仍然使用 Method P 选项,但不同的是,对于不存在的数据,可以使用 一个不存在的字段位置。下例中,Method P(1,99,2),输入文件中第 1 个和第 3 个字段内容对 应表的第 1 和第 3 个字段,而用空值补充第 2 个字段 (因为文件中不存在第 99 列,所以 DB2 用 NULL 值替代)。 db2inst1@dpf1:~> db2 "create table t3 (c1 int, c2 int, c3 int)" db2inst1@dpf1:~> db2 "select * from t3" C1 C2 C3 ----------- ----------- ----------- 0 record(s) selected. db2inst1@dpf1:~> more t3.del 1,2 db2inst1@dpf1:~> db2 "load from test.del of del method P(1,99,2) insert into t3" SQL3501W The table space(s) in which the table resides will not be placed in backup pending state since forward recovery is disabled for the database. … db2inst1@dpf1:~> db2 "select * from t3" 134 DB2 数据库管理最佳实践 C1 C2 C3 ----------- ----------- ----------- 1 - 2 1 record(s) selected. 8.6.7 要导入/导出大字段(LOB)怎么办 我们发现,很多客户使用大对象(LOB)来存储图片、大文本等内容。以下演示了两种大 对象数据导出的方法。 第一种方法是通过 modified by lobsinfile 将每个大对象字段的所有行数据存放在 lobs to 指定 的文件中,然后在导出文件(通过 export to 指定的文件)中通过指针来定位大对象数据在指定文件 中的位置偏移和长度。 在下例中,表 emp_resume 的 RESUME 字段为 CLOB 类型,将数据导出到 emp_resume.del 文件中,大对象数据导出到/data1/lobs 目录下。观察 emp_resume.del 文件内容可知,大对象数据 通过位置偏移和长度来定位在大对象文件的位置,代码如下: db2inst1@dpf1:/data1/lobs> db2 describe table emp_resume Column name Data type name Column Length --------------- ---------------- ---------------- EMPNO CHARACTER 6 RESUME_FORMAT VARCHAR 10 RESUME CLOB 5120 db2inst1@dpf1:/data1> db2 "export to emp_resume.del of del lobs to /data1/lobs modified by lobsinfile select * from emp_resume " db2inst1@dpf1:/data1> cat emp_resume.del "000130","ascii","emp_resume.del.001.lob.0.1257/" "000130","html","emp_resume.del.001.lob.1257.2415/" "000140","ascii","emp_resume.del.001.lob.3672.1261/" "000140","html","emp_resume.del.001.lob.4933.2440/" "000150","ascii","emp_resume.del.001.lob.7373.1308/" "000150","html","emp_resume.del.001.lob.8681.2467/" "000190","ascii","emp_resume.del.001.lob.11148.1237/" "000190","html","emp_resume.del.001.lob.12385.2402/" db2inst1@dpf1:/data1> cd lobs db2inst1@dpf1:/data1/lobs> ls emp_resume.del.001.lob 第二种方法是通过 modified by lobsinsepfiles 将每一行的大对象数据存放在独立文件中,在 导出文件(通过 export to 指定的文件)中指定文件名。在下例中,表 emp_resume 的 RESUME 字段为 CLOB 类型,将数据导出到 emp_resume1.del 文件中,大对象数据导出到/data1/lobs 目录 下,代码如下: 第 8 章 数据迁移 135 db2inst1@dpf1:/data1> db2 "export to emp_resume1.del of del lobs to /data1/lobs modified by lobsinsepfiles select * from emp_resume " db2inst1@dpf1:/data1> cat emp_resume1.del "000130","ascii","emp_resume1.del.001.lob" "000130","html","emp_resume1.del.002.lob" "000140","ascii","emp_resume1.del.003.lob" "000140","html","emp_resume1.del.004.lob" "000150","ascii","emp_resume1.del.005.lob" "000150","html","emp_resume1.del.006.lob" "000190","ascii","emp_resume1.del.007.lob" "000190","html","emp_resume1.del.008.lob" db2inst1@dpf1:/data1> cd lobs db2inst1@dpf1:/data1/lobs> ls emp_resume1.del.001.lob emp_resume1.del.003.lob emp_resume1.del.005.lob emp_resume1.del.007.lob emp_resume1.del.002.lob emp_resume1.del.004.lob emp_resume1.del.006.lob emp_resume1.del.008.lob 8.6.8 sequence 数据怎么办 如果目标表的值是通过 sequence 插入的,而初始数据来源于数据文件。那么加载完数据后, 需要对 sequence 的初始值进行重置,代码如下: SELECT MAX() AS counter FROM ALTER SEQUENCE RESTART WITH counter+1 db2inst1@dpf1:/data1> db2 “create sequence seq1 as integer start with 1 increment by 1 minvalue 1 maxvalue 999999999 cycle cache 10” db2inst1@dpf1:/data1> cat seq.del 1,”AAAA” 2,”bbbb” 3,”ccc” db2inst1@dpf1:/data1> db2 “create table test2 ( id int, name char(20) )” db2inst1@dpf1:/data1> db2 “load from seq.del of del insert into test2” db2inst1@dpf1:/data1> db2 “select * from test2” ID NAME ----------- -------------------- 1 AAAA 2 bbbb 3 ccc record(s) selected. Db2inst1@dpf1:/data1> db2 “select max(id) as max from test2” 136 DB2 数据库管理最佳实践 MAX 3 db2inst1@dpf1:/data1> db2 “alter sequence seq1 restart with 4” db2inst1@dpf1:/data1> db2 “insert into test2 values(nextval for seq1, ‘ddddddd’) “ db2inst1@dpf1:/data1> db2 “select * from test2” ID NAME ----------- -------------------- 4 ddddddd 1 AAAA 2 bbbb 3 ccc 4 record(s) selected. 8.6.9 导入 identity 数据怎么办 在数据加载过程中,经常遇到 identity 标识列数据处理出现问题。比如,某张表含有 identity 自增字段,而且表中包含一定数据。这时需要把某文件数据 import/load 到这张表中,那么自增 字段的值是继续增加呢?还是重置呢?还是忽略呢?针对不同的需求,DB2 提供了两组修饰符 来解决。一组为 identity 修饰符,包含 identityoverride,identityignore 和 identitymissing 3 个,用于 当表字段有 generated always as identity 或 generated by default as identity 自增列时数据加载处理 方法;另外一组为 generated 修饰符,包含 generatedoverride、generatedignore 和 generatedmissing 3 个,用于当表字段有 generated always as 时数据加载处理方法。 identityoverride 表示使用数据文件中提供的值,特别适用于系统间数据迁移时,需要保留 identity 列值的情况;identitymissing 表示当数据文件中不包含自增值时,import/load 数据产生自 增值;identityignore 命令表示当数据文件中包含自增值时,import/load 命令忽略该值,并生成自 增值。所以可以根据数据文件和用户需求,决定采用哪个修饰符。 注意:identityoverride 修饰符只适用于 generated always,不支持 generated by default。 identityoverride 不能在 import 命令中使用,只在 load 中支持。 我们举例说明这 3 个修饰符的使用。 例子 1: 在 load 数据时,指定 identityoverride 会使用输入文件中标识列的值;任何标识列 上值为空(或为 NULL 值)的行都会被拒绝。 db2inst1@dpf1:/data1> cat t6.del 3,"Shrek" db2inst1@dpf1:/data1> db2 "create table t6 (custno smallint not null generated always as identity (start with 500, increment by 1), custname varchar(16)) " db2inst1@dpf1:/data1> db2 "load from t6.del of del modified by identityoverride messages t6.msg insert into t6 " 第 8 章 数据迁移 137 db2inst1@dpf1:/data1> db2 "select * from t6" CUSTNO CUSTNAME --------- --------- 3 Shrek 例子 2:通过 import 或 load 加载数据时,输入文件不包含任何目标表中生成列的值,加载 过程会生成自增值。 db2inst1@dpf1:/data1> cat t7.del MingWei db2inst1@dpf1:/data1> db2 "create table t7 (custno smallint not null generated always as identity (start with 500, increment by 1), custname varchar(16)) " db2inst1@dpf1:/data1> db2 "load from t7.del of del modified by identitymissing messages t7.msg insert into t7" db2inst1@dpf1:/data1> db2 "select * from t7" CUSTNO CUSTNAME -------- ---------- 500 MingWei 例子 3: 当通过 import 或 load 数据时,输入文件中任何生成列的值都会被忽略,并且会为 每一行生成一个新的值。 db2inst1@dpf1:/data1> cat t8.del 1,"WangTao" db2inst1@dpf1:/data1> db2 "create table t8 (custno smallint not null generated always as identity (start with 500, increment by 1), custname varchar(16))" db2inst1@dpf1:/data1> db2 "load from t8.del of del modified by identityignore messages load.msg insert into t8" db2inst1@dpf1:/data1> db2 "select * from t8" CUSTNO CUSTNAME ------- ----------- 500 WangTao 例子 4:对于含有 generated by default as identity 自增列的表数据导入,可能会遇到加载完 成后,再插入数据时产生重复值的情况。举例来说,表中 ID 字段为 generated by default as identity(start with 500),当通过 import/load 加载了 1 万行数据后,通过 insert 插入数据时,自动 产生的自增值可能还是会从 500 开始,即自增分配值与原值重复(注意:这个问题并不是总会 出现,当出现时我们知道怎么做就可以了)。 解决办法:首先找到表中标识列的最大值,然后更改表定义,使得标识列的值从最大值+1 开始,代码如下: 138 DB2 数据库管理最佳实践 SELECT MAX() AS maxcounter from ALTER TABLE
ALTER COLUMN RESTART WITH < maxcounter + 1> 举例如下: db2inst1@dpf1:/data1> db2 "create table t9 (custno smallint not null generated by default as identity (start with 500, increment by 1), custname varchar(16))" db2inst1@dpf1:/data1> db2 "LOAD FROM t9.del OF DEL modified by identityignore INSERT INTO t9" db2inst1@dpf1:/data1> db2 "select * from t9" CUSTNO CUSTNAME ------ ---------------- 500 xusi 501 wangsan db2inst1@dpf1:/data1> db2 "insert into t9(custname) values('afadf') " db2inst1@dpf1:/data1> db2 "select * from t9 CUSTNO CUSTNAME ------ ---------------- 500 afadf 500 xusi 501 wangsan 发现新插入的行,custno 仍然从 500 开始,这不是我们希望的。通过 alter table … restart with 解决问题,代码如下: db2inst1@dpf1:/data1> db2 "SELECT max(custno) as MAXID FROM t9" MAXID ------ 501 db2inst1@dpf1:/data1> db2 "alter table t9 alter custno restart with 502" db2inst1@dpf1:/data1> db2 "insert into t9(custname) values('test4') " db2inst1@dpf1:/data1> db2 "select * from t9" CUSTNO CUSTNAME ------ ---------------- 500 afadf 500 xusi 501 wangsan 502 test4 例子 5:对于 generated always 的标识列,就要用 generated 修饰符了。使用方 法同前述 identity 修饰符,我们举例说明,代码如下: db2inst1@dpf1:/data1> cat t10.del "Crystal",102000.00 db2inst1@dpf1:/data1> db2 "create table t10 (name varchar(16) not null, salary 第 8 章 数据迁移 139 decimal(9,2), bonus decimal(9,2) generated always as (salary/10) )" db2inst1@dpf1:/data1> db2 "load from t10.del of del modified by generatedmissing messages t10.msg insert into t10" db2inst1@dpf1:/data1> db2 "select * from t10" NAME SALARY BONUS ---------- ----------- ----------- Crystal 102000.00 10200.00 8.6.10 要加载的数据有换行符怎么办 如何处理数据中含有换行符的问题,本来是一条数据,取的时候就变成两条了,造成再往 库里 load 的时候就会报错。 举例如下:通过 quest central 插入 2 行数据(在命令行下很难模拟换行插入),如图 8.4 所示。 图 8.4 查询结果发现,共有 2 行数据: db2inst1@dpf1:/data1> db2 "select * from test1" COL1 ---------- a b c d 2 record(s) selected. 将数据导出到文本文件,结果如下: db2inst1@dpf1:/data1> db2 "export to test.del of del select * from test1" Number of rows exported: 2 db2inst1@dpf1:/data1> cat test.del 140 DB2 数据库管理最佳实践 "a b " "c d " 如果采用默认的 load 选项加载到表中,那么会出现 4 条记录,这明显是有问题的,代码如下: db2inst1@dpf1:/data1> db2 "create table test2 like test1" db2inst1@dpf1:/data1> db2 "load from test.del of del insert into test2" db2inst1@dpf1:/data1> db2 "select * from test2" COL1 ---------- "a b " "c d " 4 record(s) selected. 为解决此问题,我们提供如下几种解决方案: (1)如果允许的话,导出的时候替换换行符,代码如下: db2inst1@dpf1:/data1> db2 "export to test1.del of del select replace(col1,chr(10),'') from test1" SQL3104N The Export utility is beginning to export data to file "test1.del". SQL3105N The Export utility has finished exporting "2" rows. Number of rows exported: 2 db2inst1@dpf1:/data1> cat test1.del "ab " "cd " (2)如果允许的话,通过脚本对输入数据进行清洗,去掉换行符,代码如下: db2inst1@dpf1:/data1> cat test.del "a b " "c d " db2inst1@dpf1:/data1> sed 'N;s/\n//' test.del "ab " "cd " (3)在 load/import 时指定 modified by delprioritychar 修饰符,代码如下: db2inst1@dpf1:/data1> db2 "load from test.del of del modified by DELPRIORITYCHAR insert into test2" Number of rows read = 2 Number of rows skipped = 0 Number of rows loaded = 2 第 8 章 数据迁移 141 Number of rows rejected = 0 Number of rows deleted = 0 Number of rows committed = 2 db2inst1@dpf1:/data1> db2 "select * from test2" COL1 ---------- a b c d 2 record(s) selected. DB2 默认 import/load 优先级策略为:行分隔符(record delimiter)、 字符分隔符(character delimiter)和列分隔符(column delimiter),行分隔符优先级最高。所以如果原始文件有换行的 话,load 就认为是新的记录,这时可用 delprioritychar 修饰符改变默认的优先级别,确保""之间 的数据不管有没有换行符都被认为是同一条记录。 注意:用 IXF 不会出现此问题。 8.6.11 迁移出 现乱码怎么办 对于 IXF 格式,一般不会出现编码问题。因为会发生自动编码转换。对于 DEL 格式,当在 不同系统间 export/import/load 数据时,由于数据的编码不同,可能会遇到汉字乱码问题,而 import 和 load 对数据文件的编码处理也有很大的差异。数据库编码在建库时指定,可通过 db2 get db cfg 查看。 在默认情况下,load 假定输入文件用数据库 codepage 编码,并将文件转换为数据库 codepage 编码来导入。如果输入文件的 codepage 与数据库编码不一样,那么就会将错误的数据装载到数 据库中。因此对于数据文件和数据库的 codepage 不同的情况,可以使用 modified by codepage=选项,显式地将数据文件 codepage 告诉 load 程序,确保 DB2 能够正确地 执行编码页转换。 在默认情况下,import 认为输入文件中的数据是用当前系统的代码页编码(通过 db2set db2codepage=设置,如 1386、1208 等)。当将数据文件导入到数据库时,DB2 会自 动将数据文件从当前系统代码页转换成数据库代码页。如果输入文件不是当前系统的代码页编 码,也可以通过 codepage 修饰符来轻松导入正确文件。 下面的例子很清晰地说明了这个过程。 (1)首先通过 db2 get db cfg 查看当前数据库编码集是 UTF-8, codepage 是 1208,在当前编 码下插入中文数据,代码如下: db2inst1@dpf1:~> db2 "insert into t1 values('10000','徐明伟' )" db2inst1@dpf1:~> db2 "select * from t1" 142 DB2 数据库管理最佳实践 EMPNO EMPNAME ---------- -------------------- 10000 徐明伟 (2)然后将数据以不同编码导出,在 export 时,如果不指定导出数据编码,则按数据库编 码导出。如果通过 modified by codepage 指定了编码,则按指定的编码导出,这样我们就可以构 造几种不同编码的数据,用来测试 load 和 import 加载过程,代码如下: db2inst1@dpf1:~> db2 "export to t1 of del select * from t1" db2inst1@dpf1:~> db2 "export to t1_819 of del modified by codepage=819 select * from t1" db2inst1@dpf1:~> db2 "export to t1_1208 of del modified by codepage=1208 select * from t1" db2inst1@dpf1:~> db2 "export to t1_1386 of del modified by codepage=1386 select * from t1" (3)清空当前表数据,然后按顺序以不同编码将数据 load,代码如下: db2inst1@dpf1:~> db2 delete from t1 db2 "load from t1_819 of del modified by codepage=819 insert into t1" db2 "load from t1_1208 of del modified by codepage=1208 insert into t1" db2 "load from t1_1386 of del modified by codepage=1386 insert into t1" db2 "load from t1_1386 of del insert into t1" db2inst1@dpf1:~> db2 "select * from t1" EMPNO EMPNAME ---------- ----------- 10000 -- 按 819 编码的数据无法导入,这是因为导出的文件已经发生了乱码(819不支持汉字) 10000 徐明伟 -- 按 1208 编码数据导入,正确 10000 徐明伟 -- 指定 1386 编码,正确 10000 ▒▒▒▒ΰ -- 1386 编码数据,但没有指定 codepage,出现乱码 4 record(s) selected. (4)import 对编码的处理过程如下: db2inst1@dpf1:~> db2set db2codepage=1386 --将系统编码改为 1386 db2inst1@dpf1:~> db2 terminate db2inst1@dpf1:~> db2 connect to sample db2inst1@dpf1:~> db2 "import from t1_1386 of del insert into t1" -- 输入文件和系统 编码一致,可导入 db2inst1@dpf1:~> db2 "import from t1_1208 of del insert into t1" -- 输入文件和系统 编码不一致,出现乱码 db2inst1@dpf1:~> db2set db2codepage=1208 --将系统编码改为 1208 db2inst1@dpf1:~> db2 terminate 第 8 章 数据迁移 143 db2inst1@dpf1:~> db2 connect to sample db2inst1@dpf1:~> db2 "import from t1_1386 of del insert into t1" -- 输入文件和系统 编码不一致,出现乱码 db2inst1@dpf1:~> db2 "import from t1_1386 of del modified by codepage=1386 insert into t1" -- 输入文件和系统编码不一致,通过 codepage 修饰符通知 import 输入文件的编码是 1386 我们举这个例子的目的就是要告诉大家,当遇到编码的时候,可通过指定 modified by codepage 来告诉 import 或 load 执行正确的编码转换。 8.6.12 表数据从一个表空间迁移到另外一个表空间怎么办 表数据在不同表空间的迁移是很多客户遇到的问题,原因有以下几个:  表空间页大小初始设置是 4K 页,随着业务需求的变化,某张表(假设为 t1)的一行记 录大小需要超过 4K,当前表空间页大小无法满足需求。  在 V8 版本,4K 页大小的表空间最大限制为 64GB。当数据量达到此限制时是无法插 入数据的。  性能考虑,需要将某些表从一个表空间迁移到另外一个表空间。 对于此类问题,典型做法是新建一个表空间,在此表空间创建与原表相同的表结构(假设 为 t2),然后将原表 t1 数据导出,再导入到 t2 表,完成测试后,将 t1 表改名或删除,将 t2 表改 为 t1。如果要迁移的表很多,可考虑写成脚本。注意:如果在 T1 表上有主外键约束等,则需要 先删除,否则无法 rename。 举例如下: db2inst1@dpf1:/data1/data> db2 "create tablespace ts2" db2inst1@dpf1:/data1/data> db2 "create table t2 like t1 in ts2" db2inst1@dpf1:/data1/data> db2 "export to t1.del of del select * from t1" db2inst1@dpf1:/data1/data> db2 "load from t1.del of del insert into t2" db2inst1@dpf1:/data1/data> db2 rename t1 to t1_bak db2inst1@dpf1:/data1/data> db2 rename t2 to t1 可以看到,上述解决方案操作比较烦琐,而且要求在操作时 t1 是离线的,即不允许应用进 行增、删、改操作,因此只能在特定的运维时间窗口内操作,如果表数据量很大,在指定的时 间无法完成的话,则对应用的影响较大。 为增加系统的可用性,9.7 版本引入了在线数据迁移方案,通过 ADMIN_MOVE_TABLE 存 储过程将表数据从一个表空间迁移到另外一个表空间,迁移过程中保持对原表数据的持续访问。 ADMIN_MOVE_TABLE 过程有两种等价的调用方式。第一种是提供目标表的数据、索引、 大对象表空间名;第二种事先建一个表,建表时指定数据、索引、大对象表空间,在调用时提 供表名而不需要提供表空间名,迁移完成后,表名替换为原表名。 144 DB2 数据库管理最佳实践 对于在线表迁移操作,需要以下了个表。  原表(source table):要迁移的表名。  目标表(target table):迁移的目标,如果是第一种调用,则这张表会在指定的表空间自 动创建,如果是第二种调用,目标表已经建好,作为参数传给存储过程即可。在迁移过 程中,仍然可以在原表上做数据的 insert、update 和 delete 操作,通过 Trigger 机制来捕 获变化数据,并同步到目标表上,为此需要创建第三张表。  中间表(staging table):记录迁移过程中在原表上产生的更新数据,这些变化数据随后 被同步到目标表上。迁移完成后,这张表会删除。 图 8.5 在线表迁移操作包括 5 个阶段:INIT、COPY、REPLAY、SWAP 和 CLEANUP,如图 8.5 所示。 (1)INIT 阶段:初始化阶段将验证调用者是否有权限进行表迁移;根据调用方法验证或创 建目标表;创建中间表;在原表上创建 4 个触发器,这样对原表的 insert、update、delete 操作都 将触发触发器,并将变化记录到中间表中,对 update 操作,更新前和更新后的值都将记录。 (2)COPY 阶段:复制阶段就是从原表复制数据到目标表,当复制时原表仍然可访问。复 制数据的方式有两种,一种是 insert select * from ,另一种是采用 load…from cursor 的方式。复制的最后,会在目标表上创建和原表一致的索引。复制过程中对原表的变更都 将记录到中间表中。 (3)REPLAY 阶段:REPLAY 阶段会遍历中间表,将数据写到目标表上。根据原数据的变 更情况,REPLAY 可能需要做很多次。 (4)SWAP 阶段:此阶段将交换原表和目标表表名,然后删除原表。在交换表名之前,需 要确保两边数据完全一致。因此,在最后一次 REPLAY 时,要在原表和目标表上加 x 锁,确保 没有新数据产生,这样保证了两表数据的完全一致。然后删除原表,rename 目标表到原表,并 释放锁。 (5)CLEANUP 阶段:最后的清理阶段将清除一些临时对象;删除第一步创建的触发器, 并删除中间表。 以下是一个将 t4 表从 ts1 表空间迁移到 TBS32K 表空间的例子,根据存储过程结果可以明 显地看到各个阶段的起始和完成时间,代码如下: db2inst1@dpf1:~> db2 create tablespace ts1 db2inst1@dpf1:~> db2 "create table t4 (col1 char(100), col2 int) in ts1" 第 8 章 数据迁移 145 db2inst1@dpf1:~> db2 create bufferpool bp32k size automatic pagesize 32k db2inst1@dpf1:~> db2 create tablespace tbs32k pagesize 32k bufferpool bp32k db2inst1@dpf1:~> db2 "call sysproc.admin_move_table('DB2INST1', 'T4', 'TBS32K', 'TBS32K', 'TBS32K', '','','','','','MOVE' ) " Result set 1 -------------- KEY VALUE -------------------------------- -------------------------- AUTHID DB2INST1 CLEANUP_END 2010-10-30-08.12.50.656272 CLEANUP_START 2010-10-30-08.12.50.626811 COPY_END 2010-10-30-08.12.49.560915 COPY_OPTS ARRAY_INSERT,NON_CLUSTER COPY_START 2010-10-30-08.12.49.281178 COPY_TOTAL_ROWS 10000 INDEXNAME INDEXSCHEMA INIT_END 2010-10-30-08.12.49.122941 INIT_START 2010-10-30-08.12.48.219311 REPLAY_END 2010-10-30-08.12.50.504815 REPLAY_START 2010-10-30-08.12.49.561244 REPLAY_TOTAL_ROWS 0 REPLAY_TOTAL_TIME 0 STATUS COMPLETE SWAP_END 2010-10-30-08.12.50.596472 SWAP_RETRIES 0 SWAP_START 2010-10-30-08.12.50.543877 VERSION 09.07.0001 20 record(s) selected. Return Status = 0 使用建议:  避免在同一个表空间上同时进行多个表迁移,否则在表空间上可能会产生比较多的碎 片。  在空闲时进行迁移,因为迁移过程产生的大量 I/O,可能会影响对该表的并发访问。  使用多步迁移步骤。前面我们讲的 5 个步骤可以分步执行,比如 INIT 和 COPY 阶段可 以随时调用,尽量多次执行 REPLAY 保持中间表的数据最少,在表几乎不活动时进行 SWAP。  评估是否可以采用离线迁移方法。  在线表数据迁移依赖于触发器来捕获迁移过程中变化的数据,所以不触发 Trigger 的一 些操作会导致两边数据的不一致,如 load、alter,reorg 等。建议在进行在线迁移时避免 进行以上操作。  考虑增加表空间大小,因为迁移过程会产生两份数据和索引副本,中间表还要占用空间。  需要创建 SYSTOOLSPACE 表空间,并且对 PUBLIC 有访问权限。 146 DB2 数据库管理最佳实践 表迁移限制,有些情况下不支持在线表迁移过程,可以根据 reason code 查找错误原因,以 下是一些常见的限制:  原表上不能有主外键约束,主表或依赖表都不可以,可以通过 db2look 或系统表查找约 束,并在迁移前删除,待迁移后再重建约束。  如果表上有事件监控器则不能迁移。  如果表上有 LOB、XML、LONG 类型字段,则要求表上必须有唯一性索引。  处于 set integrity pending 状态的表不能迁移。 关于此存储过程的详细解释,请参看信息中心。 8.7 db2look/db2move 8.7.1 db2move 工具介绍 db2move 程序用来在两个数据库间数据迁移,特别适合于不同平台、表数量比较多的情况。 此程序是对 export、import、load 命令的封装,根据系统表获得用户表,将数据导出为 PC/IXF 格式,同时会产生一个 db2move.lst 文件,记录导出表和数据文件名字。然后将这些文件传送到 目标系统中,通过 load 或 import 进行导入。 db2move 命令的语法如下: .----------------------------. V | >>-db2move--dbname--action----+------------------------+-+----->< +- -tc--table-definers---+ +- -tn--table-names------+ +- -sn--schema-names-----+ +- -ts--tablespace-names-+ +- -tf--filename---------+ +- -io--import-option----+ +- -lo--load-option------+ +- -co--copy-option------+ +- -l--lobpaths----------+ +- -u--userid------------+ +- -p--password----------+ '- -aw-------------------' Action 可以是 export、import 和 load 中的一种,db2move 执行时会自动进行数据库连接。 第 8 章 数据迁移 147 8.7.2 db2look 工具介绍 在数据迁移前,需要在目标端建立数据对象定义,这正是 db2look 最擅长的工作。db2look 能产生表、视图、索引、函数、Trigger、存储过程等对象定义语句,可通过 db2look –h 查看 帮助。 以下是 db2look 常用的选项,-d 表示数据库名,-e 抽取数据库对象定义,-l 抽取表空间定 义, -t 表示抽取的表名(注意:数据库默认创建的 bufferpool、表空间和节点组不会抽取),代码 如下: db2look -d db_name -e -l -o db2look.ddl -- 抽取所有用户对象定义,包括表空间等 db2look -d -t t1 t2 -e -o db2look_tables.out -- 抽取某个或某些表定义信息, t1、t2 为表名 8.7.3 db2look+db2move 迁移案例 下面举一个实例为大家演示通过 db2look+db2move 工具从 Linux 到 Win 的迁移过程。我们 要迁移的库选用 DB2 自带的 sample 数据库,别看这个库小,麻雀虽小,五脏俱全,sample 库中 包含了表、表约束、索引、视图、函数、存储过程等定义,大家很容易在自己的环境上模拟。 由于在实际环境中,有些客户表中采用了 identity 标识列,而 db2move 是无法迁移这种类型的表 数据的。因此,为了保持完整性,在数据库中定义了一张带有 identity 标识列的表。 (1)在 Linux 上创建 sample 数据库,并创建一个带有 identity 标识列的 cust1 表,在 cust1 表中插入两行数据,代码如下: db2inst1@dpf1:~> db2sampl ... 'db2sampl' processing complete. db2inst1@dpf1:~> db2 connect to sample Database Connection Information Database server = DB2/LINUX 9.7.1 SQL authorization ID = DB2INST1 Local database alias = SAMPLE db2inst1@dpf1:/data1/data> db2 "create tablespace ts1 pagesize 8k managed by database using (file '/data1/ts1/cont0' 10M ) bufferpool ibmdefaultbp " db2inst1@dpf1:~> db2 "create table cust1 (custno smallint not null generated always as identity (start with 500, increment by 1), custname varchar(16)) in ts1" db2inst1@dpf1:~> db2 "insert into cust1(custname) values('suntao') " db2inst1@dpf1:~> db2 "insert into cust1(custname) values('shiqing') " db2inst1@dpf1:~> db2 "select * from cust1" CUSTNO CUSTNAME ------ ---------------- 500 suntao 501 shiqing 148 DB2 数据库管理最佳实践 2 record(s) selected. (2)将 Linux 上 sample 的对象定义通过 db2look 导出,结果保存在 db2look.ddl 中,打开 文 件就可以看到对象的定义了,内容如下: db2inst1@dpf1:~> db2look -d sample -e -l -o db2look.ddl -- No userid was specified, db2look tries to use Environment variable USER -- USER is: DB2INST1 -- Creating DDL for table(s) -- Output is sent to file: db2look.ddl -- Binding package automatically ... -- Bind is successful -- Binding package automatically ... -- Bind is successful (3)将 Linux 上 sample 数据库表数据通过 db2move 导出,为数据传输方便,建议新建一 个目录,存放导出数据(如果数据量大的话,考虑在共享存储上建目录),代码如下: db2inst1@dpf1:~> mkdir data db2inst1@dpf1:/data1/data> db2move sample export Application code page not determined, using ANSI codepage 1208 ***** DB2MOVE ***** Action: EXPORT Start time: Thu Jan 13 19:05:57 2011 Connecting to database SAMPLE ... successful! Server : DB2 Common Server V9.7.1 Binding package automatically ... /home/db2inst1/sqllib/bnd/db2common.bnd ... successful! Binding package automatically ... /home/db2inst1/sqllib/bnd/db2move.bnd ... successful! EXPORT: 18 rows from table "DB2INST1"."ACT" EXPORT: 0 rows from table "DB2INST1"."CATALOG" EXPORT: 5 rows from table "DB2INST1"."CL_SCHED" EXPORT: 2 rows from table "DB2INST1"."CUST1" EXPORT: 6 rows from table "DB2INST1"."CUSTOMER" EXPORT: 14 rows from table "DB2INST1"."DEPARTMENT" EXPORT: 42 rows from table "DB2INST1"."EMPLOYEE" EXPORT: 10000 rows from table "DB2INST1"."EMPMDC" EXPORT: 73 rows from table "DB2INST1"."EMPPROJACT" EXPORT: 8 rows from table "DB2INST1"."EMP_PHOTO" EXPORT: 8 rows from table "DB2INST1"."EMP_RESUME" EXPORT: 4 rows from table "DB2INST1"."INVENTORY" EXPORT: 3 rows from table "DB2INST1"."IN_TRAY" EXPORT: 8 rows from table "DB2INST1"."ORG" EXPORT: 5 rows from table "SYSTOOLS"."POLICY" EXPORT: 4 rows from table "DB2INST1"."PRODUCT" EXPORT: 2 rows from table "DB2INST1"."PRODUCTSUPPLIER" EXPORT: 65 rows from table "DB2INST1"."PROJACT" EXPORT: 20 rows from table "DB2INST1"."PROJECT" 第 8 章 数据迁移 149 EXPORT: 6 rows from table "DB2INST1"."PURCHASEORDER" EXPORT: 41 rows from table "DB2INST1"."SALES" EXPORT: 35 rows from table "DB2INST1"."STAFF" EXPORT: 35 rows from table "DB2INST1"."STAFFG" EXPORT: 2 rows from table "DB2INST1"."SUPPLIERS" Disconnecting from database ... successful! End time: Thu Jan 13 19:06:01 2011 进入/data1/data 目录,发现数据已经导出。其中 db2move.lst 存放导出的表和对应的导出数 据及消息文件列表,EXPORT.out 存放导出过程,tabx.ixf 和 tabx.msg 分别存放表数据和消息文 件,代码如下: db2inst1@dpf1:/data1/data> ls db2move.lst EXPORT.out tab2.ixf tab2.msg … (4)将数据通过 FTP 或其他方式传到 Windows 机器中,注意传输编码。对于数据文件通 过 binary 格式传输;对于 db2look.ddl 和 db2move.lst 用 ASC11 传输。否则可能会出现乱码问题。 (5)在 Windows 系统创建数据库,然后重建结构。打开 db2look.ddl,修改表空间路径, 将/data1/ts1/cont0 改为 Windows 路径。注意:如果目标库名与源库不一致,需要在 db2look.ddl 中进行相应更改,对象的 schema 也要注意,代码如下: D:\temp\data>db2 "create db sample pagesize 8 k" DB20000I CREATE DATABASE 命令成功完成。 CREATE LARGE TABLESPACE "TS1" IN DATABASE PARTITION GROUP IBMDEFAULTGROUP PAGESIZE 8192 MANAGED BY DATABASE USING (FILE '/data1/ts1/cont0' 1280) --比如修改为(File 'd:\data1\ts1\cont0' 1280) EXTENTSIZE 32 PREFETCHSIZE AUTOMATIC BUFFERPOOL IBMDEFAULTBP OVERHEAD 7.500000 TRANSFERRATE 0.060000 NO FILE SYSTEM CACHING DROPPED TABLE RECOVERY ON; D:\temp\data>db2 -tvf db2look.ddl 内容较多,省略… (6)然后将数据通过 db2move 导入。导入结束后,注意检查 LOAD.out 文件,其中记录了 警告和错误信息,如加载了多少行、拒绝了多少行等。对于拒绝的表,要检查对应表的数据和 MSG 消息输出,代码如下(限于篇幅原因,删除了一些信息): D:\temp\data>db2move sample load Application code page not determined, using ANSI codepage 1386 ***** DB2MOVE ***** Action: LOAD Start time: Fri Jan 14 11:47:27 2011 150 DB2 数据库管理最佳实践 Connecting to database SAMPLE ... successful! Server : DB2 Common Server V9.7.1 Binding package automatically ... G:\IBM\SQLLIB\BND\DB2COMMON.BND ... successful! Binding package automatically ... G:\IBM\SQLLIB\BND\DB2MOVE.BND ... successful! * LOAD: table "DB2INST1"."ACT" -Rows read: 18 -Loaded: 18 -Rejected: 0 -Deleted: 0 -Committed: 18 * LOAD: table "DB2INST1"."CUST1" *** WARNING 3107. Check message file tab4.msg! *** SQL Warning! SQLCODE is 3107 *** SQL3107W 消息文件中至少有一条警告消息。 -Rows read: 2 -Loaded: 0 -Rejected: 2 -Deleted: 0 -Committed: 2 * LOAD: table "DB2INST1"."DEPARTMENT" -Rows read: 14 -Loaded: 14 -Rejected: 0 -Deleted: 0 -Committed: 14 * LOAD: table "DB2INST1"."EMPLOYEE" -Rows read: 42 -Loaded: 42 -Rejected: 0 -Deleted: 0 -Committed: 42 … … Disconnecting from database ... successful! End time: Fri Jan 14 11:47:51 2011 (7)Identitiy 标识列处理。还记得我们在第 1 步中创建的 cust1 表吗?通过以上输出发现, 这个表的数据并没有插入进去了,因为 db2move 不支持 identity标识列的导入。现在只能通过 load 命令单独导入了,打开 db2move.lst,找到 cust1 表对应的数据文件,代码如下: !"DB2INST1"."CUST1"!tab4.ixf!tab4.msg! D:\temp\data>db2 "load from tab4.ixf of ixf modified by IDENTITYOVERRIDE insert into db2inst1.cust1" … 读取行数 = 2 跳过行数 = 0 第 8 章 数据迁移 151 装入行数 = 2 拒绝行数 = 0 删除行数 = 0 落实行数 = 2 D:\temp\data>db2 "select * from db2inst1.cust1" CUSTNO CUSTNAME ------ ---------------- 500 suntao 501 shiqing 2 条记录已选择。 是不是和我们在 Linux 上的数据一样呢? (8)到这里,发现还是没有,别急,最后一步。还记得我们讲过,load 并不会进行检查参 考完整性约束和检查约束检查,需要通过 set integrity 命令进行数据完整性检查。执行以下语句 找出处于 set integrity pending 状态的表: D:\temp\data>db2 "SELECT substr(TABNAME,1,30) as TAB_NAME, STATUS, ACCESS_MODE, SUBSTR(CONST_CHECKED,1,1) AS FK_CHECKED, SUBSTR(CONST_CHECKED,2,1) AS CC_CHECKED FROM SYSCAT.TABLES WHERE STATUS= 'C' TAB_NAME STATUS ACCESS_MODE FK_CHECKED CC_CHECKED ------------------------------ ------ ----------- ---------- ---------- DEPARTMENT C N N Y EMPLOYEE C N N N EMP_PHOTO C N N Y EMP_RESUME C N N Y PROJECT C N N Y PROJACT C N N Y EMPPROJACT C N N Y ACT C N N Y ADEFUSR C N Y Y PURCHASEORDER C N N Y 执行./set.sh 脚本,解除 pending 状态(set.sh 在 8.5.4 节有详细介绍)。 8.8 db2dart db2dart 的功能非常强大,本节主要介绍数据导出功能。由于事务日志被破坏,或磁盘故障 导致数据库无法连接,而又没有数据库备份情况下,db2dart 可能是您的最后一根救命稻草了。 下面是通过 db2dart /ddel 选项导出表数据的例子,该选项需要用户提供表 ID 或表名、表 空间 ID、起始页和页数。表 ID 和表空间 ID 可以通过 syscat.tables 系统表查询,起始页可以 从 0 开始,页数可以设为 999999999。默认的输出目录是/sqllib/db2dump/DART0000, 152 DB2 数据库管理最佳实践 可以通过/rpt 来指定。默认的导出数据名是 TST.DEL,如本例是 TS8T4.DEL。代码如下: db2inst1@dpf1:/data1/data> db2dart sample /ddel Table object data formatting start. Please enter Table ID or name, tablespace ID, first page, num of pages: 4,8,0,999999999 1 of 1 columns in the table will be dumped. Column numbers and datatypes of the columns dumped: 0 CHAR() -FIXED LENGTH CHARACTER STRING Default filename for output data file is TS8T4.DEL, do you wish to change filename used? y/n Filename used for output data file is TS8T4.DEL. If existing file, data will be appended to it. Formatted data being dumped ... Dumping Page 0 … Table object data formatting end. DB2DART Processing completed with warning(s)! Complete DB2DART report found in: /home/db2inst1/sqllib/db2dump/DART0000/SAMPLE.RPT 注意:强烈建议执行 db2dart 的时候 deactivate 数据库,否则可能会出现不一致。 这时,有的读者可能会问,既然数据库都无法连接了,还怎么查询 tableid 和 tablespaceid 呢? 问得好,要解决这个问题还得靠 db2dart,不过这次使用/db 选项。 db2dart sample /db /rpt /home/db2inst1/dart /rptn chekdb_dart.out db2dart /db 选项会检查数据库的完整性,对库中的每张表进行检查,输出报告中会打印表 名、表 ID 和表空间 ID。以下是一个结果片段,结果显示了表名 DBA.SNAP_LOCKWAIT,以及 表 ID=29 和表空间 ID=3: Table inspection start: DBA.SNAP_LOCKWAIT Data inspection phase start. Data obj: 29 In pool: 3 Data inspection phase end. … 对于一个存在很多表的数据库,需要 db2dart 进行表数据导出时,建议写 shell 或批处理 脚本。将数据导出后,接下来就要对数据库重建了,重建仍然需要通过 db2look 目录导出表 结构,因此,我们强烈建议在平时运维中经常保存一份建库脚本,以便出现问题时能够进行 系统恢复。 db2look -d sample -e -l –o db2look.ddl 建完数据对象后,就可以将 db2dart 导出的数据文件通过 load/import 命令进行数据加载。 第 8 章 数据迁移 153 注意: db2dart 不会抽取大对象数据,如果表中含有 CLOB/BLOB 字段,并且是最后一个 字段,且字段类型允许为空,那么 db2dart 导出的数据是可以插入的,但 CLOB/BLOB 值为 NULL 值。如果不是最后一个字段,load 加载时就会出错,这 时需要对 db2dart 导出的 DEL 格式数据进行处理,用 NULL 或空格等补齐大对象 所在的字段。 除非在没有别的办法的情况下,否则不建议用 db2dart,因为:  如果缓冲池有数据没有写到磁盘,用 db2dart 导出的数据可能是不完整、不准确的。  db2dart 抽取速度会比较慢,特别是表很多、数据量很大的情况下,需要的时间很长。  db2dart 会忽略大对象数据的抽取。  必须有表、索引、存储过程、函数等的定义,否则无法重建结构。 注意:只有当系统表(catalog tables)有效时,才能用 db2dart;当系统表数据被破坏的情 况下,除了使用 restore 没有别的方法。这也从另外一个侧面说明了做好备份的重 要性。 8.9 小结 数据迁移也许是数据库运维中最常见的工作。将一些数据从数据库 A 输入数据库 B,如何 才能简单有效地完成这样的工作,是很多 DBA 的一大挑战。DB2 提供了多种工具可以选择,例 如 import、load,甚至一些第三方的 ETL 工具(底层不外乎 insert、import 和 load)。 当用户手工运行 import 和 load 命令时,一定要注意这两个命令包含多种不同的参数适用于 不同类型的数据。用户一定要对自己的数据迁移脚本做好完整测试以后再投入使用,否则如果 生产系统的数据与测试数据出入过大时,可能测试良好的脚本在生产系统中会出现问题(例如, 生产系统的迁移数据中,字符串包含空行,而测试系统中的数据没有空行)。 8.10 判断题 (1)DB2 可以使用 IMPORT 与 LOAD 命令加载数据。 T:正确 F:错误 (2)DB2 可以使用 EXPORT 命令导出数据。 T:正确 154 DB2 数据库管理最佳实践 F:错误 (3)使用 db2move 移动数据性能远高于 EXPORT/LOAD。 T:正确 F:错误 (4)用户可以使用 db2look 与 db2move 工具进行数据的跨平台迁移。 T:正确 F:错误 (5)db2dart 命令可以用来导出 LOB 数据。 T:正确 F:错误 第 2 章 Oracle 开发常用工具及使用 在上一章中,我们详细地介绍了性能数据收集与分析的思路,并且给出了 5 个不同的案例, 来讲述如何在不同的情况下分析性能问题。 不过上一章我们主要强调的是历史性能数据收集的重要性,但是在用户的日常工作中,很 有可能一个系统自从建立以来,其性能一直不尽如人意,那么我们如何帮助这种系统运行得更 快呢?这就引出了我们这一章的论题:性能调优。在介绍性能调优之前,我们先要看一下 DB2 优化器的架构,理解 DB2 如何利用优化器来优化一个用户查询,使它能够以最高的效率执行。 本章内容安排如下:  优化器简介。  性能调优的思路和方法。  关键性能指标介绍(KPI)。 16.1 优化器简介 在介绍系统调优之前,我们先简要介绍一下 DB2 优化器的架构,只有这样,才能够更好的 理解 DB2 内部执行过程,以及 DB2 如何优化一个用户查询,并使它能够以最高的效率执行。由 于篇幅限制,我们尽量用简短的语言阐述优化器与系统调优细节原理,更深入和高级的内部优 化器原理,将在《DB2 数据库高级管理》一书中详细讨论。 第 章 16 优化器与性能调优 第 16 章 优化器与性能调优 439 优化器是关系数据库的核心,相当于汽车的发动机,DB2 在业界最值得称道的功能之一就 是其强大的优化器。在大多数情况下,用户不必手工干预优化器的执行,而且 DB2 优化器总是 足够聪明地选择最佳访问计划,当然前提是数据库的统计信息被及时收集。 我们俯瞰一下优化器的构成。 首先,当一条查询被 DB2 接收以后,该查询会被查询重写器重新翻译,重写成一种更高 效的能够被优化器直接理解的格式。一般来说,被重写的查询也是基于 SQL 的,但是重写器 会利用一些固定的规则将某些指令翻译成不同的表达方式,或者重新安排子查询之间的顺序, 然后将 DB2 的视图、触发器与约束等数据库对象带入,以便优化器能够更有效地理解一段复 杂的查询。 紧接着,被重写后的查询会被送入 DB2 优化器模块。在优化器中,DB2 会以动态规划算法 (如果在优化级别 optlevel≤2 时,将会是用贪心算法),根据每个数据库对象所对应的统计数据, 估算出按照不同访问数据库对象的顺序所得到的开销。 譬如说,我们有 3 个表 A、B、C。表 A 中有 10000 行相同的数据,而 B 与 C 中只有 1 行 同样相同的数据(比如都是 0)。 当需 要 3 个表进行关联的时候,我们可以针对表 A 中的所有行 与 B 关联,然后结果集与 C 关联。这样的关联需要扫描 A 中的所有行,然后对于每一行与 B 关 联后,结果集中依然还有 10000 行(因 为数据都相同),然后对这 10000 行再与 C 管理,还是得 到 10000 行。 这样的计算也许效率不高,我们有没有更有效的访问数据的手段呢? 我们可以先用 B 与 C 关联,这样我们只需要扫描 1 行,然后将这 1 行与 10000 行的 A 进行 关联。这种访问途径可以减少对中间结果集扫描 10000 次的开销。 从上面的例子我们可以看出,以不同的方式访问相同的数据,其开销可能截然不同。因此, DB2 优化器的作用就是根据 DB2 对每一个数据对象的了解,制定出一个最佳的访问途径。 当优化器计算出若干种访问数据的途径后,会选择预期最有效的一个途径,然后将这个计 算出的规则发送给代码生成模块(CodeGen)生成可以运行的路径,然后该路径被发送至运行模 块(Runtime)真正执行。 这是一个从一光年之外看到的优化器模块,具体的细节我们会留到下一本数据仓库的书中 介绍。不过,对于数据运维的 DBA 来说,了解数据库对象的统计信息是至关重要的。 我们刚才谈到了,为了能够让 DB2 更好地生成一个最佳的访问计划,准确地理解每一个数 据对象是非常重要的。对于刚才的例子,如果数据库不知道表 A 有 10000 行,优化器就不能有 效地选择第二种访问方式的。 这些代表每个数据库对象状态的信息叫做统计信息,其相关信息可以使用 SYSSTAT 模式的 视图访问: 440 DB2 数据库管理最佳实践 /home/db2inst1 $ db2 "list tables for all" | grep -i "SYSSTAT" COLDIST SYSSTAT V 2011-01-12-20.56.05.285121 COLGROUPDIST SYSSTAT V 2011-01-12-20.56.05.291256 COLGROUPDISTCOUNTS SYSSTAT V 2011-01-12-20.56.05.297720 COLGROUPS SYSSTAT V 2011-01-12-20.56.05.301516 COLUMNS SYSSTAT V 2011-01-12-20.56.05.304493 FUNCTIONS SYSSTAT V 2011-01-12-20.56.05.311247 INDEXES SYSSTAT V 2011-01-12-20.56.05.318353 ROUTINES SYSSTAT V 2011-01-12-20.56.05.328908 TABLES SYSSTAT V 2011-01-12-20.56.05.333256 从名字可以看出,这些系统编目表有些是针对表对象的,而有些则是针对索引或者列对象 的。不同数据对象类型的统计信息使用不同的视图访问,相关的列信息请参见 IBM DB2 信息 中心。在第 10 章我们已经介绍了统计信息收集的命令:Runstats,并介绍了 Runstats 执行的时 机和最佳实践。 接下来我们介绍如何查看执行计划。可能很多读者已经了解了 Visual Explain 工具,但是作 者强烈推荐使用 db2exfmt 工具,生成文本访问计划。 相比起图形化 Visual Explain 生成的访问计划,db2exfmt 工具的输出要完善许多,并且包含 很多重要的信息,帮助用户理解“为什么 DB2 生成如此的访问计划”。 首先,让我们来看一看如何收集 db2exfmt 信息: /home/db2inst1 $ db2sampl Creating database "SAMPLE"... Connecting to database "SAMPLE"... Creating tables and data in schema "DB2INST1"... Creating tables with XML columns and XML data in schema "DB2INST1"... 'db2sampl' processing complete. /home/db2inst1 $ db2 connect to sample Database Connection Information Database server = DB2/AIX64 9.7.3 SQL authorization ID = DB2INST1 Local database alias = SAMPLE (db2inst1@db2host1) /home/db2inst1 $ db2 -tvf ~/sqllib/misc/EXPLAIN.DDL > /dev/null /home/db2inst1 $ db2 "select empno, firstnme, lastname from employee, department where employee.workdept = department.deptno and mgrno='000060'" EMPNO FIRSTNME LASTNAME ------ ------------ --------------- 000060 IRVING STERN 000150 BRUCE ADAMSON 第 16 章 优化器与性能调优 441 000160 ELIZABETH PIANKA 000170 MASATOSHI YOSHIMURA 000180 MARILYN SCOUTTEN 000190 JAMES WALKER 000200 DAVID BROWN 000210 WILLIAM JONES 000220 JENNIFER LUTZ 200170 KIYOSHI YAMAMOTO 200220 REBA JOHN 11 record(s) selected. /home/db2inst1 $ db2 set current explain mode explain DB20000I The SQL command completed successfully. /home/db2inst1 $ db2 "select empno, firstnme, lastname from employee, department where employee.workdept = department.deptno and mgrno='000060'" SQL0217W The statement was not executed as only Explain information requests are being processed. SQLSTATE=01604 /home/db2inst1 $ db2 set current explain mode no DB20000I The SQL command completed successfully. /home/db2inst1 $ db2exfmt -d SAMPLE -g TIC -w -1 -n % -s % -# 0 -o prod_sample_exfmt.txt DB2 Universal Database Version 9.7, 5622-044 (c) Copyright IBM Corp. 1991, 2009 Licensed Material - Program Property of IBM IBM DATABASE 2 Explain Table Format Tool Connecting to the Database. Connect to Database Successful. Binding package - Bind was Successful Output is in prod_sample_exfmt.txt. Executing Connect Reset -- Connect Reset was Successful. 在创建了数据库之后,我们首先运行了“db2 -tvf ~/sqllib/misc/EXPLAIN.DDL” 来创建访问 计划表。EXPLAIN.DDL 文件存在于所有的实例目录 sqllib/misc 下,包含多个表的定义用来临时 存储访问计划信息。 当运行了 DDL 后,用户可以使用命令“db2 set current explain mode explain” 打开访问计划选 项,按照普通方式执行 SQL,然后使用“db2 set current explain mode no”关闭访问计划选项。 当访问计划表被创建,并且访问计划选项被打开后,执行 SQL 的语句并不真正执行该 SQL, 而是生成一个针对该 SQL 的访问计划。访问计划的信息存储于使用 EXPLAIN.DDL 生成的表中, 使用 db2exfmt 工具将这些信息汇总到一个文件中。 db2exfmt 工具包含很多参数,这里我们不一一列举。一般来说,使用下列参数就可以很好 地从一个数据库中得到最新收集的访问计划: db2exfmt -d <数据库名称> -g TIC -w -1 -n % -s % -# 0 -o <输出文件> 让我们来看一看 db2exfmt 生成的文件中都包含了什么信息。 442 DB2 数据库管理最佳实践 (1)系统信息: DB2_VERSION: 09.07.3 SOURCE_NAME: SQLC2H21 SOURCE_SCHEMA: NULLID SOURCE_VERSION: EXPLAIN_TIME: 2011-01-18-14.02.17.151293 EXPLAIN_REQUESTER: DB2INST1 用户在分析的过程中首先要判断,一个文件是不是我们真正感兴趣的。假设如果问题发生 在 3:00,但是文件的收集时间在 1:00,很有可能这个文件并不包含真正的问题语句。 (2)系统设置: Parallelism: None CPU Speed: 4.000000e-05 Comm Speed: 0 Buffer Pool size: 1000 Sort Heap size: 256 Database Heap size: 1200 Lock List size: 4096 Maximum Lock List: 10 Average Applications: 1 Locks Available: 13107 在数据库的几十个参数中,真正对优化器起作用的也只有这么几个(当然还包括一些 db2set 和多分区信息)。其中 Parallelism 可以是 None,代表单分区,或者 Interparallel 代表多分区,或 者 Intraparallel 代表分区内并行,或者多分区与分区内并行的结合。 然后 CPU 速度则是当前 CPU 速度的一个估算值,注意该值并不代表任何有意义的单位, 而是 DB2 在优化其中使用 CPU 速度作为一个参量。该数值在实例启动时自动计算。 Comm Speed 是通信速度,在多分区系统中默认为 100,可以根据需求调节。 Buffer Pool Size 是系统中缓冲池总页数的和。注意 DB2 优化器在计算访问计划时,并不知 道都有哪些缓冲池将被使用(譬如说某些计划可能需要一些临时表,而临时表的缓冲池也许不 同于数据表),因此缓冲池大小只是简单地将数据库中所有缓冲池的页面数量相加。 Sort Heap Size 是排序堆的大小,用来估算排序或者哈希关联是否会溢出。 Database Heap size 是数据库堆栈大小,在通常情况下对访问计划不会产生决定性的影响。 Lock List Size、Maximum Lock List、Average Applications 与 Locks Available 是锁列表信息, 用来估算某些类型的数据访问是否有足够锁列表容纳相关数据。 (3)用户查询: Original Statement: ------------------ 第 16 章 优化器与性能调优 443 select empno, firstnme, lastname from employee, department where employee.workdept = department.deptno and mgrno='000060' 这个查询就是用户所输入的查询,应当与用户的输入完全匹配。如果用户发现该查询不符 合刚输入的命令,请详细检查先前的步骤。 (4)查询重写器所重写的查询: Optimized Statement: ------------------- SELECT Q2.EMPNO AS "EMPNO", Q2.FIRSTNME AS "FIRSTNME", Q2.LASTNAME AS "LASTNAME" FROM DB2INST1.DEPARTMENT AS Q1, DB2INST1.EMPLOYEE AS Q2 WHERE (Q1.MGRNO = '000060') AND (Q2.WORKDEPT = Q1.DEPTNO) 可以看到,重写器在每个表后面加上了别名,然后重新组织了一下两个条件。 注意:在这里想要提一句,经常看到有用户问:到底哪一个条件应该放在语句的前面。 在 DB2 中,答案是无所谓的。DB2 的优化器足够强大,可以通过不同的排列次序 估算出执行时哪个条件在前会比较有利。因此用户自己的排列不会被优化器所认 可,优化器完全基于开销对访问计划进行评估,例如图 16.1 所示的访问计划。 图 16.1 访问计划 444 DB2 数据库管理最佳实践 这就是我们谈论了很久的访问计划。在这个访问计划中,我们首先看到,Total Cost 的估算 为 20.3069。注意这个估算值并没有实际单位。其值并不是基于秒或者毫秒的,因此在比较两个 访问计划开销时,一定要基于同一个系统。在不同系统间的开销不具有可比性。而下面的树状 结构图就是访问计划本身。我们在读计划时从下往上,从左往右。 首先,左下方是 IXSCAN,也就是索引扫描。这个扫描使用了 XDEPT2 索引,统计信息显 示该表中包含了 14 行,而 IXSCAN 预期返回一行,预期开销为 1.78788。然后返回的 RID 则通 过 FETCH 操作从表中读出相应的行,通过嵌套循环关联(Nested Loop Join)与右子树关联。 在右子树中,我们有类似的操作。IXSCAN 使用 XEMP2 索引中读出,每次预期读取 3 个 RID,然 后从 EMPLOYEE 表中读取相应的行。最后将结果集返回给 RETURN,代表查询的结束。 在嵌套循环关联中,其规则是对左子树的每一个元素都会执行一次右子树。譬如说,如果 两个表 A 包含(1,2,3),而 B 包含(3,4),如果使用嵌套关联并且 A 在左子树中,则对于 A 中 的每一条记录都要扫描一遍 B 表。也就是说,对于 1,会检查 B 表中是否包含这个数据,然后 检查 2,以此类推。 但是如果 B 在左子树中,那么首先对于 3 会扫描整个 A 表,然后读取 4 再一次扫描 A 表中 匹配的数据。 在这个访问计划中,我们暂时可以看出,DB2 估算的最佳访问计划是:首先用索引扫描从 DEPARTMENT 中得到预期一行,然后对于这些行使用索引扫描,从 EMPLOYEE 表中得到 3 行, 最后返回结果。 在接下来的讲解中,我们会根据 db2exfmt 所提供的越来越多的信息不断完善这个访问计划 的描述。 下面我们值得关注的信息是这部分: 1) RETURN: (Return Result) Cumulative Total Cost: 20.3069 Cumulative CPU Cost: 129672 Cumulative I/O Cost: 2 Cumulative Re-Total Cost: 1.694 Cumulative Re-CPU Cost: 42350 Cumulative Re-I/O Cost: 0 Cumulative First Row Cost: 19.8859 Estimated Bufferpool Buffers: 3 Arguments: --------- BLDLEVEL: (Build level) DB2 v9.7.0.3 : s101006 HEAPUSE : (Maximum Statement Heap Usage) 112 Pages PREPTIME: (Statement prepare time) 第 16 章 优化器与性能调优 445 34 milliseconds STMTHEAP: (Statement heap size) 8192 Input Streams: ------------- 9) From Operator #2 Estimated number of rows: 3 Number of columns: 3 Subquery predicate ID: Not Applicable Column Names: ------------ +Q3.LASTNAME+Q3.FIRSTNME+Q3.EMPNO 该信息描述 RETURN(1)操作中的一些重要信息。 前面的一段是开销估算,例如总开销为 20.3069,其中 CPU 开销为 129672(注意,这些开 销没有单位,在对比中只能与同数据库中的开销对比),I/O 开销为 2 等。 其中包括当前的数据库版本、使用了多少 statement heap、编译该语句的时间,以及一些可 能的针对优化器的 db2set 信息(在本例中没有显示)。 Input Stream 说明该操作的输入是来自操作(2),预期返回 3 行,包括 3 列,这 3 列的名字 分别是 LASTNAME、FIRSTNME 与 EMPNO。 现在,我们继续完善我们的访问计划描述。 先前的描述为:首先用索引扫描从 DEPARTMENT 中得到预期一行,然后对于这些行使用 索引扫描,从 EMPLOYEE 表中得到 3 行,最后返回结果。 然后我们添加上一些信息:首先用索引扫描从 DEPARTMENT 中得到预期一行,然后对于 这些行使用索引扫描,从 EMPLOYEE 表中得到 3 行,最后返回 3 行,包括 LASTNAME、 FIRSTNME 与 EMPNO。 紧接着,看下面的数据: 2) NLJOIN: (Nested Loop Join) Cumulative Total Cost: 20.3069 Cumulative CPU Cost: 129672 Cumulative I/O Cost: 2 Cumulative Re-Total Cost: 1.694 Cumulative Re-CPU Cost: 42350 Cumulative Re-I/O Cost: 0 Cumulative First Row Cost: 19.8859 Estimated Bufferpool Buffers: 3 Arguments: 446 DB2 数据库管理最佳实践 --------- EARLYOUT: (Early Out flag) NONE FETCHMAX: (Override for FETCH MAXPAGES) IGNORE ISCANMAX: (Override for ISCAN MAXPAGES) IGNORE Predicates: ---------- 3) Predicate used in Join, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.0714286 Predicate Text: -------------- (Q2.WORKDEPT = Q1.DEPTNO) Input Streams: ------------- 4) From Operator #3 Estimated number of rows: 1 Number of columns: 1 Subquery predicate ID: Not Applicable Column Names: ------------ +Q1.DEPTNO 8) From Operator #5 Estimated number of rows: 3 Number of columns: 4 Subquery predicate ID: Not Applicable Column Names: ------------ +Q2.WORKDEPT(A)+Q2.LASTNAME+Q2.FIRSTNME +Q2.EMPNO Output Streams: -------------- 9) To Operator #1 Estimated number of rows: 3 Number of columns: 3 第 16 章 优化器与性能调优 447 Subquery predicate ID: Not Applicable Column Names: ------------ +Q3.LASTNAME+Q3.FIRSTNME+Q3.EMPNO 这一段数据代表嵌套循环关联步骤,也就是 NLJOIN(2)。 首先出场的还是估算开销,紧接着是一些 NLJOIN 使用的特定参数,然后就是该 NLJOIN 的关联列,在这里就是: 3) Predicate used in Join, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.0714286 Predicate Text: -------------- (Q2.WORKDEPT = Q1.DEPTNO) 这部分的意思是,经过根据表对象统计数据的估算,对于 DEPARTMENT 中的每一个返回 行有大约 7.14%的数据在 EMPLOYEE 中满足 Q2.WORKDEPT = Q1.DEPTNO 条件。 剩下的 Input Stream 和 Output Stream 就是输入和输出列,在此不用详细叙述。 现在,我们再来更新访问计划描述:首先用索引扫描从 DEPARTMENT 中得到预期一行, 然后对于这些行使用 NLJOIN,与 EMPLOYEE 在(Q2.WORKDEPT = Q1.DEPTNO)列上进行关 联,预期大约 7.14%的数据满足关联条件,这样使用索引扫描从 EMPLOYEE 表中预期得到 3 行, 最后返回 3 行,包括 LASTNAME、FIRSTNME 与 EMPNO。 接下来后面的信息是 FETCH(3)与 IXSCAN(4)。由于篇幅所限,这里就不贴出全部数据了, 有兴趣的读者可以自己尝试。 这两个操作对应着 FETCH(3)与 IXSCAN(4)。其中 FETCH 是指对于索引扫描的结果中的每 一个 RID 都要去表对象中将相应的行读出来,如图 16.2 所示。 图 16.2 Fetch 和 Ixscan 访问计划 448 DB2 数据库管理最佳实践 而我们比较感兴趣的是 IXSCAN(4): 4) IXSCAN: (Index Scan) Cumulative Total Cost: 1.78788 Cumulative CPU Cost: 44697 Cumulative I/O Cost: 0 Cumulative Re-Total Cost: 0.36944 Cumulative Re-CPU Cost: 9236 Cumulative Re-I/O Cost: 0 Cumulative First Row Cost: 1.71904 Estimated Bufferpool Buffers: 1 Predicates: ---------- 2) Start Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.0714286 Predicate Text: -------------- (Q1.MGRNO = '000060') 2) Stop Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.0714286 Predicate Text: -------------- (Q1.MGRNO = '000060') 这里最关键的是它的两个 Predicate,乍一看都是(Q1.MGRNO = '000060'),不过仔细看,一 个是 Start Key Predicate,一个是 Stop Key Predicate。Predicate 是说,在进行索引查询时,可以 根据给定的键,通过索引 B+树的结构快速地定位数据节点,然后从节点中读取 RID,将其返回 给 FETCH,从而可以在表对象中将对应的数据读取出来。 现在,我们再来重新组织一下访问计划描述:首先用 Start/Stop Key 索引扫描,根据 Q1.MGRNO = '000060'的条件从 DEPARTMENT 中得到预期一行,然后对这些行使用 NLJOIN, 与 EMPLOYEE 在(Q2.WORKDEPT = Q1.DEPTNO)列上进行关联,预期大约 7.14%的数据满足 关联条件,这样使用索引扫描从 EMPLOYEE 表中预期得到 3 行,最后返回 3 行,包括 LASTNAME、FIRSTNME 与 EMPNO。 这样就变得很完善了,我们继续看最后的两个操作步骤,如图 16.3 所示。 第 16 章 优化器与性能调优 449 图 16.3 这两个步骤是从 EMPLOYEE 表中读数据,同理前面所提到的 FETCH 和 IXSCAN,我们 直 接跳到 IXSCAN 步骤的详细信息: 6) IXSCAN: (Index Scan) Cumulative Total Cost: 2.1406 Cumulative CPU Cost: 53515 Cumulative I/O Cost: 0 Cumulative Re-Total Cost: 0.72216 Cumulative Re-CPU Cost: 18054 Cumulative Re-I/O Cost: 0 Cumulative First Row Cost: 1.93408 Estimated Bufferpool Buffers: 1 Predicates: ---------- 3) Start Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.0714286 Predicate Text: -------------- (Q2.WORKDEPT = Q1.DEPTNO) 3) Stop Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.0714286 Predicate Text: -------------- (Q2.WORKDEPT = Q1.DEPTNO) 450 DB2 数据库管理最佳实践 这里的 Predicate 依然是 Start/Stop Key,而条件则是(Q2.WORKDEPT = Q1.DEPTNO),所代 表的含义是,对于从 DEPARTMENT 表中使用索引扫描和 FETCH 之后所拿到的行,得到每行的 DEPTNO 列,然后根据该列的数值,在 XEMP2 索引中使用(Q2.WORKDEPT = Q1.DEPTNO)条 件,对其输入的每行得到对应的 RID。 因此,最终的访问计划描述为:首先 用 Start/Stop Key 索引扫描,根据 Q1.MGRNO = '000060' 的条件从 DEPARTMENT 中得到预期一行,然后对于这些行使用 NLJOIN,与 EMPLOYEE 在 (Q2.WORKDEPT = Q1.DEPTNO)列上进行关联,预期大约 7.14%的数据满足关联条件,这样使 用索引扫描,对于从 DEPARTMENT 表上返回的行,根据 Q2.WORKDEPT = Q1.DEPTNO 条件 从 EMPLOYEE 表中预期总共得到 3 行,然后预期返回 3 行,包括 LASTNAME、FIRSTNME 与 EMPNO 列。 由于分析 db2exfmt 并不是一件简单的工作,本文在这里也只是抛砖引玉,帮助读者初步了 解如何阅读并理解 db2exfmt 的输出。如果用户想要进一步了解 DB2 优化器,我们将在今后出版 的数据仓库部分中详细讨论优化器原理,以及一些基本的分析步骤。 16.2 性能调优简介 类似性能诊断,性能调优在很多方面与问题诊断有相通之处,甚至从某种程度上说,性能 调优是建立在成功的问题诊断的基础之上。 但是两者还是有一定的差异。 性能诊断强调的是“诊断”二字,也就是弄明白系统为什么慢,瓶颈出在什么地方。可是对 于曾经真正做过系统调优的用户来说,也许深有体会的一件事就是,弄明白问题的出处和真正 解决一个问题,有时候并不是完全相关的。 也许有人会问:既然一个问题的根源都已经被发现了,为什么不能解决呢? 有的时候,譬如说我们发现一个系统的瓶颈制约在 I/O 上,可是也许我们的系统已经使用 最高端的存储设备,同时对于 24×7 的在线系统不允许我们花费一两天的时间将数据重新物理 分布,那么在这种情况下,有没有什么其他的办法从侧面解决问题,这就是性能调优的最大的 难处。 而另一方面,有的时候彻底地研究性能问题的根源相当困难与费时,那么如何能够在并不 完全透彻地了解问题根源之前,就用某些方法绕过问题,也是性能调优专家需要精通的技能。 所以,调优的最终目的不是完全挖掘出问题的根源(与性能分析的终极目标不同),而是用不 同手段提升系统的单位时间内吞吐量。有可能性能问题的根源根本不在数据库端(譬如应用程序 的设计不好,造成很多锁等待,使得系统性能极不稳定),但是如果我们能够在数据库端找到某种 方法绕过问题(比如使用 currently committed 功能)使性能提升,就是一次成功的性能调优。 第 16 章 优化器与性能调优 451 正如读者现在可能在想的,从某种意义上说,性能调优比性能分析更加直观与灵活。有的 时候只要了解了性能问题的现象,通过经验与一定程度上对应用与数据库的理解,就能够大体 上给出一个调优的方向(譬如是磁盘问题,还是 CPU 问题,还是内存问题)。 在这一节中,我们不想花费大量的篇章讨论性能调优的原理(关于性能分析的原理可以参 照性能诊断章节),而是专注于如何解决和绕过一些常见的性能问题。 类似性能分析,在调优时,首先尝试理解系统的架构。至少要弄明白,该系统有多少 CPU, 多少内存,多少连接之类的最基本的问题。 其次,是将问题细化到 CPU、内存 、I/O 或者系统懒惰的大方向。在阅读了性能诊断篇章后, 相信读者都会对这一步不再陌生。 再次,就是要给出一个期望值,尝试用不同的手段将某种指标提高到目标之上,然后再次 重复细化问题的步骤,直到系统的整体性能达到预期。 因此,在系统调优时,我们面临以下两个难点:  如何找到一个合理的目标与期望值。  用什么手段达到目的。 要说这两个难点哪一个更重要,自然是第一点。只有寻找到一个正确的目标,才能保证我 们其后的调优是有效的。如果目标都定位不准,那么也就无从谈起如何调优了。 在没有经历过完整的性能分析时,最常见的手段就是参照一些最佳实践文档,一步步采纳 文档中的建议进行调优。 不要小看这种类似纯体力工作的步骤,对于大多数性能问题,这种方法可以简单有效地解 决问题。不过由于市场上已经存在太多的关于最佳实践的文档,因此作者在本书中将不再讨论 这一点。需要的读者可以在 IBM 网站上找到很多相关资料。 当最佳实践文档无法解决用户的难题,或者说文档中的很多建议无法在一个已经运行多年 的系统上实现时,我们就需要一个更加具体的思路来找到合理的目标,这就是我们在前文中所 提到的 KPI。 KPI 作为国内外无数性能分析专家多年来的总结,可以被大部分系统所采纳。当然,有些 KPI 是针对某一种特定业务(例如 OLTP),而另一种类型的业务(比如 OLAP)可能会在同样 的指标中使用完全不同的建议值。 一般来说,KPI 可以被分为以下几个级别:  数据库级别。  实例级别。  操作系统级别。 452 DB2 数据库管理最佳实践 对于数据库级别 KPI,其分析指标完全来自数据库本身。而实例级别则是针对实例中的一 些指标进行监控。操作系统级别就是从高层面观察一个系统的行为,看看是否能够迅速定位出 需要提高的部分。 这里我们先列出几种最常用的 KPI,让读者大致明白 KPI 的作用,然后我们会通过一些案 例来说明如何进行系统性能调优。 后面我们将会列举更加详尽的 KPI 及其作用。本章调优中,只是抛砖引玉,简单介绍几个 KPI 的用法,如表 16.1 所示。 表 16.1 作者知道,上面的指标远远不能涵盖日常工作中所遇到的性能问题。不过通过表 16.1,至 少读者有了一个关于什么是 KPI 的了解。在下面的 KPI 中我们将详细探讨几十个最重要与常用 的性能关键指标。 KPI 名称 来 源 计 算 公 式 建 议 数 值 详 细 说 明 数据索引缓冲 池命中率 数据库、缓冲池、 表空间快照 (逻辑读-物理读)/逻辑读 优秀:>95% 良好:>80% 代表着一个数据请求可以 在不通过物理 I/O 的情况 下,直接从内存池找到的 几率。该项目的提高,代 表使用 I/O 几率的降低 读行 v.s.选择行 数据库快照 Rows Read / Rows Select OLTP 系统:<=5 SAP 系统:<=3 数据库读取的行数与真正 在结果集中所选择的行 数。该项目的数值越低, 说明每次都读取真正有用 的数据的比例越高 排序内存过量 使用 实例快照 排序内存高水 位 > SHEAPTHRES 当排序内存超出 SHEAP THRES 时,实例将会分配 额外的排序内存。可能导 致内存持续的分配与回 收,甚至使用临时表空间 磁盘 物理磁盘数量 操作系统信息 每 CPU 内核对应 6~20 个物理磁 盘数量 物理磁盘的数量决定着 I/O 的效率。当数据库数据 被均匀分布到不同磁盘上 时,从不同的页面中读取 数据会使有更多的机会访 问不同的物理磁盘,这样 能够加快并行系统的 I/O 吞吐量 第 16 章 优化器与性能调优 453 接下来,让我们来看几个真正的性能调优案例。正像前面所提到的,针对系统优化问题的 特殊性,没有什么方法能够保证用户一定找到问题的答案。所以同样地,我们将会给出相对完 整的分析步骤和思路,但是着重强调所表现出的现象,而不是像上一章那样所强调后台的原理。 该案例发生在 2011 年 1 月初,系统为 Linux,64 位,8 颗 CPU 内核,32GB 内存, db2 版本为 DB2 9.5 Fixpack 5。 用户的问题是,每年年底在运行结算程序时,系统性能严重下降。该系统平时性能表现一般,但是当每年年底 的结算程序运行时,一定要减少并发数量,否则会轻易造成 100%CPU 使用率。 客户明白该应用很可能存在设计问题,但是需要我们提供一些调优思路帮助解决问题。 系统中只有一个实例运行,单分区。 既然是没有针对性模块的普通调优建议,那么我们可以从快照入手分析。 用户重置快照后,15 分钟收集了若干包括数据库、实例、表空间、SQL、应用程序等快照和一些系统信息,希 望我们能够从中给出建议。 从系统信息中,我们可以看到系统的使用率维持在大概 50~70%左右。而在数据收集的最后几分钟冲到 90%以 上。很显然,我们的 CPU 使用显然是个大问题,也从侧面验证了客户曾经的观测结果。 procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------ r b swpd free buff cache si so bi bo in cs us sy id wa st 3 0 305420 4602212 2072 23901232 0 0 22878 5994 4190 50413 21 16 58 5 0 15 1 305420 4434696 2088 24062144 0 0 4132 17042 4658 16947 11 8 80 1 0 2 1 305420 9139256 2096 19357272 0 0 20491 7516 3845 61736 18 21 59 2 0 2 1 305420 10577924 2112 17933692 0 0 46191 2136 4723 55323 19 14 59 8 0 19 0 305420 9975284 2128 18542952 0 0 81786 3156 5093 156035 56 19 20 5 0 2 1 305420 9579476 2144 18919188 0 0 61790 12208 5431 105615 38 13 38 11 0 1 1 305420 9247096 2152 19254984 0 0 61102 614 4628 35970 24 9 56 11 0 8 0 305420 8885696 2168 19630252 0 0 66905 2662 5085 44408 27 10 51 13 0 1 1 305420 8654744 2184 19857360 0 0 43444 3844 4095 20296 24 8 63 4 0 0 2 305420 8279600 2200 20219676 0 0 64951 10162 5624 12737 43 16 34 6 0 7 0 305420 10358480 2224 18125288 0 0 43894 663 3955 23715 36 13 47 3 0 … procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------ r b swpd free buff cache si so bi bo in cs us sy id wa st 5 0 297096 5964328 4036 22506736 0 0 4334 93423 8621 57023 57 23 19 1 0 10 2 297096 5745696 4052 22740020 0 0 9579 42424 6255 55024 68 17 13 1 0 24 0 297096 5155376 4068 23321812 0 0 717 119550 8734 45119 63 26 11 0 0 24 1 297096 4769692 4084 23710536 0 0 2754 67116 5453 44193 71 23 5 0 0 19 0 297096 4267292 4100 24214268 0 0 1707 121084 7784 39624 67 28 5 0 0 36 0 297096 3824592 4120 24648828 0 0 546 89163 6764 64811 71 22 6 0 0 18 0 297096 3527636 4128 24962944 0 0 11949 55640 6592 63437 58 17 23 2 0 27 0 297096 3195580 4144 25290712 0 0 3430 64558 7308 32265 71 22 7 0 0 27 1 297096 2750700 4160 25733060 0 0 3695 92971 8972 41527 62 24 13 0 0 而从 ps -elf 输出中,db2sysc 进程的 C 列维持在 99: 4 S db2inst1 28735 28733 99 85 0 - 1983046 184466 08:22 ? 1-05:35:49 db2sysc 454 DB2 数据库管理最佳实践 0 同时还有一个 db2fmp 也在使用不少的 CPU,不过比起 db2sysc 就少很多了: 4 S db2inst1 15638 28733 21 76 0 - 233743 semtim 21:27 ? 00:04:34 db2fmp ( ,1,1,0,0,0,0,0,1,0,8a1984,14,1e014,2,0,1,2331fc0,0x210000000,0x210000000,16000 00,50268004,2,47da800b 这点毫无疑问地说明了 db2sysc 中存在大量消耗 CPU 的任务。 然后在数据库快照中我们看到: Buffer pool data logical reads = 3037962816 Buffer pool index logical reads = 854461868 这说明什么呢?数据逻辑读与索引逻辑读的比例相差将近 4 倍,也就是说非常多的读是走表扫描,而不是索引 扫描。 于是,这是第一点可能有待提高的方面(当然,对于 OLAP 系统来说,有时候这种现象是正常的。分析人员一 定要理解应用程序正在执行的业务)。 然后继续我们的观察: Rows selected = 76395867 Rows read = 45143452324 Rows selected 与 Rows read 的比例极低。对比我们刚才的 KPI,在 OLTP 系统中预期数值是 5。而这个 系统明显是 OLAP,但是其比例达到 500 以上也是明显不合理的(说明需要读平均 500 行才能真正选定一行用 户需要的数据)。 然后我们进入 SQL 快照,通过计算 Total user cpu time / Number of executions 得到单一 SQL 的 平均消耗 CPU 时间,可以看到: Number of executions = 434 Total user cpu time (sec.microsec) = 22828.046168 Rows read = 11327044778 Rows written = 9804015280 Buffer pool data logical reads = 1280044999 Buffer pool data physical reads = 171604105 Buffer pool temporary data logical reads = 51594042 Buffer pool temporary data physical reads = 4860358 Buffer pool index logical reads = 22887712 Buffer pool index physical reads = 4403 Statement text = INSERT INTO SESSION.MYTEMPTABLE… 具体的 SQL 在这里不显示。通过观察 434 次执行的总 CPU 时间与逻辑、物理读的数量,可以发现该 INSERT占用大量的资源(平均每个 INSERT 要读取 2600 万行)。 继续找还可以发现其他占用大量 CPU 的语句: Number of executions = 402 Total user cpu time (sec.microsec) = 4708.016408 Buffer pool data logical reads = 542103826 Buffer pool data physical reads = 21217460 第 16 章 优化器与性能调优 455 Buffer pool temporary data logical reads = 5240499 Buffer pool temporary data physical reads = 29094 Buffer pool index logical reads = 564654474 Buffer pool index physical reads = 3814 Rows read = 1007057175 Rows written = 147518950 Statement text = insert into SESSION.MYTEMPTABLE2… Number of executions = 22461 Total user cpu time (sec.microsec) = 4635.475109 Rows read = 228327230 Buffer pool data logical reads = 33378048 Buffer pool data physical reads = 95206 Buffer pool temporary data logical reads = 16846 Buffer pool temporary data physical reads = 0 Buffer pool index logical reads = 101241520 Buffer pool index physical reads = 18 Statement text = SELECT COUNT(*) INTO :HV00011 :HI00011 … 通过对比这些逻辑读与 CPU 消耗的时间,我们可以清楚地看到在重置快照到数据收集的这段时间内,到底哪些 SQL 占用了最多的资源。 然后继续应用程序快照,我们看到了当前有 10 个正在 UOW Executing 的连接,其中 8 个为可执行文件,2个为 Java: Application handle = 48138 Application status = UOW Executing Status change time = Not Collected Application code page = 819 Application country/region code = 0 DUOW correlation token = A1C23E82.C281.012D592C1D49 Application name = db2jccThread-7 Application ID = A1C23E82.C281.012D592C1D49 ... Application handle = 48326 Application status = UOW Executing Status change time = Not Collected Application code page = 819 Application country/region code = 1 DUOW correlation token = 161.194.65.138.1037.11010602420 Application name = myprogram.exe Application ID = 161.194.65.138.1037.11010602420 Sequence number = 00001 两个 Java 程序都是在执行同样的 INSERT,同时另外 8 个.exe 程序都在执行前面所看到的那个消耗 CPU 最 大的查询(查询本身大约为 350 行的 SQL)。 这样,我们就有足够的理由认为,这几个(尤其是那个消耗 CPU 最大的查询)查询需要被进一步优化,而入手 点则应该从减少逻辑读开始。因为单纯的内存读并不占用 I/O,但是大量的内存访问绝对对 CPU 是一大负担。 456 DB2 数据库管理最佳实践 当然,找到了这些 SQL 语句后,从 DBA 的角度,我们可以尝试加一些索引或其他一些物理设计影响优化器的执 行计划,减少表扫描和排序等,降低 CPU 和其他资源的占用。如果在 DBA 层面无法解决的问题,比如 SQL 语 句写的实在很差,那就要从查询的设计入手(开发人员的工作,而不是运维人员的任务),看是否可以从逻辑的 角度减少数据访问的数量。 在上面的例子中,我们尽管没有明确提及,但是读者应该看到了,引导我们针对 SQL 进行 调优分析的重要依据就是“数据读取/数据选择”的比例。尽管在 OLAP 系统中很难找到一个合 适的数值来定义其好坏,但是每 500 个数据读对应一个选择的比例,在这种中等系统中过于夸 张。假设如果有一种理想状态下的方法使得所读的每行都是要选择的数据,那么理论上系统性 能将会提高 500 倍。因此,我们的调优方向就是尽可能减小这个比例。有些情况下,我们的调 优并不能与问题的根源正面交锋,而是需要一些迂回的策略解决问题。 在接下来的案例中,由于作者并没有太多当时收集的性能统计数据,所以作者只是简要地 阐述一下问题的现象和调优的思路。 该案例发生于 2010 年初国外的一家银行。该系统运行 DB2 V8,主要用于全国各地支行的柜台业务员在工作 时间的一些业务处理(也就是 OLTP)。 系统的整体运行相对稳定,但是唯一有问题的就是其中的一个操作对应着数据库中的一个存储过程。该存储过 程并不复杂,最主要的部分是一个 INSERT 操作,从 4 个表中做 SELECT 然后插入一个临时表。 问题就是,每周刚开始的时候该存储过程的性能还算说得过去,大概维持在 0.5 秒左右。可是随着数据量的增 加该存储过程的性能不断下降。到了周五的时候就只有 3~4 秒了。然后第二个周一开始的时候,存储过程性 能恢复正常,但是在一周中持续下降,到周五又变成 3~4 秒了。 通过描述,我们第一个问题就是:每天的数据量是否相同。 通过对系统的深入研究,发现用户每周的维护时间都使用脚本将一个大表清空一半左右的数据,然后进行 REORG、RUNSTATS 和 REBIND,开始新的一周的工作。 看到这里,相信很多读者都意识到发生了什么,就是随着数据量的上升,系统的统计信息无法反应最真实的当 前状态,因此优化器一直认为表中的数据量相对较少,因而选择了一个对当前数据量并非最优的访问计划。 逻辑上的推理完全能够解释这个现象,根据当时所观察到的数据,我们同样发现,问题的大表所被读取的行数 在一周内的每天都有增长,同时对应的语句在一周内不同时间使用动态 SQL 抓 exfmt,所显示的访问计划与维 护完成之后的计划明显有所区别。 这些数据都可以从侧面证实我们的推论(与性能诊断相同,在调优中千万不要由于某几条快照信息就匆忙地下 结论,一定要从多个方向和角度看待问题)。 但是用户的系统维护策略无法让他们每天运行 RUNSTATS 和 REBIND,因此我们必须使用其他的方法来保证存 储过程使用最优的访问计划。 那么我们应该怎么办呢? 通过一般的逻辑判断,假设一个查询的访问计划对于大数据量时是最优的,那么当数据量减少后,尽管该计划 可能并非最优,但是其执行时间应该不会比数据量占用更多时长。因为按照同样访问数据的方法,在数据量减 第 16 章 优化器与性能调优 457 小的情况下,性能不会比数据量多时更差。 在这个前提条件成立的情况下,我们测试了手工 RUNSTATS 和 REBIND,发现在数据量最多的情况下,使用最 新的统计数据可以使存储过程维持在一秒之内。 那么我们下一步要做的就是看看如何让这个存储过程一直强制使用该访问计划。 可能有人说要用 profile,不过不需要那么复杂,我们有两个选择。 一种方法就是在删除数据之前做 RUNSTATS 与 REBIND,然 后 REORG 表释放空间,另一种方法就是找一个周末 删除数据之前做 RUNSTATS+REBIND 的存储过程,然后在今后的维护脚本中永远不进行 REBIND。 这两种方法的目的是一样的,就是能够在数据量减小之前将该存储过程的访问计划设定好,这样在接下来的一 周中就算数据量减小,也可以使用同样适用于大规模数据的访问计划。 这个案例告诉我们,性能调优并不一定是要解决系统性能问题的根源。在这个案例中,如 果想要解决根源问题,需要每天定时进行 RUNSTATS 与 REBIND,但是在客户的系统中根本无 法实现。这样就需要我们使用其他的方法“绕过”这个这问题。所以,这里再次强调,性能调优 不同于性能分析,我们的终极目标并不是找到根源并且硬碰硬地解决它,而是通过各种迂回的 办法绕过这些问题。在很多时候,我们可能并不知道问题的根源,而是凭着处理过大量性能问 题后得到的宝贵经验,对某一个或几个参数进行调整后提升性能,都是能够被认可的成功性能 调优。 在书中,分析并且理解问题的根源是关键的,但是在用户的工作中,结果才是最重要的。 因此读者千万不要因为阅读本书后而对自己的思想产生局限,而是要以此为基础拓展思路,争 取早日获得属于自己的性能调优理念。 根据笔者的经验,很多数据库的性能问题都是 SQL 语句引起,对于 DBA 来说,即使找到 了差的 SQL 语句,也很难直接修改 SQL 语句本身。这时,就可以考虑在数据库层面对数据库做 一些优化。接下来,将重点介绍索引的使用和最佳实践,以及排序的根源及减少排序的思路和 方法。 16.2.1 索引 在 7.4 节,介绍了索引的算法、原理和命令使用,本节我们要谈的是索引的优缺点及如何有 效地使用索引,即索引使用的最佳实践。 相信很多用户都有过类似经验,当发现一个 SQL 运行效率不佳的时候,在向别人询问时, 他人的答案很多都是“加索引”。但是不知道大家思考过没有,什么样的索引才是有效的。 我们在前面提到了,索引实际上就是 B+树,树中的每一个节点包含键值与一系列 RID。索 引之所以能够提升性能,就是在于当查找 SQL 的某个谓词时,可以使用 B+树迅速地找到对应 的一系列键值,而不用去扫描整个表中所有的数据页。 458 DB2 数据库管理最佳实践 因此,了解了 B+树的工作原理后,如何有效地使用索引也就呼之欲出了:对于给定谓词, 能够将谓词中的条件列与索引键值所代表的列相匹配,并且当优化器判定使用 B+树扫描的效率 高于全表数据页扫描的时候,这个索引就是有效的。 在这里我们有两个条件。第一个条件就是,怎样能够让谓词条件与索引键值对应的数据列 所匹配;而第二个条件就是,怎样让优化器认为使用索引的效率更高。 在平时的工作中,作者发现很多用户仅仅注意其中的一个条件。当发现建立的索引不能被 使用时,很多人仅仅从其中一个方面入手但是忽略了另外一个因素。 首先,让我们看一看怎样将谓词条件与索引键值对应的列匹配。 在一个 B+树的键中,当包含多个元素的时候(多个数据列),其排列次序是顺序排列,也 就是在一个给定的 B+树节点(键)中,按照键值 1、键值 2、键值 3 一个接一个地排列。 而当使用 B+数进行多个元素的查找时,首先匹配键值 1。如果键值 1 不符合查找的第一个 元素所引用的范围,则查找其左子树或者右子树。当键值 1 匹配的时候,则进一步判定键值 2 是否满足引用的范围,以此类推。 这样我们能够看出,想要有效地利用 B+树,最重要的一点就是,对于键中的每一个元素(每 一列),想要使用元素 2,必须要在元素 1 被判定后才有可能。也就是说,假设一个键中包含数 据列(C1,C2,C3),那么想要判定 C2 的时候,给定的谓词条件中必须包含 C1。同理,想要判定 C3 的时候,调词条件必须保证 C1、C2 列都被包含。只有满足这个条件的谓词,才能够在索引 扫描时使用 Start/Stop Key 谓词检索。否则即使使用索引,也是 Sargable 谓词或者 Residual 谓词 扫描整个 B+树,其开销在很多情况下甚至高于表扫描。 具体这些谓词含义的解释,我们将在《DB2 数据库高级管理》中的优化器部分详细讨论。 现在我们给出 Start/Stop Key 与 Sargable 谓词显示的例子: /home/db2inst1/temp $ db2 “create table t1 (c1 int, c2 int, c3 int, c4 int)” /home/db2inst1/temp $ db2 “create index i1 on t1 (c1, c2, c3)” /home/db2inst1/temp $ db2 “insert into t1 values (1,2,3,4)” …-- 插入一些不同的数据 /home/db2inst1/temp $ db2 set current query optimization 0 /home/db2inst1/temp $ db2 set current explain mode explain DB20000I The SQL command completed successfully. (db2inst1@db2host1) /home/db2inst1/temp $ db2 "select * from t1 where c1=? and c2=? and c3=? and c4=?" SQL0217W The statement was not executed as only Explain information requests are being processed. SQLSTATE=01604 /home/db2inst1/temp $ db2 set current explain mode no DB20000I The SQL command completed successfully. /home/db2inst1/temp $ db2exfmt -d SAMPLE -g TIC -w -1 -n % -s % -# 0 -o prod_sample_exfmt.txt DB2 Universal Database Version 9.7, 5622-044 (c) Copyright IBM Corp. 1991, 2009 Licensed Material - Program Property of IBM 第 16 章 优化器与性能调优 459 IBM DATABASE 2 Explain Table Format Tool Connecting to the Database. Connect to Database Successful. vim Output is in prod_sample_exfmt.txt. Executing Connect Reset -- Connect Reset was Successful. 图 16.4 Predicates: ---------- 3) Start Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.04 Predicate Text: -------------- (Q1.C3 = ?) 3) Stop Key Predicate, 460 DB2 数据库管理最佳实践 Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.04 Predicate Text: -------------- (Q1.C3 = ?) 4) Start Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.04 Predicate Text: -------------- (Q1.C2 = ?) 4) Stop Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.04 Predicate Text: -------------- (Q1.C2 = ?) 5) Start Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.04 Predicate Text: -------------- (Q1.C1 = ?) 5) Stop Key Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.04 Predicate Text: -------------- (Q1.C1 = ?) 下面的是使用 sargable 谓词的语句: 第 16 章 优化器与性能调优 461 图 16.5 Predicates: ---------- 2) Sargable Predicate, Comparison Operator: Equal (=) Subquery Input Required: No Filter Factor: 0.04 Predicate Text: -------------- (Q1.C3 = ?) 因此,简单地说,就是一个能够被使用 Start/Stop Key 谓词的索引,在查询中谓词所引用的 列中一定要包含该索引的起始列。 譬如查询“SELECT * FROM MYTABLE WHERE C1=5 AND C2=3”,一个有效的索引起始列 必须是 C1 或者 C2 列。如果一个索引即使包含了 C1 或者 C2,但是起始列是其他列,则依然不 能被看成是合理有效的索引。 而第二个条件,如何影响优化器使用索引,就是一个更为复杂的问题。由于对优化器的详 细介绍将在下一本数据仓库书中进行,因此在这里我们不讨论优化器的算法。 462 DB2 数据库管理最佳实践 一般来说,想要影响优化器,需要从统计信息上做文章。如果用户发现即使建立的索引合 法,但是有时候优化器依然使用全表扫描,这个时候用户可以将优化级别调为 0,或者在表上加 volatile 标识,使优化器更倾向于使用索引扫描而不是全表扫描。 volatile 的意思是可变的、易变的。我们知道,优化器选择是否执行索引依赖于统计信息, 如果表数据变化很频繁,当统计信息收集的时候,有可能并不代表该表在其他时刻的统计信息。 那么优化器如果完全根据统计信息制定访问计划的时候,有可能判定访问计划时所根据的统计 信息并不能很好地代表当前的数据分布,这样这个访问计划很有可能并非最优。如果一个表被 设定了 volatile 属性,优化器就会尽量忽略该表的统计信息,尽量使用索引扫描。 用户可以通过 Alter table <表名> volatile 来设定其属性。 在创建索引时,应该遵循以下最佳实践:  分析组合索引键的顺序,如:(a,b)和(b,a)是完全不一样的;  不要创建冗余索引,如:某张表有(a),(a,b)两个索引,则(a)索引是多余的;  验证索引是否被用到,如果没有用到,建议删掉;  尽可能通过 include 创建 index-only 索引,减少数据获取 I/O,提升效能。 索引是 DBA 的性能调优利器,它的主要优点可以概括为以下几点:  最主要的目的是提高查询速度;  避免不必要的表扫描,表扫描是 CPU 的第一杀手;  避免排序操作,排序是 CPU 的第二杀手;  减少死锁发生的概率。 但索引也有缺点,总结如下:  增加了 insert/update/delete 等操作的负担;  索引需要占用额外的磁盘空间;  增加了运维成本,如 RUNSTATS, REORG, LOAD 等操作都要维护索引。 因此,索引并不是越多越好,对于 DBA 来说,需要找出长期不需要的索引并将其删除,这 也是一项比较重要的工作。那么怎样找出无效的索引呢? 以下几种方法可供选择: (1)通过 db2pd–d sample–tcbstats–index。有一个字段是 scans,找到一段时间内 scans 为 0 的值,表示这个索引没有被用到,即可删除: TCB Index Stats: Address TableName IID EmpPgDel RootSplits BndrySplts PseuEmptPg Scans KeyUpdates InclUpdats NonBndSpts PgAllocs Merges PseuDels DelClean IntNodSpl 0x9FF18424 T1 9 0 0 0 0 32 0 0 0 0 0 0 0 0 第 16 章 优化器与性能调优 463 (2)DB2 9.7 提供了一个新的监控表 MON_GET_INDEX 用来识别没有用到的索引: db2inst1@dpf1:~> db2 "SELECT substr(T.tabschema,1,18),substr(t.tabname,1,18),substr(S.INDSCHEMA,1,18), substr(S.INDNAME,1,18) ,t.page_allocations,s.uniquerule,s.indextype FROM TABLE(MON_GET_INDEX('','', -1)) as T, SYSCAT.INDEXES AS S WHERE T.TABSCHEMA = S.TABSCHEMA AND T.TABNAME = S.TABNAME AND T.IID = S.IID and T.INDEX_SCANS = 0" TABSCHEMA TABNAME IDXSCHEMA IDXNAME PAGE_ALLOCATIONS UNIQUERULE INDEXTYPE DB2INST1 T1 DB2INST1 I1 0 D REG (3)DB2 9.7 还提供了一个方法用来查看索引最后使用的时间。9.7 在 syscat.tables, syscat.indexes 和 syscat.packages 视图中增加了一个字段 lastused,用来表示此对象最后一次被访 问的时间。01/01/0001 表示此对象从来没有被访问过,因此可用其作为过滤条件: db2inst1@dpf1:~> db2 "select indname, lastused from syscat.indexes where lastused='01/01/0001' " 16.2.2 排序 在 DB2 数据库系统监控中,经常会遇到 CPU 资源使用过高问题,造成此类问题的原因很 多,但最主要的原因有两个,第一是过多的逻辑 I/O 读取,第二就是过多的排序(Sort),这两 个因素有时也称为 CPU 的两大杀手。所谓过多的逻辑 I/O 读取,通常指发生在缓冲池中的表 扫描。 排序是指对某些数据按照某个(或某些)字段从大到小或从小到大排列的过程。比如,按 照学生成绩从高到低排序,根据姓氏字母顺序排序等。 在 DB2 数据库中,哪些操作会引起表数据排序呢?熟悉 SQL 的朋友立即会想到 Order By。 没错,如果排序字段上没有索引,或者 DB2 认为索引的开销比表扫描更大时,DB2 就会对数据 进行排序。 除 Order By 之外,在以下操作中也可能引起排序:  对于包含 DISTINCT、GROUP BY、 HAVING、INTERSECT、EXCEPT、UNION 等操作的 SQL 语句,如果没有索引满足所取的行的顺序要求,或者优化器认为排序 的代价低于索引扫描,就需要进行排序。  对于包含 Max/Min/Sum/Cube/Rollup/Rank 等聚集函数,DB2 会把语句重写为一个使 用排序的嵌套子查询。  对于 Reorg, Create Index 等操作,需要对表数据进行排序才能完成。  对于查询计划中的 Dynamic Bitmap Index ANDing(IXAND)、Hash Join(HSJOIN) 等操作的生成的 Hash 表会放在 SORTHEAP 中。 通常情况下,大量的排序会对性能造成极大影响。比如,排序会导致很高的 CPU 使用时 464 DB2 数据库管理最佳实践 间;增加 SQL 执行时间;增加锁超时和死锁发生的几率;排序会严重消耗有限的内存空间;排 序溢出会引起临时表空间的频繁 I/O 等。在实际运维过程中,经常发现很多人对排序的监控和 调优缺乏理论和实践经验,遇到相关问题时不知所措。本节首先介绍排序的原理,然后通过一 个实例介绍排序的监控和诊断方法,最后提出减少排序的建议,希望对大家有所帮助。 1. 排序的原理 正常情况下,DB2 排序发生在内存中,这块内存叫做排序堆,即 SORTHEAP。当需要排序 的数据超出 SORTHEAP 大小限制时,就会发生排序溢出。溢出的数据会写到临时表中,这会产 生更多的 I/O,因此对性能会有较大影响。 通过图 16.6 可知,DB2 的内存集包括实例内存集、数据库共享内存集、应用程序内存集和 代理私有内存集等。内存池是从内存集中分配的。根据排序内存池的分配来源,分为私有排序 和共享排序。私有排序是从代理私有内存集中分配的,而共享排序从数据库共享内存集中分配。 图 16.6 DB2 的内存集 DB2 选择私有排序还是共享排序,是由 3 个排序参数决定的:SHEAPTHRES、 SHEAPTHRES_SHR 和 SORTHEAP。在不同配置组合下,DB2 对排序内存的分配使用方式也大 不相同,不同版本的 DB2 对排序内存的使用也存在较大差异。下面我们看一下这 3 个排序参数 的用法。  SORTHEAP: 数据库配置参数,指定为每个排序分配的最大内存大小,实际使用 的大小是由优化器来决定的。如果表的统计信息不准确,会导致优化器对要使用的 排序内存的大小估算不准,有可能分配比实际需要少的内存,导致不必要的排序溢 出。这就提醒我们要经常使用 runstats 更新统计信息。 第 16 章 优化器与性能调优 465  SHEAPTHRES_SHR: 数据库配置参数,该参数指定了数据库共享内存集中共享排 序内存池的大小,它限制了该数据库上的所有应用能达到的共享排序内存上限。在 DB2 8 版本中,这个值是硬限制,当达到此限制后,请求排序的新应用会收到 SQL0955(reason code 2)错误。从 DB2 9.1 起,这个参数改为软限制,超过 SHEAPTHRES_SHR 的共享排序请求可以从数据共享内存集的溢出区(Database overflow Buffer)获得。  SHEAPTHRES:实例配置参数,指定为本实例中所有私有排序分配的内存上限的软 限制。当私有排序分配的内存达到了此限制,新请求的私有排序的内存大小分配将 会小于 sortheap 配置的大小。 简单地说,每个排序是从 SORTHEAP 中分配的。如果使用私有排序,那么允许分配的排序 内存大小不能超过 SHEAPTHRES 实例参数;如果使用共享排序,允许分配的排序内存大小不 能超过 SHEAPTHRES_SHR 数据库参数。 那么如何配置 DB2 使用共享排序还是私有排序呢?从 DB2 9.1 开始,如果将 SHEAPTHRES 实例参数设置为 0,DB2 将使用共享排序,即排序内存从共享排序内存池中分配,排序内存的 最大限制由 SHEAPTHRES_SHR 决定。 2. 排序的监控 DB2 排序可以通过 SNAPSHOT 快照监控,快照监控结果提供了很多关于排序的信息,如总 的排序次数、排序时间、排序溢出数量等。以下是数据库快照排序监控指标: inst20@db2server:/data1/sh> db2 get snapshot for database on perfdb | more Database Snapshot Total Private Sort heap allocated = 9553 Total Shared Sort heap allocated = 0 Shared Sort heap high water mark = 0 Total sorts = 12936047 Total sort time (ms) = 19098348 Sort overflows = 83950 Active sorts = 1 … … Commit statements attempted = 1130957 Rollback statements attempted = 519  Total sorts:表示发生的总排序次数。  Total sort time(ms):表示发生的总排序时间。  Sort overflows:表示发生的排序溢出次数。  Active sorts:表示监控时正在进行的排序次数。 前 3 个指标是自数据库启动以来的统计值,最后一个指标是当前值。几个关键的指标如下: 466 DB2 数据库管理最佳实践  Sort overflows/Total sorts * 100%表示排序溢出百分比,通常情况下,该值应该小于 3。如果大于 3,表示溢出的比例太高,需要优化。  Total sorts time(ms)/Total sorts 表示每次排序花费的时间(毫秒),对于交易系统来 说,该值最好小于 50ms。  Total sorts time(ms)/(Commit statements attempted + Rollback statements attempted) 表示每个事务花费在排序上的时间。一个事务响应时间包含很多方面,比如读/写时 间、锁时间、排序时间、CPU 时间等。排序时间越少,最终用户的响应时间也越快, 占用的 CPU 资源也越少。 上例中,在 4 天的时间内共计发生了 12936047 次排序,排序溢出比为 83950/12936047=0.6%, 每个事务需要花费的排序时间为 19098348/(1130957+519) = 16ms。 除了数据库快照,应用程序快照、动态 SQL 快照也包含一些排序信息。这对于我们定位排 序根源有很大帮助。以下是动态 SQL 语句快照片段,该语句平均每次执行需要 4 次排序,而且 3 次发生溢出,根据这些信息,该语句很明显需要优化。 Number of executions = 36 Number of compilations = 1 Worst preparation time (ms) = 18 Best preparation time (ms) = 15 Internal rows deleted = 0 Internal rows inserted = 0 Rows read = 57462636 Internal rows updated = 0 Rows written = 30062371 Statement sorts = 144 Statement sort overflows = 108 Total sort time = 145700 Buffer pool data logical reads = 19546362 Buffer pool data physical reads = 8 Buffer pool temporary data logical reads = 4141609 Buffer pool temporary data physical reads = 250 Buffer pool index logical reads = 35513607 Buffer pool index physical reads = 1096 Buffer pool temporary index logical reads = 0 Buffer pool temporary index physical reads = 0 Total execution time (sec.ms) = 355.390643 Total user cpu time (sec.ms) = 164.663270 Total system cpu time (sec.ms) = 1.529099 Statement text = select * from … (略) 3. 排序的优化 以上我们介绍了排序的监控,当发现排序的监控指标超出期望值时,就需要优化。影响排 序的因素主要包括排序的行数、排序列的宽度和 ORDER BY 的列数等,排序的行数乘以排序列 的宽度决定了一次排序占用的空间,因此,应该尽量减少排序的行数(很多情况下,排序列无法 第 16 章 优化器与性能调优 467 更改)。以下介绍一些常用的排序优化方法。 1)参数调优 从 DB2 9.1 开始,SHEAPTHRES_SHR 实例参数的默认值为 0,这表示排序内存从数据库共 享内存区分配,排序总量由 SHEAPTHRES 数据库参数控制。 那么,如何设置 SORTHEAP 和 SHEAPTHRES 的大小呢? 首先 SORTHEAP 是每次排序能够分配的最大内存空间,当排序的数值超过该值时,就要发 生排序溢出,SORTHEAP 的默认大小是 256 页,即 1MB。假定,要排序的数据每行是 1KB 大 小,那么每个 SORTHEAP 最多允许排序 1M/1K=1024 行,超出 1024 行数据,排序将会溢出。 inst20@db2server:/data1/sh> db2 get db cfg for sample | grep -i sort Sort heap thres for shared sorts (4KB) (SHEAPTHRES_SHR) = 5000 Sort list heap (4KB) (SORTHEAP) = 256 对于数据仓库系统来说,一般行记录很长,排序的行数也很多,这是可考虑适当增大 SORTHEAP 大小,但不要扩得太大。 SHEAPTHRES_SHR 参数,用来限定所有排序可以占用的内存空间。该值的大小由两个指 标来决定,一个是 SORTHEAP,另外一个是同时排序的个数。但是,同时排序的个数很难估计, 一般采用并发数来估计。以下的监控结果可知,当时正在并发的应用数量大概是 10 个。 db2inst1@ibmswg01:~/> db2 get snapshot for database on prefdb Appls. executing in db manager currently = 10 考虑到单个应用可能需要进行多次排序,可设置: SHEAPTHRES_SHR = SORTHEAP * 应用并发数 * 2 在 DB2 9.1 及以上版本,DB2 提供了自动调优功能(STMM),好处是 DB2 根据工作负载 和资源情况,决定一些内存的分配和回收,比如缓冲池、排序堆、锁列表和 Package cache 大小, 当需要排序时,可以从别的内存区借用,当不需要时归回,充分利用内存使用。对于运行稳定 的系统,可考虑启用 STMM,设置这两个参数为 automatic 自动值。 inst20@db2server:/data1/sh> db2 update db cfg for perfdb using SORTHEAP automatic inst20@db2server:/data1/sh> db2 update db cfg for perfdb using SHEAPTHRES_SHR automatic 2)逻辑/物理设计优化 调整参数有些情况下能解决问题,但可能会隐藏问题,因为调参只是尽量减少排序溢出的 几率,并不能减少排序的次数。因此,作为一个优秀的 DBA,需要挖掘排序的根源,并给出解 决方案。根源是什么?尽管 Reorg、建索引等操作需要排序,但几乎 99%的概率是 SQL 语句引 起的。在 SQL 语句无法更改的情况下,作为 DBA,能够做的就是在物理/逻辑设计上做优化。 那么如何找到哪些语句发生了大量排序呢?答案就是我们前面介绍过的动态 SQL 快照和应 468 DB2 数据库管理最佳实践 用快照监控。 当找到 SQL 语句后,就可以通过一些物理设计来优化 SQL,如索引、物化视图(MQT)等。 当然,一些影响优化器的运维工作也必不可少,如 runstats、reorg、rebind 等操作。 索引是减少排序的利器,因为索引本身已经是经过排序的数状结构。创建合适的索引会大 大减少、甚至避免排序。当 SQL 语句中出现以下操作时,可以考虑在相应的字段上创建索引:  Order By/Group By 操作,如: SELECT a,b,c FROM tab1 ORDER BY a,b SELECT a,b,c FROM tab1 WHERE a=100 ORDER BY b SELECT a,b,count(*),sum(*) FROM tab1 group by a,b 这时可考虑在 tab1(a,b)上建索引。  Distinct 操作,如: SELECT distinct a FROM tab1 可考虑在 tab1(a)上建索引,对于 distinct 的查询可避免排序。  Hash Join 操作需要在 SORTHEAP 中建立哈希表,对于发生大量 Hash Join 的表,可 考虑建立索引。  Create index idx_1 on tab1(a) allow reverse scan 建索引时考虑用 Allow Reverse Scans 进行索引值逆向扫描,这也是 DB2 9.1 的默认选项。 3)调优 SQL 语句 如果通过前两步仍然不能解决排序问题,那就需要对 SQL 语句本身进行逻辑调整和修改。 比如:  能否在 SQL 语句中省略 ORDER BY, DISTINCT 等操作,如果无法省略,能否尽量 减少排序的行数或列数,比如通过增加过滤条件等。  如果 UNION All 能满足需求,就避免用 UNION,因为 UNION All 不需要排序。  写优化的 SQL 语句,比如不要在排序字段上使用函数(因为函数无法走索引)。 4. 排序问题的诊断分析 下面我们通过一个详细的例子说明对排序问题的诊断分析过程。 1)搭建实验环境 以下实验在 DB2 9.5 for Linux 下运行。首先创建一个 SORTDB 数据库,然后创建一个 EMPLOYEE 员工表,并插入 100 万行数据,其中 DEPT_ID 字段是随机产生的 1000 个部门编号, 所有员工数据均匀分布在 1000 个部门里,每个部门大概包含 100 万/1000=1000 个员工。 第 16 章 优化器与性能调优 469 脚本如下(以下命令存到一个脚本文件 create.ddl): --创建测试 database create db sortdb@ --更改日志参数,否则会在插入数据时出现 log full update db cfg for sortdb using logfilsiz 2000 @ update db cfg for sortdb using logprimary 20@ update db cfg for sortdb using logsecond 30@ --日志参数的更改需要断掉连接才会生效 force applications all@ --连接数据库 connect to sortdb@ --创建 bufferpool create bufferpool bp4k size 10000@ --创建 automatic storaged tablespace create tablespace emp_dms bufferpool bp4k@ --创建 table create table db2inst1.employee ( emp_id int not null primary key, name char(20), dept_id int, salary decimal, address char(30), remark char(40) ) in emp_dms @ --向表里插入 100 万条数据,其中 dept_id 的范围为 0-1000,其余几个 char 字段值都是根据 count 值产 生 begin atomic declare count int default 1; while (count <1000000) do insert into db2inst1.employee values( count, '--emp' concat char(count), ceiling( (rand()*1000) ), 20000.50, '-----address' concat char(count) concat '---', '------remark' concat char(count) concat '----' ); set count=count+1; end while; end 470 DB2 数据库管理最佳实践 @ --对表做 runstats runstats on table db2inst1.employee @ 执行 db2 -td@ -f create.ddl 命令创建脚本。 然后设置排序参数,sortheap=250,sheapthresshr=1000,并打开监控开关。 inst20@db2server:/backup> db2 update db cfg for sortdb using sheapthres_shr 10000 sortheap 250 DB20000I The UPDATE DATABASE CONFIGURATION command completed successfully. inst20@db2server:/backup> db2 update dbm cfg using SHEAPTHRES 0 DB20000I The UPDATE DATABASE MANAGER CONFIGURATION command completed successfully. inst20@db2server:/backup> db2 UPDATE DBM CFG USING DFT_MON_SORT ON DB20000I The UPDATE DATABASE MANAGER CONFIGURATION command completed successfully. inst20@db2server:/backup> db2stop force 06/03/2011 07:33:04 0 0 SQL1064N DB2STOP processing was successful. inst20@db2server:/backup> db2start 06/03/2011 07:33:15 0 0 SQL1063N DB2START processing was successful. 然后查询 dept_id<100 的记录,并按 dept_id 排序,大概会有 9 万 9 千行记录返回。 inst20@db2server:/backup> db2 "SELECT dept_id, name FROM db2inst1.employee WHERE dept_id<100 ORDER BY dept_id" | grep selected 99011 record(s) selected. 2)对排序问题的诊断和分析 现在我们要通过数据库监控,查找排序问题的根源。 ①数据库监控分析 通过监控结果可知,在共享内存区发生了一次排序,并且发生了排序溢出,排序时间是 204ms。以下监控是 SQL 语句完成后执行的,所以 Total Shared Sort heap allocated 的结果是 0, 否则会显示当前分配的排序堆页数。因为是共享排序,所有与私有排序相关的参数都是 0。 inst20@db2server:/backup> db2 get snapshot for db on sortdb | more Total Private Sort heap allocated = 0 Total Shared Sort heap allocated = 0 Shared Sort heap high water mark = 250 Post threshold sorts (shared memory) = 0 Total sorts = 1 Total sort time (ms) = 206 Sort overflows = 1 Active sorts = 0 然后监控缓冲池临时数据和临时索引的逻辑读/写页数,当排序溢出时,会溢出到临时表空 第 16 章 优化器与性能调优 471 间中。如果发现临时数据读写值较高,那么很可能是 SORT 引起的。 inst20@db2server:/backup> db2 get snapshot for database on sortdb|grep -i "temporary data" Buffer pool temporary data logical reads = 1792 Buffer pool temporary data physical reads = 31 Buffer pool temporary index logical reads = 0 Buffer pool temporary index physical reads = 0 ② 监控 SQL 语句 接着根据动态 SQL 语句快照,找到排序多、溢出多的 SQL 语句。我们会发现,该语句执行 了一次,执行时间花费了 7.6 秒,Rows Read=1099018, Rows Written= 99011。排序发生了一次, 溢出一次。可能有人有会比较奇怪,为什么 select 语句会产生 rows written 呢?这就是因为排序 溢出写到临时表空间,产生了写 I/O,溢出的行数为 99011,正对应前面的查询结果。 Number of executions = 1 Number of compilations = 1 Worst preparation time (ms) = 134 Best preparation time (ms) = 134 Internal rows deleted = 0 Internal rows inserted = 0 Rows read = 1099018 Internal rows updated = 0 Rows written = 99011 Statement sorts = 1 Statement sort overflows = 1 Total sort time = 204 Buffer pool data logical reads = 30561 Buffer pool data physical reads = 2367 Buffer pool temporary data logical reads = 1792 Buffer pool temporary data physical reads = 1 Buffer pool index logical reads = 35 Buffer pool index physical reads = 27 Buffer pool temporary index logical reads = 0 Buffer pool temporary index physical reads = 0 Buffer pool xda logical reads = 0 Buffer pool xda physical reads = 0 Buffer pool temporary xda logical reads = 0 Buffer pool temporary xda physical reads = 0 Total execution time (sec.microsec)= 7.612814 Total user cpu time (sec.microsec) = 0.938412 Total system cpu time (sec.microsec)= 0.000000 Total statistic fabrication time (milliseconds) = 0 Total synchronous runstats time (milliseconds) = 0 Statement text = SELECT dept_id, name FROM db2inst1.employee WHERE dept_id<100 ORDER BY dept_id 当然,也可以使用 SYSIBMADM.SNAPDYN_SQL 管理视图找到排序最多的动态 SQL 语句。 472 DB2 数据库管理最佳实践 ③SQL 语句分析 通过 explain 工具生成 SQL 语句的执行计划,并通过 db2exfmt 进行格式化。 inst20@db2server:~/sqllib/misc> db2 -tvf EXPLAIN.DDL inst20@db2server:~> more query.sql SELECT dept_id, name FROM db2inst1.employee WHERE dept_id<100 ORDER BY dept_id; inst20@db2server:~> db2 set current explain mode explain DB20000I The SQL command completed successfully. inst20@db2server:~> db2 -tvf query.sql SELECT dept_id, name FROM db2inst1.employee WHERE dept_id<100 ORDER BY dept_id SQL0217W The statement was not executed as only Explain information requests are being processed. SQLSTATE=01604 inst20@db2server:~> db2exfmt -1 -d sortdb -o query_exfmt.out 然后打开 query_exfmt.out 文件,计划如下: Access Plan: ----------- Total Cost: 36010 Query Degree: 1 Rows RETURN ( 1) Cost I/O | 98999.9 TBSCAN ( 2) 36010 30763 | 98999.9 SORT ( 3) 35270.1 30094.5 | 98999.9 TBSCAN ( 4) 30134.7 29426 | 999999 第 16 章 优化器与性能调优 473 TABLE: DB2INST1 EMPLOYEE Q1 通过以上结果可知,在对表 EMPLOYEE 进行 TBSCAN 后,有一次 SORT 操作,然后对 SORT 的结果再次 TBSCAN。观察 TBSCAN(4),SORT(3)和 TBSCAN(2)这 3 个操作符的累计 I/O 次数, 可以看出 I/O 在增长,再看 SORT 操作符的具体信息: 3) SORT : (Sort) Cumulative Total Cost: 35270.1 Cumulative CPU Cost: 2.59546e+09 Cumulative I/O Cost: 30094.5 Cumulative Re-Total Cost: 0 Cumulative Re-CPU Cost: 0 Cumulative Re-I/O Cost: 668.5 Cumulative First Row Cost: 35270.1 Estimated Bufferpool Buffers: 30318 Arguments: --------- DUPLWARN: (Duplicates Warning flag) FALSE NUMROWS : (Estimated number of rows) 99000 ROWWIDTH: (Estimated width of rows) 32 SORTKEY : (Sort Key column) 1: Q1.DEPT_ID(A) SPILLED : (Pages spilled to bufferpool or disk) 892 TEMPSIZE: (Temporary Table Page Size) 4096 UNIQUE : (Uniqueness required flag) FALSE Input Streams: ------------- 2) From Operator #4 Estimated number of rows: 98999.9 Number of columns: 2 Subquery predicate ID: Not Applicable Column Names: ------------ +Q1.NAME+Q1.DEPT_ID Output Streams: -------------- 3) To Operator #2 474 DB2 数据库管理最佳实践 Estimated number of rows: 98999.9 Number of columns: 2 Subquery predicate ID: Not Applicable Column Names: ------------ +Q1.DEPT_ID(A)+Q1.NAME 通过 SORT(3)排序操作,预计 892 页发生了排序溢出。 SPILLED : (Pages spilled to bufferpool or disk) 892 DB2 优化器预测的排序行数是 99000 行,每行宽度是 32 个字节,所以需要的排序空间至少 是 99000*32/1024=3093k=3.1M 左 右, 而 SORTHEAP 设定的排序内存是 250*4K=1M,所以发生 了排序溢出。 3)排序溢出处理 发生排序溢出时,第一个要考虑的就是调整 SORTHEAP 参数,本例中我们将 SORTHEAP 参数值由 250 页(1M)增加到 2500 页(10M)。为便于对比,我们把以前的监控结果清零。 inst20@db2server:~> db2 update db cfg for sortdb using sortheap 2500 DB20000I The UPDATE DATABASE CONFIGURATION command completed successfully. inst20@db2server:~> inst20@db2server:~> inst20@db2server:~> db2 reset monitor all DB20000I The RESET MONITOR command completed successfully. 然后再次执行 SQL 语句,并观察结果。没有发现排序溢出,并且排序时间是 162ms,小于 之前的 206ms。 [db2inst1@ibmswg01]$ db2 get snapshot for db on sortdb | more Total Private Sort heap allocated = 0 Total Shared Sort heap allocated = 0 Shared Sort heap high water mark = 2322 Post threshold sorts (shared memory) = 0 Total sorts = 1 Total sort time (ms) = 162 Sort overflows = 0 Active sorts = 0 再次查看 SQL 语句的执行计划, Access Plan: ----------- Total Cost: 30201.9 Query Degree: 1 第 16 章 优化器与性能调优 475 Rows RETURN ( 1) Cost I/O | 98999.9 TBSCAN ( 2) 30201.9 29426 | 98999.9 SORT ( 3) 30201.9 29426 | 98999.9 TBSCAN ( 4) 30134.7 29426 | 999999 TABLE: DB2INST1 EMPLOYEE Q1 3) SORT : (Sort) Cumulative Total Cost: 30201.9 Cumulative CPU Cost: 2.55223e+09 Cumulative I/O Cost: 29426 Cumulative Re-Total Cost: 30134.6 Cumulative Re-CPU Cost: 2.34872e+09 Cumulative Re-I/O Cost: 0 Cumulative First Row Cost: 30201.9 Estimated Bufferpool Buffers: 29426 Arguments: --------- DUPLWARN: (Duplicates Warning flag) FALSE NUMROWS : (Estimated number of rows) 99000 ROWWIDTH: (Estimated width of rows) 32 SORTKEY : (Sort Key column) 1: Q1.DEPT_ID(A) TEMPSIZE: (Temporary Table Page Size) 476 DB2 数据库管理最佳实践 4096 UNIQUE : (Uniqueness required flag) FALSE Input Streams: ------------- 2) From Operator #4 Estimated number of rows: 98999.9 Number of columns: 2 Subquery predicate ID: Not Applicable Column Names: ------------ +Q1.NAME+Q1.DEPT_ID Output Streams: -------------- 3) To Operator #2 Estimated number of rows: 98999.9 Number of columns: 2 Subquery predicate ID: Not Applicable Column Names: ------------ +Q1.DEPT_ID(A)+Q1.NAME 对比前面的访问计划,可以看到 TBSCAN(4), SORT(3)和 TBSCAN(2)这三个操作符的 累计 I/O 次数一直保持不变,SORT(3)操作中没有溢出。整个语句的 Cost 由 36010 降为 30201.9。 查看 SQL 语句执行时间,由 7.6 秒将为 6.97 秒。 4)消除排序 尽管调整参数后,排序溢出消失,但速度仍然较慢。接下来要做的是如何消除排序,采用 的方法是创建索引。 DB2 提供了一个很有效的索引推荐工具,db2advis。 db2advis -d sortdb -i advisesql.txt -n db2inst1 -o newindex.ddl 从 newindex.ddl 可以看到,推荐了如下索引: -- LIST OF RECOMMENDED INDEXES -- =========================== -- index[1], 41.149MB CREATE INDEX "DB2INST1"."IDX_EMP_DEPTID" ON "DB2INST1"."EMPLOYEE" ("DEPT_ID" ASC, "NAME" DESC) ALLOW REVERSE SCANS ; COMMIT WORK ; 第 16 章 优化器与性能调优 477 RUNSTATS ON TABLE "DB2INST1"."EMPLOYEE" FOR INDEX "DB2INST1"." IDX_EMP_DEPTID " ; COMMIT WORK ; 创建索引: db2 –tvf newindex.ddl 再次执行 SQL 语句,观察生成的执行计划,这是一个非常的高效的索引只读计划,没有任 何排序。整个语句的 Cost 也大幅降为 1147.64,SQL 语句的执行时间变为 3.9 秒。 Access Plan: ----------- Total Cost: 1164.35 Query Degree: 1 Rows RETURN ( 1) Cost I/O | 98999.9 IXSCAN ( 2) 1164.35 1043.44 | 999999 INDEX: DB2INST1 IDX_EMP_DEPTID Q1 16.3 KPI 在性能专题的最后阶段,我们要讲的就是关键性能指标。就像前文中所提高的,关键性能 指标在很多时候是一种快速定位“可能的瓶颈”的重要手段。当一个富有经验的 DBA 观察一个 系统时,他们可以很快地判断出什么地方可能是出现问题的入手方向。他们凭借的是什么?直 觉、方法,再加上早已经深深印在脑海中的一些性能指标。 本节我们将从数据库、实例和操作系统等方面介绍一些重要的 KPI,希望对大家有所帮助。 16.3.1 缓冲池命中率(bufferpool hit ratio) 来源:数据库快照 478 DB2 数据库管理最佳实践 公式:(逻辑读—物理读)/逻辑读 指标:优秀>95%,良好>80% 这个也许是人们最熟悉的 DB2 性能指标之一。当系统性能下降的时候,甚至非专职 DBA 也会跳出来问一句:“数据库缓冲区命中率怎么样?” 没错,命中率是判定物理 I/O 频繁程度的一个最重要的指标之一,而且在很多优化不足的 系统中是一个最容易出现的问题。 从内存中访问数据可能仅需纳秒级,而从磁盘访问数据则需数毫秒。DB2 对数据的获取是 通过缓冲池,如果数据已经缓存到缓冲池,就可通过缓冲池直接获取,这称为缓冲池命中(hit); 相反,如果数据不在缓冲池,则需要从磁盘读到缓冲池,这称为缓冲池未命中(hit miss)。 因此,命中率越高,代表着读取同样的数据时需要的 I/O 越少,性能就越好,因此广大 DBA 不惜将大部分内存分配给缓冲池以提高命中率。 对于 OLTP,一般要求数据 bufferpool 达到最少 90%的命中率。对于 OLAP,由于经常需要进 行表扫描,所以不必追求很高的命中率,但临时数据和索引的命中率需要关注,因为仓库系统 中一些复杂的 SQL 语句需要进行大量排序或哈希关联操作,而排序和哈希关联可能需要在临时 表空间内完成。 可以通过数据库快照获取缓冲池相关的信息,几种常见的命中率如下。 数据库整体缓冲池命中率: (1 -(Buffer pool data physical reads + Buffer pool temporary data physical reads + Buffer pool index physical reads + Buffer pool temporary index physical reads) / (Buffer pool data logical reads + Buf-fer pool temporary data logical reads + Buffer pool index logical reads + Buffer pool temporary index logical reads) ) * 100 数据缓冲池命中率: (1 -(Buffer pool data physical reads ) / (Buffer pool data logical reads) ) * 100 索引缓冲池命中率: (1 -(Buffer pool index physical reads ) / (Buffer pool data index reads) ) * 100 临时空间缓冲池命中率: (1 -(Buffer pool temporary data physical reads + Buffer pool temporary index physical reads) / (Buf-fer pool temporary data logical reads + Buffer pool temporary index logical reads) ) * 100 有一个问题需要格外注意,缓冲池命中率可能会蒙蔽我们的眼睛,我们经常发现很多系统 命中率很高,但仍然存在严重的性能问题。 举例来说,有一张表 T1,大小为 200M,包含 100 万行数据。该表对应的表空间缓冲池大 第 16 章 优化器与性能调优 479 小为 1G。在 T1 表有一条 SQL 语句,带过滤条件,但过滤字段上没有索引,满足此查询条件的 行数只有 2 行。 由于没有索引,DB2 通过表扫描将 100 万行数据全都缓存到缓冲池,每次查询时 DB2 在缓 冲池里进行表扫描。由于每次都是逻辑读,缓冲池命中率高达 100%。但为了查找 2 行数据,每 次都要在内存中对 100 万行数据进行全表扫描,效率是非常低的。 感兴趣的读者,可以创建一张表,在里面插入 1 万条记录,然后执行“select * from t1 where col2=1000” 10 次,观察快照的结果,会发现只有第 1 次访问时进行了物理读,其余的 9 次访 问都是从缓冲池中直接获取。 这样,缓冲池命中率很高,而且随着访问频度的增加,命中率会更高。但是从逻辑上就可 以推断出,所有的访问都是扫描全表,并且 100 万行中的绝大部分都是无用的数据,真正选择 的只有两行。所以,我们想要提升性能,不能从减少 I/O 上入手,而是应该从提高读取效率上 着手: Buffer pool data logical reads = 2782 Buffer pool data physical reads = 274 Commit statements attempted = 10 Rollback statements attempted = 0 Rows selected = 10 Rows read = 100004 在 col2 上建索引,然后观察结果,发现缓冲池逻辑读大大降低: db2inst1@dpf1:~> db2 "create index i1 on t1 (col2 ) " db2inst1@dpf1:~> db2 "runstats on table db2inst1.t1 with distribution and detailed indexes all " db2inst1@dpf1:~> db2 reset monitor all db2inst1@dpf1:~> db2 "select * from t1 where col2=1000 " 连续执行 10 次 db2inst1@dpf1:~> db2 get snapshot for db on db1 Buffer pool data logical reads = 22 Buffer pool data physical reads = 5 Buffer pool index logical reads = 33 Buffer pool index physical reads = 6 Commit statements attempted = 10 Rollback statements attempted = 0 Rows selected = 10 Rows read = 14 因此,单纯的缓冲池命中率的分析并不能涵盖所有的性能问题。从某种意义上来说,命中 率只能代表性能问题中很小的一部分,而且在某些情况下,该数值过高反而可能代表一些潜在 的性能问题。 480 DB2 数据库管理最佳实践 16.3.2 有效索引读 来源:数据库快照 公式:行读取/行选择(rows read/rows selected) 指标:OLTP≤5,SAP 系统≤3 这个指标就是我们刚刚在命中率部分讨论的,到底有多少行是属于有效的读取。为了获取 一行数据,DB2 需要验证或读取多少行? 如果 DB2 不能根据有效的索引过滤结果集,那么可能需要扫描大量的数据页才能找到满足 条件的数据。 rows read 是读的行数,rows selected 是返回的结果集,公式如下: IREF(index read efficiency) = Rows read / Rows Selected (Fetched) 假如一张员工表有 10 万行数据,根据员工号查询员工信息,假定员工号唯一并且没有索引, 那么 rows read 是 10 万行,rows selected 是一行,IREF=100000/1=100000,即为了查找一行数据, 需要扫描 10 万行数据,这个效率是非常低的。如果在员工号字段上有唯一性索引,则只需读取 一行数据就可获取值,IREF=1/1=1,效率非常高。 在 DB2 9.1 以上版本中,可通过如下语句实现: db2inst1@dpf1:~> db2 "select Rows_read / (Rows_Selected+1) as IREF from sysibmadm.snapdb" IREF -------------------- 5 IREF 当然越小越好,如果是 5,意味着找一行数据需要读取 5 行数据。 对于 OLTP 数据库,IREF 应当小于等于 5,如果远远大于这个数值,则意味着需要调优。 对于 OLAP 仓库系统,IREF 一般相对较高。值越高,则消耗的 CPU 资源越高,性能越差, 因此对此值的监控仍然必要。当 IREF 比较高时,需要对 SQL 语句进行分析,找出 IREF 比较高 的 SQL 语句,然后进行物理设计,比如设计优良的索引等,降低 IREF。 16.3.3 包缓存命中率 (package cache hit ratio) 来源:数据库快照 公式:1-包缓存插入数量/包缓存读取数量(1–Package Cache Insert/Package Cache Lookup) 第 16 章 优化器与性能调优 481 指标:1,或者能够长时间保持接近 1 的稳定数值 该指标表示有多少查询语句可以直接在包缓存中找到。 当一条查询被请求的时候,数据库在将其编译之前,首先要从包缓存中查找有没有已经被 编译好的包可以直接运行。如果该包已经存在,那么该 SQL 可以直接被运行而不用再次编译(不 过取决于 REOPT 等参数,在《DB2 数据库高级管理》中的优化器部分讨论)。 因此,我们的目标是,如果应用程序在运行一段时间后,绝大部分的语句都已经被缓存在 包缓存中,那么我们可以节省很多 SQL 编译的时间与 CPU 消耗。 不过,正像大家所考虑的一样,在有些系统中该值不可能固定在很高的数值。有些系统在 设计之初,并没有对 SQL 使用绑定变量。因此,每一个使用不同参数的 SQL,譬如 “select * from t1 where c1=1”与“select * from t1 where c1=2”会被当做两个完全不同的语句编译(这是由编译器 的设计决定的,具体的原因及如何在不修改应用的前提下绕过这个问题的方法请参考优化器部 分的讨 论) 。 在这种情况下,系统几乎没有两个查询是完全一样的,这样就会导致每一个查询都 要向包缓存中插入编译后的包,而且当缓存满了以后会替换缓存中显存的包,导致性能的降低。 为了减少编译时间,建议在使用动态程序(如 java 等)开发时指定 prepare statement,这样就 只需编译一次。使用静态程序(嵌入 C 等)时,建议使用绑定变量。 16.3.4 平均结果集大小 来源:数据库快照 公式:行选择/执行 Select SQL 的次数 (Rows selected / Select SQL statements executed) 指标:OLTP≤10 平均结果集用来表示平均每条 Select SQL 语句返回的结果行数。对于 OLTP 系统来说,结 果集一般很小,通常小于 10;而对于 OLAP 系统,结果集一般很大。平均结果集的计算公式 如下: Avg_Result_Set = Rows selected / Select SQL statements executed 通过快照可以得出这两个元素的值。rows selected 表示返回的结果集,Select SQL statements executed 表示查询语句的执行次数: db2inst1@dpf1:~> db2 get snapshot for database on db1 … Select SQL statements executed = 2311 … Rows selected = 5531 DB2 9 以上的版本,可通过管理视图获得同样结果 db2inst1@dpf1:~> db2 "SELECT ROWS_SELECTED / (SELECT_SQL_STMTS+1) AS Avg_Result_Set 482 DB2 数据库管理最佳实践 from sysibmadm.snapdb" AVG_ROWS -------------------- 2 1 record(s) selected. 通过平均结果集大小,可以判断数据库类型。假设某一个数据库是 OLTP 交易型系统,但 发现平均结果集很大,这就说明应用程序有改善余地,比如可在 SQL 语句上做查询条件过滤并 降低返回的结果集大小,而不是将所有数据取出,然后在应用逻辑上过滤。 16.3.5 同步读取比例 来源:数据库快照 公式:异步读取/逻辑读取 100*(1-Asynchronous read/Logical read) 指标:OLTP≥90% DB2 的数据读取 I/O 主要包括异步读和同步读。如果采用高效的索引获取结果集时,DB2 将使用同步读 I/O 访问索引页和需要的数据页,当没有索引或物理设计不够有效时,DB2 将采 用异步读 I/O 扫描索引或数据页。异步 I/O 是 DB2 通过预取(prefetcher)进程/线程执行的,当 查询的结果集较大,DB2 认为顺序预取更有效率时就会触发预取请求,异步预取 I/O 的比例越 高,则表示获取的数据量越大,性能就可能越差,而更高的同步读则表示索引的高效。 通过快照监控结果可知,并没有任何元素表示同步 I/O 读的页数,但提供了异步 I/O 读和 所有 I/O 读的值,用所有 I/O 读减去异步 I/O 值就可以计算出同步 I/O 读的值,这个 KPI 的公 式如下: SRP = 100 - (((Asynchronous pool data page reads + Asynchronous pool index page reads) x 100) / (Buffer pool data physical reads + Buffer pool index physical reads)) 这个公式不仅适用于整个数据库异步读监控,也适用于表空间和缓冲池对象。 在 DB2 9.1 以上版本中,可通过如下语句实现: db2inst1@dpf1:~> db2 "select 100 - (((pool_async_data_reads + pool_async_index_reads) * 100 ) / (pool_data_p_reads + pool_index_p_reads + 1)) as SRP from sysibmadm.snapdb where DB_NAME = 'DB1' " SRP -------------------- 1 1 record(s) selected. 在 OLTP 系统中,如果同步读高于 90%,则表明数据库使用了高质量的同步 I/O 获取结果 集。如果低于 50%,则系统性能一般会较差,或者是响应时间慢,或者 CPU 利用率比较高,一 第 16 章 优化器与性能调优 483 般是由于没有进行很好的物理设计引起的,比如缺乏有效的索引。 在 OLAP 系统中,往往需要进行数据扫描,并返回大量的结果集,这时可能采用异步预取 I/O 效率更高。 16.3.6 数据、索引页清除 来源:数据库快照 公式:异步写入/总写入(async writes/total writes) 指标:≥95% 该指标代表着页面清除进程(线程)是否能够有效地将脏页在后台刷入磁盘。 由于缓冲池的大小是有限的,一般来说数据库不可能把所有的数据都放入内存。这时,哪 些数据需要驻留内存;哪些被更新的数据需要被写入磁盘,然后留出空间给其他数据,就是 DB2 缓冲池管理模块需要决定的。 当缓冲池中的被修改的数据页(脏页)与缓冲池总大小的比例超过一定阈值的时候 (chngpgs_thresh), DB2 就会触发后台的页面清除进程/线程,将被选择的页(victim pages)以 异步方式物理写入磁盘。 但是如果该清除机制触发得不够频繁,或者缓冲池太小使得系统无法有效地找到一个干净 的页面,DB2 就会选择一个脏页,然后将它写入磁盘,然后读取另外一个页面进入内存,这种 写入方式叫做同步写入。 可以想象,相对异步写入,同步写入会对数据的读取造成很大的性能问题。因而,该指标 的用途就是监测异步写入与总写入的比例: /home/db2inst1/temp $ db2 get snapshot for database on sample | grep -i "write" Buffer pool data writes = 542 Asynchronous pool data page writes = 323 Buffer pool index writes = 395 Asynchronous pool index page writes = 357 Buffer pool xda writes = 0 Asynchronous pool xda page writes = 0 Total buffer pool write time (milliseconds)= 659 Total elapsed asynchronous write time = 111 16.3.7 脏页偷取(dirty page steal) 来源:数据库快照 公式:脏页偷取触发次数(dirty page steal cleaner triggers) 484 DB2 数据库管理最佳实践 指标:非常低 正像上文中刚提到的,脏页偷取是一种对性能影响极大的操作。当系统中的脏页偷取过多 的时候,意味着 DBA 需要让页清除器工作得更加卖力。 那么如何做到这点呢?如果我们发现系统中的页清除器一直很空闲,则可以通过调节 softmax 与 chngpgs_thresh 来让它们忙起来。这两个参数都是控制何时触发页清除器的参数,其 中 softmax 是按照缓冲池中 MinbuffLSN 与当前 LSN 之间的差距来计算何时需要触发,而 chngpgs_thresh 则是计算缓冲池中脏页的数量与可用页面总数来进行计算。两者的作用同样都是 触发页清除器,只不过从不同的角度计算而已。 但是如果我们通过 db2pd -stack all 抓取的 stack 发现所有的页清除器一直非常繁忙,但是无 论如何刷新磁盘的速度也赶不上数据写入缓冲区的速度,这时就需要增加页清除器的数量了。 默认情况下 chngpgs_thresh 为 60,即缓冲池 60%的页为脏页时,会自动触发页清除器。 曾经欧洲的一家大型企业使用单个缓冲区高达 300GB,为了能够让系统更有效地将数据均 匀地写入磁盘,chngpgs_thresh 在调优之后被设定为 5%。 16.3.8 缓冲区读写 I/O 响应时间 来源:数据库快照 公式:缓冲区读取时间/物理读取次数 指标:1~10 毫秒 对数据和索引的读取都要先通过磁盘读到缓冲区,我们曾经提过,快照中的缓冲区读取时 间并不包括内存读取。也就是说,该数值仅仅包括物理读取的时间。 可通过以下公式计算出完成一个物理读需要的平均时间: Overall Average Read Time(ms) = (Total buffer pool read time (milliseconds) / (Buffer pool data physical reads + Buffer pool index physical reads + Buffer pool temporary data physical reads + Buffer pool temporary index physical reads)) 对于现代存储系统来说,平均物理读/写时间一般在 1~10 毫秒左右(取决于存储系统的性 能与缓存)。因此,如果我们监控的指标值超出此值,则需要结合操作系统 I/O 监控工具,调查 I/O 系统的瓶颈: db2 “select tbsp_name, (POOL_READ_TIME / (POOL_DATA_P_READS + POOL_INDEX_P_READS + POOL_TEMP_DATA_P_READS + POOL_TEMP_INDEX_P_READS + 1) as TSORMS from sysibmadm.snaptbsp order by TSORMS desc fetch first 10 rows only” 这个公式可找出读时间最慢的表空间,然后评估这个表空间的设计是否为最佳。 当发现该值过高时(譬如超过 20 毫秒),一般来说系统会明显感觉到性能下降。这时,为 第 16 章 优化器与性能调优 485 了能够直观地证明存储的性能问题,可以通过 UNIX/Linux 中的 dd 工具向容器所在的文件系统 (而不是容器文件)写入几千个数据页,计算平均写入时间(该数值为顺序写入),然后可以使 用同样的工具从容器文件中随机读取几千个数据页,计算平均读取时间。 db2 “select tbsp_name, (POOL_READ_TIME / (POOL_DATA_P_READS + POOL_INDEX_P_READS + POOL_TEMP_DATA_P_READS + POOL_TEMP_INDEX_P_READS + 1) as TSORMS from sysibmadm.snaptbsp order by TSORMS desc fetch first 10 rows only” 这个公式可找出读时间最慢的表空间,然后评估该表空间的设计是否最佳。 16.3.9 Direct I/O 时间 来源:数据库快照 公式:直接读取(写入)时间/直接读取(写入)次数 指标:1~10 毫秒 Direct I/O 是指直接从磁盘访问而不经过缓冲区的 I/O,主要针对 LONG/LOB 数据的访问。 当一列被定义为 LONG/LOB,那么该列的数据则不存储在表的数据页内。而在数据页记录 中该列所对应的位置,则是一些“指针”指向该数据真实所在的位置。 很多时候,这些大对象的数据可以非常大(比如视频),因此,对于 LONG 与 LOB 数据类 型,所有的读取都是直接 I/O,并不通过缓冲池。 DRIOMS - The average time (ms) required to complete a Direct Read DWIOMS - The average time (ms) required to complete a Direct Write DRIOMS = Direct reads elapsed time (ms) / Direct Reads DWIOMS = Direct write elapsed time (ms) / Direct Writes 16.3.10 直接 I/O 读取(写入)的次数 来源:数据库快照 公式:直接读取(写入)次数/缓冲区读取(写入)次数 指标:非常低 我们已经知道了什么是直接 I/O,而且我们知道了直接 I/O 需要物理读,而不能在缓冲池中 保存数据。因此,在应用程序的设计中我们希望尽量少用 Direct I/O。 通过对比 Direct I/O 与普通缓冲池读写的比例,我们可以知道到底有多少数据访问是读取大 数据的。 除了有限的特定应用外(比如在线视频网站),大部分的应用程序都应该尽量避免频繁的大 486 DB2 数据库管理最佳实践 数据访问。一般来说,如果一列数据可以被定义为普通列的话,就尽量不要使用大对象。 当一定要使用大对象定义某列的时候,在应用的某些操作中不是真正需要访问该列时,请 尽量在查询时不要指定大对象数据列,避免不必要的 Direct I/O。 16.3.11 编目缓冲区插入比例 来源:数据库快照 公式:编目缓冲区插入次数/编目缓冲区查询次数(catalog cache inserts/catalog cache lookups) 指标:0,或者在接近 0 的数值上长时间维持稳定 在系统的每一个分区中,database heap 中都会分配出一块空间用于缓冲编目表的信息。 由于在数据库的日常操作中,查询编目表是一个非常频繁的操作。譬如当用户从一个表读 取数据的时候,系统就要查询编目表,理解该表在什么表空间、应该如何访问等信息。因此为 了性能着想,DB2 在数据库栈内存中单独开辟了一块空间,用于存储编目信息。 但是如果用户有很多数据库对象,而该编目缓存的大小过小,则该内存无法容纳下所有的 信息。那么当新的信息来临时,就会把一些不常用的信息替换出去。 不过可以想象,如果该替换经常发生,那么每次当系统想要查找编目数据时,就要从编目 表空间中查找,这样会导致系统性能一定程度上的下降。 因此,一般建议将 catalogcache_sz 设置逐渐增大,直到系统中不再频繁出现编目缓冲区插 入的操作。 16.3.12 排序指标 1. 排序溢出比例 来源:数据库快照 公式:排序溢出次数/排序总数(sort overflow/total sorts) 指标:OLTP:0,或者在接近 0 的数值上长时间维持稳定 前面我们已经详细讨论了数据库的排序,因此原理在这里不再赘述。 排序溢出就是当排序内存不够时,数据需要使用临时空间进行排序。 一般来说,我们希望数据尽可能在内存中完成。当我们发现大量的排序溢出时,就要看排 序堆内存参数设置是否足够大。很多时候,排序的根源是由于 SQL 语句引起的。 第 16 章 优化器与性能调优 487 2. 平均排序时间 来源:数据库快照 公式:排序总时间/排序总数(total sort time/total sorts) 指标:远小于系统预期平均语句的执行时间 这个指标是理解平均每次排序所消耗的时间。 一般来说,一条语句的执行时间包括锁等待、数据读取时间(内存+I/O)加上排序时间(内 存读取和内存排序都属于 user CPU 时间)。因此用数据库中平均的排序时间对比平均语句返回 的速度,就可以大概估算出执行一条语句时有多少的时间用于排序。 作为 DBA,我们一般都希望该排序时间越少越好。如果该值过高,用户可以考虑优化查询 与添加索引,尽量减少排序的消耗。 3. 平均每条交易的排序次数 来源:数据库快照 公式:排序总数/交易总数(total sorts/(Commit statements attempted + Rollback statements attempted)) 指标:OLTP<5 对于 OLTP 应用来说,由于每条交易的短小精干的特性,我们需要尽可能减少每条交易所 需的排序数量。 大部分情况下,这种调优需要应用开发人员的配合,一方面在数据库中建立合适索引的同 时,另一方面优化应用程序逻辑,减少 SQL 所需要排序的次数。在典型的 OLTP 系统中,尽量 将每一条交易平均所需的排序数量维持在 5 以下。 16.3.13 基于事务的指标度量 通过监控事务负载,我们可以了解业务的特点,比如每个事务包含多少条 select 语句、多少 DML 语句(insert、update、delete),每个事务需要的排序次数、每个事务需要读取的行数、每 个事务返回的行数、每个事务需要的逻辑 bufferpool I/O。如果能够把每个事务的 cost 降低,那 将会对性能有很大个改善。让我们一起来看下这些与事务相关的一些度量指标。 1. 总的事务数量 来源:数据库快照 公式:提交事务 + 回滚事务(Commit statements attempted + Rollback statements attempted) 488 DB2 数据库管理最佳实践 指标:无 前面我们已经介绍了事务的概念,事务是一个完整的工作单元,具有 ACID 特性。该指标 可以直接反映出系统单位时间内的吞吐量高低。 可以通过以下公式计算数据库总的事务数量: Total Number of Transactions (TXCNT): TXCNT = Commit statements attempted + Rollback statements attempted db2inst1@dpf1:~> db2 "select COMMIT_SQL_STMTS + ROLLBACK_SQL_STMTS as txn_counts from sysibmadm.snapdb" TXN_COUNTS -------------------- 5149 1 record(s) selected. 2. 每个事务包含的查询 SQL 语句数量 来源:数据库快照 公式:查询语句数量/交易总数(Select SQL statements executed / (Commit statements attempted + Rollback statements attempted)) 指标:OLTP≤10 对于 OLTP 系统来说,每个事务执行的查询次数一般小于 10。如果事务太长,可能会造成 一些锁等,影响并发性能。 db2inst1@dpf1:~> db2 "select decimal(SELECT_SQL_STMTS)/decmial(COMMIT_SQL_STMTS + ROLLBACK_SQL_STMTS+1) as selpertxn from sysibmadm.snapdb " SELPERTXN -------------------- 2.94 3. 每个事务包含的增删改语句数量 来源:数据库快照 公式:插入、更新与删除语句的数量/ 交易总数( Update/Insert/Delete statements executed/(Commit statements attempted+Rollback statements attempted)) 指标:OLTP <= 5 在 OLTP 系统中建议不要在同一条交易中使用过多的数据更改语句(插入、更新、删除), 单条交易中过多的这类语句会造成大量的锁,容易引起锁等待甚至死锁,同时过长的交易可能 会引起活动日志过长,导致日志空间占满。 db2inst1@dpf1:~> db2 "select decimal(UID_SQL_STMTS)/decimal(COMMIT_SQL_STMTS + ROLLBACK_SQL_STMTS+1) as uidpertxn from sysibmadm.snapdb " 第 16 章 优化器与性能调优 489 UIDPERTXN ----------------------- 5.58 4. 每个事务返回的结果集行数 来源:数据库快照 公式:选择的行数/事务总数(Rows Selected/(Commit statements attempted + Rollback statements attempted)) 指标:针对 OLTP 业务,尽量靠近最终用户真正在终端上看到的结果集的数量 这个指标用来计算每个事务返回的平均结果集。这个值尽管无法控制,但如果发现该指标 异常高,表明应用程序获取大量数据,可能的原因是在应用层进行数据过滤,可以考虑在数据 库层增加过滤条件,减少返回到应用层的结果集。 db2inst1@dpf1:~> db2 "select decimal(rows_selected)/decimal(COMMIT_SQL_STMTS + ROLLBACK_SQL_STMTS+1) as rowsselpertxn from sysibmadm.snapdb " ROWSPERTXN ------------------------ 17.11 5. 每个事务读的行数 来源:数据库快照 公式:读取的行数/事务总数(Rows Read/(Commit statements attempted + Rollback statements attempted)) 指标:OLTP 尽量靠拢前一个指标 该指标与上一个指标不同。在获取结果集的过程中,DB2 必须读取数据页,因此要读取的 数据行数(rows_read)可能远远大于结果集(rows_selected)。 通过优化物理设计,比如设计好的索引,DBA 可以提高结果集与读取行数的比例。如果缺 乏好的索引,DB2 必须读更多的行才能找到满足条件的数据。当应用了索引后,记得重新计算 和评估这两个指标,验证读取的行数是否降低。 db2inst1@dpf1:~> db2 "select decimal(rows_read)/decimal(COMMIT_SQL_STMTS + ROLLBACK_SQL_STMTS+1) as rowsreadpertxn from sysibmadm.snapdb " ROWSREADPERTXN --------------------------------- 52.60 6. 每个事务需要的缓冲区逻辑读 来源:数据库快照 490 DB2 数据库管理最佳实践 公式:逻辑读的总数/事务总数((Buffer pool data logical reads + Buffer pool index logical reads)/(Commit statements attempted + Rollback statements attempted)) 指标:取决于业务量,在 OLTP 中尽量降低 DB2 对数据页和索引页的读取是通过缓冲区执行的。如果请求的数据不在 bufferpool 中, 则首先将磁盘数据读到缓冲区。如果数据已经在缓冲区,则直接从缓冲区中获取,我们把这种 数据读叫做逻辑读。 那么大量的逻辑读为什么会对性能有影响呢?这是因为大量的逻辑读一般代表的是大量的 数据扫描,通常是由于缺乏良好的物理设计造成的,所以尽管从缓冲区直接读取,但会消耗大 量的 CPU 资源。 逻辑读和 CPU 资源消耗有很大关系,一个事务中执行的逻辑读越多,消耗的 CPU 资源就 越多。 牢记一点:逻辑读与 CPU 消耗成正比。 为了降低该指标,需要找到那些开销高的 SQL 语句,比如 CPU 时间、排序时间、I/O 时间、 读取的行数、写行数等。一旦找到,可以通过物理设计或对 SQL 语句进行调优的方法降低指数。 降低数据库整体逻辑 I/O 可从 SQL 语句着手,通过 get snapshot for dynamic sql 可监控每个 SQL 语句的逻辑读,然后对语句进行分析。也可以通过 event monitor for statements 进行监控。 16.3.14 检测索引页扫描 来源:数据库快照 公式:逻辑索引读的总数/事务总数(Buffer pool index logical reads/ (Commit statements attempted + Rollback statements attempted)) 指标:取决于业务量,在 OLTP 中尽量降低 The number of Bufferpool Index logical read per Transaction (BPLITX) : BPLITX = Buffer pool index logical reads / TXCNT 索引是 B+结构,包含根节点,中间节点和叶子节点,数据存在叶子节点,根节点和中间节 点提供了遍历的路径。对索引的访问一般有两种遍历方式:一种是从 root 页开始读,然后访问 中间节点,最后访问指向数据页 RID 的叶子节点。另外一种是不通过跟节点和中间节点,而直 接遍历叶子节点获取需要的数据。 第一种方式是理想情况,一般每条 SQL 语句平均需要访问 3-4 个索引页。而第二种方式可 能需要更多的索引页访问,需要消耗大量的 I/O 和 CPU 时间。 什么情况下会使用第二种访问方式呢?比如,在表 T1 (A,B)列上建一索引,如果根据 B 字 第 16 章 优化器与性能调优 491 段执行查询: select * from T1 where B='aaa',这时 DB2 优化器可能会通过索引扫描,遍历索引的 叶子节点,查找满足 B 查询条件的值(一般情况下这种访问计划不会发生,因为大部分时候直 接扫描表可能会更加有效,但是在某些特殊的设置中,如果优化器判定扫描所有索引节点更加 有效的话,优化器可能会选择索引扫描)。 除了查询语句,更新和删除语句也应该充分利用索引。假如一个事务包含 10 个查询, 2 个修 改操作,那么理想的索引页扫描数应该是: (10+2) *4*1.5=72 (4 表示 4 个索引页,1.5 考虑了额外的开销) 如果实际环境中该指标值过大,很可能是 DB2 在扫描索引页。 db2inst1@dpf1:~> db2 "select POOL_INDEX_L_READS/(COMMIT_SQL_STMTS+ROLLBACK_SQL_STMTS+1) as BPLITX from sysibmadm.snapdb" BPLITX -------------------- 7 1 record(s) selected. 16.3.15 日志写入速度 来源:数据库快照 公式:日志写时间/日志写次数(Log write time (sec.ns)/Number write log IOs) 指标:<3 毫秒 日志写入的速度有时会对经常进行提交的应用程序性能产生决定性的影响。 在 mincommit 为 1 的情况下,每一次的提交都会伴随着物理日志的写入。在一个频繁提交 的系统中,如果物理日志的写入速度过低,会对性能产生非常大的影响。 在监测中,可以使用数据库快照中 Log write time (sec.ns)与 Number write log IOs 判断平均 每个写日志 I/O 的时间消耗。如果发现该值超过 5 毫秒,则说明该部分有待提高。 Log write time (sec.ns) = 3823.000000004 Number write log IOs = 1184311 16.3.16 查询执行速度 来源:SQL 快照 公式:总执行时间/总执行数量(Total execution time (sec.microsec) / Number of executions) 492 DB2 数据库管理最佳实践 指标:OLTP 中<1 毫秒 该指标代表给定查询的平均执行时间。对于典型的 OLTP 应用程序,一般单条查询的时间 在 1 毫秒以内。即使逻辑相对复杂,也应该保持在 10 毫秒之内。对于运行时间超出 10 毫秒的 纯 OLTP 查询,需要进一步研究该 SQL 的访问计划并提升性能。 前面介绍一些常用的有代表性的数据库级别性能指标。当然,还有很多语句级性能指标, 由于与应用程序与业务逻辑联系过于紧密,我们无法给出预期指标(譬如查询的每次执行所花 费的 CPU 时间)。请读者在进行指标监控时根据自身的业务特点和平日间的数据收集,制定出 适用于该系统的性能指标。 16.3.17 实例级性能指标 1. 排序内存过量使用 来源:实例快照 公式:无 指标:排序内存高水位 < SHEAPTHRES 当排序内存超出实例排序内存的软上限后,系统会从溢出内存池分配一些内存,为请求的 排序操作服务。 从数据库的可用性来说,这种现象是完全正常的。但是,如果系统经常需要进行大量的排 序,实例排序内存软上限在每次大规模的排序请求时都会被超过,那么用户可以考虑提高软上 限,避免系统一次又一次无意义地分配与释放内存。 2. 实例内存使用 (注:在 9.5 及以后的版本中,实例内存代表着该实例该分区所占用的全部内存。在 9.1 及 以前的版本中,我们这里讨论的实例内存为实例内存+数据库内存+应用程序组内存) 来源:db2pd -dbptnmem 公式:当前使用 / 系统总物理内存 指标:在只运行一个实例单分区,并且没有其他应用程序运行的系统中≤90% 当实例的内存没有被妥善配置时,如果所提交使用的内存超出了系统物理内存的限制,则 会引起换页。因而,为了确保换页不会发生,需要为操作系统内核预留出部分内存,一般我们 将一个实例的总内存上限设置为物理内存的 80%~90%。 第 16 章 优化器与性能调优 493 16.3.18 操作系统级指标 1. LTG(Logical Track Group)大小 来源:lquerypv -M hdiskn 公式:无 指标:最大硬件可以支持的 LTG 当 LVM 接收到一个 I/O 请求时,系统将会把传入的 I/O 请求切分成一段一段的,每一段的 大小就是这个 LTG,然后将被切分的请求传入磁盘。 在 AIX 5.2 中,内核所支持的 LTG 可以为 128KB、256KB、512KB 与 1024KB,而很多存 储设备或磁盘则有自己的限额,对于现代的存储设备,大部分都可以支持超过 1MB 的 LTG。因 此在 AIX 5.3 中,内核所支持的 LTG 被调整为最大 16MB。 为了提升 I/O 性能,一般建议将 LTG 调整为硬件所支持的最大尺寸: varyonvg -M512K tmpvg 2. CPU 内核 / 磁盘比例 来源:操作系统与存储硬件信息 公式:存储系统中总物理磁盘数量 / 内核数量 指标:介于 6~20 之间 我们知道,很多系统中 I/O 都是制约性能的瓶颈。因此,将数据均匀地分布在多块物理磁 盘中,能够有效地加快数据并行读取的性能。 一般来说,对于现代的服务器系统,很少有用户使用本地硬盘存储所有数据。很多时候数 据存储在 SAN 或者 RAID 中,而存储设备则包含多个物理磁盘。 在系统的设计之初,一般的建议是一个 CPU 内核有 6~20 块物理硬盘相对应。过少的物理 磁盘会对性能造成负面影响。 3. CPU 内核/内存比例 来源:db2pd -osinfo 公式:总内存/总 CPU(TotalMem/TotalCPU) 指标:4~8GB 在通常情况下,当一台系统中包含多个 CPU 内核的时候,每个 CPU 内核对应的内存数量 494 DB2 数据库管理最佳实践 应当在 4~8GB 之间。 过小的内存意味着数据库不能将足够多的数据放入内存,这样将会导致数据在内存和磁盘 之间的频繁交换,致使 CPU 的处理能力不能够完全发挥。 同理,过大的内存意味着会有很多的数据需要处理,而有限的 CPU 数量会使 CPU 的负担 加重。尽管在抛开业务逻辑后,内存的大小和 CPU 运算能力之间没有一个直接的运算公式,但 是对经过了处理大量的性能负载问题分析后,关键性能指标建议每个 CPU 内核对应 4~8GB 内 存比较合理。 16.4 小结 本章我们首先介绍了 DB2 优化器原理,然后通过实例介绍了性能调优的思路和方法。接着 重点介绍了索引的使用和最佳实践,以及排序的根源及减少排序的思路和方法。 最后是一些 KPI 的介绍,KPI 是一些国内外性能分析专家总结的一些通用指标,目的是为 了快速地判断系统是否存在异常。 16.5 判断题 (1)DB2 在进行访问计划的开销估算时,完全基于数据库对象的统计信息。 T:正确 F:错误 (2)收集到当前的统计信息一定能够保证访问计划是最优的。 T:正确 F:错误 (3)DB2 支持 B+树索引与 Bitmap 索引。 T:正确 F:错误 (4)语句中的排序一定可以通过增加合适的索引进行消除。 T:正确 第 16 章 优化器与性能调优 495 F:错误 (5)KPI 适用于所有场合。 T:正确 F:错误 第五部分 DB2 问题诊断及安全 一般来说,系统的运维早已经有了成熟的脚本和软件,每天并不需要 DBA 手工运行无数的 命令。在 DBA 的日常工作中,出问题的概率也不会问题很高,但是我们绝对不要小看问题诊断 能力在工作中的重要性。因为问题往往是突发性的,如果不能在出现问题时快速诊断和解决, 将会给生产造成重要影响。通常情况下,问题诊断和性能调优的能力高低,才能真正反映一个 DBA 的水平。 我们在上几章中已经介绍了性能监控与调优,本章不再重复。而锁相关问题则自成体系, 已经在相关章节中进行了探讨。在这一章中,我们主要讨论几种典型的问题,以及对应的分析 思路。 本章内容安排如下:  概述。  诊断日志分析。  宕机。  挂起。  错误信息分析。  分析数据收集工具。 17.1 概述 在列出问题类型之前,我们必须明确,世界上没有任何一本书能够告诉读者如何对所有类 第 章 17 问题诊断 496 DB2 数据库管理最佳实践 型的问题“一步一步”地进行问题诊断。本身问题诊断便是当系统发生意外的情况下如何处理, 如果有任何人能够把所有可能出现的“意外”都想到的话,那么意外便不是真正的“意外”了。 因此,在问题诊断的过程中,用户必须依靠自己的判断和直觉,通过分析各种相关的日志 和数据,一步一步地接近问题的根源。 之所以我们要讨论几种不同类型的问题,是因为对于不同类型的问题,所使用的分析思路 有着很大的区别。在本章中,我们主要讨论下列几个类型的问题:日志信息(应用程序与实例 没有出错现象,但是日志中反映出一些错误)、宕机(实例 crash)、挂起(hang,在性能分析章 节中讨论过,这里进一步说明)及错误信息(一些命令返回错误信息)。 17.2 日志信息错误 db2diag.log文件是DBA的好朋友,DB2会将大部分的错误信息都记录到该诊断日志文件中。 对于日志信息错误,从用户的角度看,数据库与应用程序一切运行良好(至少还能够工作, 没有崩溃或者停止),但在日志中显示了一些与错误相关的信息。 在遇到这类问题时,我们第一个要问的问题就是,这个错误信息是否真的代表什么地方发 生了错误,还是说 DB2 只是显示一些比较重要的不容忽略的正常信息。 例如下面的信息: 2011-02-18-13.30.42.616890-300 E3077A415 LEVEL: Warning PID : 47302258 TID : 1 PROC : db2agent (SAMPLE) INSTANCE: db2inst1 NODE : 000 DB : SAMPLE APPHDL : 0-409 APPID: *LOCAL.db2inst1.110218183018 AUTHID : DB2INST1 FUNCTION: DB2 UDB, base sys utilities, sqleDatabaseQuiesce, probe:1 MESSAGE : ADM7506W Database quiesce has been requested. 2011-02-18-13.30.42.629345-300 E3493A431 LEVEL: Warning PID : 47302258 TID : 1 PROC : db2agent (SAMPLE) INSTANCE: db2inst1 NODE : 000 DB : SAMPLE APPHDL : 0-409 APPID: *LOCAL.db2inst1.110218183018 AUTHID : DB2INST1 FUNCTION: DB2 UDB, base sys utilities, sqleDatabaseQuiesce, probe:2 MESSAGE : ADM7507W Database quiesce request has completed successfully. 这部分信息的级别是警告,但是是否意味着系统中真的出现了问题呢?答案是否定的。这 条信息会在用户发出 quiesce 命令时显示,也就是用户手工挂起了这个数据库。 一般来说,在 DB2 中值得注意的错误类型是 Severe 和 Error。Warning 信息一般是指用户不 应当忽略,或者当问题发生时用户需要查看的重要信息,而不是指系统真正存在问题,但是要 第 17 章 问题诊断 497 注意,有一些警告信息本身可能无关痛痒,但是和一些其他信息结合在一起时就会意味着一些 问题。对于 Info 则是可以忽略,一般用来显示一些调错时需要的重要信息。 在阅读日志文件的时候,首先找到自己所关心的信息,然后根据每一条记录的时间戳与进程 和线程 ID 向上翻阅日志得到该进程(线程)在这个事件中(相近的时间戳)第一个报告的问题。 当我们找到了一个事件中问题起始的时间,我们就可以开始理解该条日志在说什么了。 对于一条 db2diag.log 日志,第一行主要包含日期与记录级别,第二行是进程 ID、线程 ID 和进程名,第三行为实例名和节点 ID。如果该记录对应某一特定应用,则第四行是应用程序句 柄和 ID,第五行为其授权 ID;否则第四行为函数名和记录点。接下来是该记录的信息。 我们来尝试分析一下下面这条记录中的信息: 2010-12-10-02.00.19.631828-300 E102299A401 LEVEL: Info PID : 1028606 TID : 1 PROC : db2agent (SAMPLE) 0 INSTANCE: db2inst1 NODE : 000 DB : SAMPLE APPHDL : 0-3033 APPID: *LOCAL.db2inst1.101210070006 AUTHID : DB2INST1 FUNCTION: DB2 UDB, database utilities, sqlubSetupJobControl, probe:1410 MESSAGE : Starting an online db backup. (1)首先,这条信息产生于 2010-12-10-02.00.19.631828,类型为“信息”,也就是不代表任 何错误。 (2)进 程 ID 为 1028606,线 程 ID 为 1,进程 名 为 db2agent,数据库名(括号中的)为 SAMPLE。 (3)实例为 db2inst1 ,节点为 0,数据库名为 SAMPLE。 (4)应用程序句柄为 0-3033,ID 为 LOCAL.db2inst1.101210070006,也就是说该应用为本 地应用程序,启动实例为 db2inst1,连接时间为 2010 年 12 月 10 号 07 点 00 分 06 秒。连接授权 用户为 DB2INST1。 (5)产生该记录的 DB2 函数名为 sqlubSetupJobControl,记录点为 1410。这部分信息主要 是 IBM 的 DB2 支持专家用来确认问题发生的位置,对于用户来说意义不大。简单地说,DB2 中大部分函数都以 sql 起始,然后后面的一至两个字母代表着该函数在 DB2 中所属的模块,紧 接着就是以大写字母开始的函数名称。而记录点则是每个函数中的唯一标识,用来帮助 IBM 的 技术支持专家确定问题的出处。 (6)具体信息为在线数据库备份开始。 因此,我们通过以上的分析,可以看出,数据库 SAMPLE 在节点 0 中,曾经在 2010 年 12 月 10 号 07 点 00 分 06 秒由用户 DB2INST1 发起了在线数据库备份。 然后我们来看接下来的记录: 2010-12-10-02.29.22.307082-300 I102701A584 LEVEL: Error PID : 1274298 TID : 1 PROC : db2med.1028606.9 0 498 DB2 数据库管理最佳实践 INSTANCE: db2inst1 NODE : 000 FUNCTION: DB2 UDB, oper system services, sqlowrite, probe:200 MESSAGE : ZRC=0x850F000C=-2062614516=SQLO_DISK "Disk full." DIA8312C Disk was full. DATA #1 : File handle, PD_TYPE_SQO_FILE_HDL, 8 bytes 0x0FFFFFFFFFFFD5D0 : 0000 0003 0000 0080 … DATA #2 : unsigned integer, 8 bytes 1048576 DATA #3 : signed integer, 8 bytes 98304 DATA #4 : signed integer, 4 bytes 35 这条记录在 db2diag.log 中紧接着上一条。我们来分析这条错误信息的含义。该错误发生在 2010-12-10-02.29.22.307082,也就是数据库在线备份开始的 29 分 03 秒之后,进程 ID 为 1274298, 线程为 1,而进程名则是 db2med.1028606.9,分区为 0。 从进程名中我们可以看出,该 db2med 是服务于进程 1028606 的, 而 1028686 则是我们刚才 的那个发出在线备份开始信息的进程,因此我们可以推断出,该错误是在线备份所产生的。 紧接着,我们能够看到,写出该信息的 DB2 函数为 sqlowrite,记录点 200,信息为 SQLO_DISK,即磁盘已满。而下面的具体的数据部分则没有具体信息,一般来说是由 IBM 技 术支持专家解读。这部分数据需要对照 DB2 源代码,才能够了解每段数据所代表的具体含义, 因此不用深究。 那么从用户的角度,我们可以知道,在在线备份开始 29 分钟之后,db2med 进程报告在线 备份磁盘空间满。 然后再继续向下看,这时我们发现日志中还有很多其他 db2med 进程的信息(每一个备份会 启动若干个 db2med 进程),因此如果我们想要知道 db2med.1028606.9 的信息,则需要跟踪进程 1274298: 2010-12-10-02.29.22.488199-300 I132164A353 LEVEL: Warning PID : 1274298 TID : 1 PROC : db2med.1028606.9 0 INSTANCE: db2inst1 NODE : 000 FUNCTION: DB2 UDB, database utilities, sqluMCWriteToDevice, probe:956 MESSAGE : Media controller -- Disk full encountered on /udb/db2inst1/SAMPLE/udb10/backup 这条信息告诉我们造成磁盘满的路径,即 /udb/db2inst1/SAMPLE/udb10/backup。而对于这个 错误,用户需要做的就是增加该磁盘路径的空间。 第 17 章 问题诊断 499 17.3 宕机 宕机是一类比较严重的问题,意味着 DB2 服务的意外中断。可能很多人下意识地认为,宕机 就一定是 DB2 的 bug。这种说法也对也不对。从某种意义上说,DB2 应该尽量保证服务的不中断 运行,理论上应该能够检测到不同类型的错误并给出相应的错误信息,而不是意外停止服务。 但是,对于一个数据库系统来说,比保障系统不中断运行更加重要的事情就是保证数据的 完整性。如果任何操作可能会造成数据的损坏,当 DB2 无法有效地处理该问题,那么 DB2 就会 在牺牲连续性的基础上,强行中断数据库服务,以保障数据的合法与完整性。 而另一种宕机则是非法内存访问。如果大家写过 C 的程序,都会明白当尝试访问一个非法 内存地址时系统会发生什么。没错,就是信号 11 SIGSEGV,代表应用程序尝试访问一块没有被 映射在进程空间中的内存。 一般来说,我们可以认为,第一种的宕机可能是由于人为或者物理介质的损坏造成 DB2 主 动停止服务;而第二种宕机则在大部分情况下都是 bug 所致,导致 DB2 内部处理时发生非法内 存访问,以至于进程崩溃。 那么,在这一节,我们给出两个例子来说明这两种宕机情况。 1. 非法内存访问 在这类问题中,如果用户发现系统无缘无故地宕机,应该在 db2diag.log 中先搜索字符串 sigsegv。结果应该返回类似如下的信息: 2011-01-19-17.54.09.607488+000 I45688A517 LEVEL: Error PID : 1781762 TID : 1 PROC : db2agent (SAMPLE) 0 INSTANCE: db2inst1 NODE : 000 DB : SAMPLE APPHDL : 0-420 APPID: 9.63.48.235.51598.110119175407 AUTHID : SAMPLE_C FUNCTION: DB2 UDB, base sys utilities, sqleagnt_sigsegvh, probe:1 MESSAGE : Error in agent servicing application with coor_node: DATA #1 : Hexdump, 2 bytes 0x0FFFFFFFFFFF3E50 : 0000 .. 用刚才讨论过的方法分析该数据,可以看到数据库 SAMPLE 在 2011-01-19-17.54.09.607488 产生 sqleagnt_sigsegvh。 下一步我们要做的就是进入 db2dump 目录,找到所产生的 trap 文件。在 9.5/9.7 中,对应的 文件在一个叫做 FODC 的目录中,可以在 db2dump 下搜索*FODC*,将目录的时间戳和 db2diag.log 信息的时间对应,而 trap 文件则是包含 trap 字符串的文件名,搜索*trap*.txt 可以找 到。对于 8/9.1 来说,trap 文件在 db2dump 目录下,起始字母为 t,紧接着进程 ID: U:\>dir t1781762* Volume in drive U is sample 500 DB2 数据库管理最佳实践 Volume Serial Number is 2B34-090C Directory of U: 01/19/2011 05:54 PM 47,724 t1781762.000 1 File(s) 47,724 bytes 0 Dir(s) 511,223,988,224 bytes free 在该文件中,查找字符串 Signal #,会出现类似下面的结果: Signal #4 (SIGILL): si_addr is 0x0000000000000000, si_code is 0x0000001E (ILL_ILLOPC:Illegal opcode.) 一般来说,Signal #4、#10 或者#11 都代表非法内存访问;而如果是#36、#21 之类的,则说 明 DB2 主动写入的信息。 对于非法内存访问的问题,从用户角度没有太多可以做的,需要做的就是尽快联系 IBM DB2 技术支持小组进行分析,同时笔者建议用户尽量使用最新的补丁包,避免遇到已经被修复的问题。 2. 数据损坏(包括数据文件与日志文件) 当数据损坏发生的时候,如果 DB2 检测到某一个数据页包含不合法的数据,或者所需要的 日志文件不存在,那么 DB2 会主动停止数据库服务,以避免进一步的数据损坏。 用户必须区分该类型的宕机与内存非法访问类型的宕机。数据损坏或日志文件破坏所造成 的宕机,大部分情况下都是由于存储问题或者人为原因所造成的。 对于数据页损坏的问题,类似下面的日志信息会在 db2diag.log 日志中显示: 2010-10-12-03.28.49.612975-300 I353077A2547 LEVEL: Severe PID : 19575 TID : 1 PROC : db2agent (SAMPLE) 0 INSTANCE: db2inst1 NODE : 000 DB : ODC APPHDL : 0-822 APPID: GAEBC627.A53D.101012070528 AUTHID : DB2INST1 FUNCTION: DB2 UDB, buffer pool services, sqlb_verify_page, probe:3 MESSAGE : ZRC=0x86020001=-2046689279=SQLB_BADP "page is bad" DIA8400C A bad page was encountered. DATA #1 : String, 64 bytes Error encountered trying to read a page - information follows : DATA #2 : String, 23 bytes Page verification error DATA #3 : Page ID, PD_TYPE_SQLB_PAGE_ID, 4 bytes 301339 DATA #4 : Object descriptor, PD_TYPE_SQLB_OBJECT_DESC, 72 bytes Obj: {pool:4;obj:149;type:1} Parent={3;66} lifeLSN: 0000F4477218 tid: 0 0 0 第 17 章 问题诊断 501 extentAnchor: 28320 initEmpPages: 0 poolPage0: 28336 poolflags: 102 objectState: 27 lastSMP: 0 pageSize: 8192 extentSize: 16 bufferPoolID: 3 partialHash: 26542084 bufferPool: 0x0000000223919b00 DATA #5 : Bitmask, 4 bytes 0x00000002 DATA #6 : Page header, PD_TYPE_SQLB_PAGE_HEAD, 48 bytes pageHead: {pool:29723;obj:0;type:0} PPNum:5 OPNum:240 begoff: 0 datlen: 0 pagebinx: 1 revnum: 0 pagelsn: 0300031EADB0 flag: 0 signature: 0 cbits1to31: abcf04c4 cbits32to63: 0 CALLSTCK: [0] 0xFFFFFFFF7A8CAF48 __1cZsqlbLogReadAttemptFailure6FIpnQSQdDLB_OBJECT_DESC IpnJSQdDLB_PAGE_ibLIpcpnMSQdDLB_GLOBALS__v_ + 0x150 [1] 0xFFFFFFFF7A8CEEC8 __1cQsqlb_verify_page6FpnJSQdDLB_PAGE_pnQSQdDLB_OBJECT DESC_IIpnMSQdDLB_GLOBALS_pL_i_ + 0x498 [2] 0xFFFFFFFF7A8CC400 sqlbReadPage + 0xDD8 [3] 0xFFFFFFFF7A8ABBB4 __1cTsqlbGetPageFromDisk6FpnLSQdDLB_FIX_CB_i_i_ + 0x2EC [4] 0xFFFFFFFF7A8A5BD8 __1cHsqlbfix6FpnLSQdDLB_FIX_CB__i_ + 0x9A0 [5] 0xFFFFFFFF7C3DE184 __1cHsqlifix6FpnHSQdDLI_CB_pnOSQdDLI_PAGE_DESC_Ii_i_ + 0x64 [6] 0xFFFFFFFF7C41F78C __1cIsqlischa6FpnHSQdDLI_CB_pnLSQdDLI_SAGLOB_iI_i_ + 0x30AC [7] 0xFFFFFFFF7C41E6B4 __1cIsqlischa6FpnHSQdDLI_CB_pnLSQdDLI_SAGLOB_iI_i_ + 0x1FD4 [8] 0xFFFFFFFF7C41DA30 __1cIsqlischa6FpnHSQdDLI_CB_pnLSQdDLI_SAGLOB_iI_i_ + 0x1350 [9] 0xFFFFFFFF7C41D8F4 __1cIsqlischa6FpnHSQdDLI_CB_pnLSQdDLI_SAGLOB_iI_i_ + 0x1214 该信息看起来很复杂,让我们来一步步地解读。 首先,问题发生的日期为 2010-10-12-03.28.49.612975,级别为严重(Severe),进程 ID 为 19575,数据库为 SAMPLE。 其次,问题发生时 DB2 的函数为 sqlb_verify_page,记录点为 3,从函数名称我们可以知道, 这个函数是来检验数据页(或者索引页)的。 502 DB2 数据库管理最佳实践 从下面的信息,Data #1 显示错误原因:Error encountered trying to read a page 也就是读取页 面的时候发生错误。Data #2 是相对详细的解释:Page verification error,即页校验错误。Data #3 是数据页 ID,这里是 301339,而 Data #4 是对象句柄的内存映像,其中我们可以看到该对象应 该为表空间 4,对象 149,类型 1(即索引类型),父对象为表空间 3,对象 66(即表的对象)。 然后 Data #6 说的是在这个数据页上,其标识为表空间 29723,数据对象 0,类型 0,因此我们 可以断定,该数据页完全损坏。当 DB2 将任何数据页写入磁盘之前,都要检测其合法性。因此, 如果我们读到磁盘上的一个数据页的页头完全损坏,那么原因中 99%的可能是发生在 DB2 之外, 也就是磁盘或者存储系统的问题。 而要想抓到该完整的数据页,可以在 db2diag.log 中搜索 db2dart,会列出建议的命令, 例如: db2dart DBNAME /di /tsi 3 /oi 66 /ps 4089753p /np 1 /v y /scr n /rptn iTsi3oi66pg4089753p+0.rpt db2dart DBNAME /di /tsi 3 /oi 66 /ps 301339p /np 10 /v y /scr n /rptn iTsi3oi66pg301339p+9.rpt 运行时需要把 DBNAME 换成真实的数据库名称。 对于索引损坏的问题,可以使用 db2dart/MI 参数,通过指定索引对象 ID,该参数可以将索 引设置为不可用,然后在下次数据库启动时(或者第一次使用该索引时)自动重新创建。 对于上面的错误,用户可以使用如下命令将索引设置为不可用: db2dart DBNAME /MI /TSI 4 OI 149 但是如果问题发生在数据上(Data #4 中的 type 为 0),那么用户需要联系 IBM 技术支持人 员手工修复该表,或者恢复早先的数据库备份,回滚到最新时间戳。 对于另外一种损坏,也就是交易日志的损坏,相关的 db2diag.log 信息可能会多种多样。如 果交易日志在数据库运行时被意外删除,可能会得到类似下面的日志信息: 2011-02-18-17.50.55.813824-300 I2284E460 LEVEL: Error PID : 16998 TID : 47580792744256PROC : db2sysc INSTANCE: db2inst1 NODE : 000 EDUID : 37 EDUNAME: db2loggw (SAMPLE) FUNCTION: DB2 UDB, data protection services, sqlpgasn2, probe:1150 MESSAGE : ZRC=0x860F000A=-2045837302=SQLO_FNEX "File not found." DIA8411C A file "" could not be found. DATA #1 : Log file: S0000001.LOG 2011-02-18-17.50.55.838576-300 I2745E413 LEVEL: Severe PID : 16998 TID : 47580792744256PROC : db2sysc INSTANCE: db2inst1 NODE : 000 EDUID : 37 EDUNAME: db2loggw (SAMPLE) FUNCTION: DB2 UDB, data protection services, sqlpgasn2, probe:1160 第 17 章 问题诊断 503 RETCODE : ZRC=0x860F000A=-2045837302=SQLO_FNEX "File not found." DIA8411C A file "" could not be found. 该信息说明日志文件 S0000001.LOG 无法被找到,读者可以用我们之前所讨论的方式自己 尝试解读该日志信息。关于日志文件破坏的解决办法,我们在第 9 章节有详细解释。 17.4 挂起 挂起的意思就是数据库系统停止响应。一般来说有两种类型的挂起:一种是整个实例都停 止响应,包括任何用户的输入,例如 GET SNAPSHOT 或者 LIST APPLICATIONS;另一种是某 个应用程序挂起,而其他所有的连接保持正常。 对于挂起问题的分析思路,主要理解正在被挂起的进程(线程)到底在等待什么。 对于挂起问题,我们第一步要做的就是判断,到底是整个实例挂起,还是某一个应用挂起。 一般来说,我们可以用 LIST APPLICATIONS 命令判断是否是实例问题。当证明实例挂起, 则可以用 db2pd 判断是否为操作系统的系统调用函数问题,还是 DB2 的 latch 问题。 大体上我们可以用图 17.1 所示的逻辑进行判断。 图 17.1 挂起问题诊断步骤 由于 db2pd 不需要等待任何 DB2 latch,所以如果 db2pd 命令依然挂起,则基本可以认为是 操作系统问题。这时使用 procstack、pstack 或者 pdump.sh 等工具可以抓取该 db2pd 的堆栈,从 而了解挂起在哪个操作系统函数中: db2inst1:/DB2diag>sudo procstack 6901854 504 DB2 数据库管理最佳实践 6901854: db2pd -alldbp -db adwprod -bufferpools 0x090000000005ec10 __fcntl(??, ??, ??) + 0x260 0x090000000005edac fcntl(0x300000003, 0xd0000000d, 0xfffffffffff9e88, 0x9001000a 090a9b0, 0x0, 0x10bc, 0x0, 0x0) + 0x44 0x09000000038ef978 sqloflock(0x300000100, 0x100000000000001, 0x200000000000002) + 0xb8 0x09000000038ee6e4 sqloopenp__fdpr_2(0x0, 0x34c0000034c, 0x18000000180, 0xffffff fffffa520) + 0x64 0x090000000287ff9c sqlex_write_log_record(char,char,SQLEX_AUDIT_RECORD_T*,char*,unsigned int)(0x0, 0xfffffffffffaac8, 0x9001000a090a9b0, 0x1, 0x0) + 0x484 0x090000000287fa44 sqlex_write_log_record(char,char,SQLEX_AUDIT_RECORD_T*,char*,unsigned int)(??, ??, ??, ??, ??) + 0x80 0x0900000003658850 sqlex_aud_rec_func(char,int,unsigned int,short,SQLEX_AUD_DATA_T*, unsigned int*,sqlca*)__fdpr_2(??, ??, ??, ??, ??, ??, ??) + 0x69c 0x09000000031613d0 sqlex_get_user_authinfo(unsigned char,unsigned char,unsigned char,int,SQLEX_AUTHINFO_T*,sqlca*)__fdpr_1(0x100000000000001, 0x0, 0x0, 0x0, 0x0, 0x0) + 0x138 0x0000000100009f1c pdProcessAuthorization(pdCB_t*)(??) + 0x234 0x00000001000048f4 main(??, ??) + 0x328 0x0000000100000318 __start() + 0x90 在这个输出中,最下面的函数代表最先被调用的,因此__start()作为进程的入口调用 main 函数,而 main 函数则调用 pdProcessAuthorization,然后该函数调用 sqlex_get_user_authinfo,依 次类推。 最后,我们发现最上层的 DB2 函数为 sqloflock,它 调用 fcntl 系统函数后,fcntl 调用__fcntl, 然后造成挂起,很显然该函数为文件控制函数,因此解决思路应当着重考虑文件系统。 该挂起在解决了文件系统的 mount 问题后不再发生。 如果 db2pd 没有挂起,但是 list applications 命令停止响应,那么说明操作系统暂时没有证据 可以证明其有问题,因此我们要从 DB2 进程(线程)栈入手分析。 使用 db2pd -stack all 可以对每一个进程(或者线程)发送 signal,然后在进程(线程)的 signal handler 之中会调用函数写出 trap 文件。通过分析每个 trap 文件中的堆栈信息和 latch 信息,可以 分析出每个进程(线程)之间的等待关系。 既然是整个实例都在挂起,那么一定有一个全局 latch 阻挡了所有的请求。那么我们预期会 在进程堆栈中看到很多的 latch conflict 信息。通过该 latch 的 ID,我们可以找到是谁持有这个 latch,然后分析该进程(线程)的状态。由于篇幅原因,这里不再举例。 而在最后一种情况,连接数据库与 list applications 都正常,但是系统中一个或者几个应用程 序挂起,其状态一直在 UOW Executing 中,但是快照中逻辑读写与 CPU 使用都没有增长,这就 是典型的应用程序挂起状态。这种情况类似全局挂起,我们需要通过快照中的应用程序 ID 对应 到代理进程(线程)的 ID,然后同样用 db2pd -stack all 得到系统中所有进程(线程)的堆栈, 第 17 章 问题诊断 505 然后从对应那个挂起的代理进程(线程)的堆栈入手。 譬如我们的挂起的代理线程中最上方的堆栈为: __systemcall(0x1,0x8a228,0x100d3689d48,0x3ffffe0000030000,0x2000454d8,0x80000000) + 0x1c __1cRSQdDLO_SLATCH_CAS64LgetConflict6MkL_i_(0x100d3689d48,0x1,0x689e02f1100040,0 x3ffffe0000030000,0x46b0,0x8000) + 0xf0 __1cNsqlo_latch_nsDget6MkLkpkc1pnQSQdDLO_LATCH_TABLE_kbknOSQdDLO_LT_VALUES__i_(0 xcc,0x1,0xffffffff7dcc80bc,0x2a3,0x201e74760,0x1) + 0xf8 __1cRsqlbSMSDirectRead6FpnRSQdDLB_DIRECT_IO_CB__i_(0xcc,0xffffffff2cff43d0,0xfff fffff7dcc80bc,0xffff7c00,0xffffffff7e5aa5a8,0xffffffff13a93780) + 0x284 __1cOsqlbDirectRead6FpnRSQdDLB_DIRECT_IO_CB__i_(0xffffffff2cff43d0,0x0,0x10010,0 x0,0x0,0x100e1bea5c8) + 0x138 __1cOsqldx_diskread6FpnJSQdDLDX_LWA_Iipc_i_(0xffffffff2cff4798,0x1,0x100e1bea5c8 ,0x2023d68e0,0x100e1bea5c8,0xffffffff13a91380) + 0x168 __1cQsqldxReadLobData6FpnJSQdDLDX_LWA_pknMSQdDLDX_IOPARM_pc_i_(0xffffffff2cff479 8,0xffffffff2cff4668,0x8000,0x2c2,0xffffffff547642c0,0x10) + 0x184 __1cMsqldxReadLob6FpnJSQdDLDX_LWA_pnISQdDLDX_LD_Iipc_i_(0xffffffff2cff4798,0xfff fffff54764170,0x10,0xffffffff547642c0,0x0,0x0) + 0x368 __1cOsqldx_lob_read6FpnIsqeAgent_pnISQdDLD_TCB_IpIpnISQdDLDX_LD_pc_i_(0xffffffff 54764178,0x0,0x10,0xffffffff2cff4dec,0xffffffff54764170,0xffffffff547642c0) + 0x1a0 __1cMsqldFetchLFD6FpnIsqeAgent_HHCipnISQdDLD_LFD_iipipc_i_(0x2023d68e0,0x0,0x24, 0xffffffff2cff4dec,0x2,0xffffffff54764170) + 0x21c __1cKsqlrlFieldOmaterializeLOB6MpnIsqlrr_cb_pnNSQdDLO_MEM_POOL_pnISQdDLD_CCB_pnK SQdDLD_VALUE__i_(0xffffffff54764080,0xffffffff1a5718a0,0xffffffff2cff4dec,0x7a00 0,0xffffffff547640e0,0x0) + 0x138 而其 stack 文件中对应的 latch 信息为: Waiting on latch type: (SQLO_LT_SQLB_POOL_CB__readLatch) - Address: (100d3689d48), Line: 675, File: sqlbfiles.C 这个意思就是该代理线程在等待 latch 100d3689d48,因此我们在目录中搜索: U:\FODC_Hang_2011-02-05-11.19.38.431601>grep -i "100d3689d48" *.stack.txt | grep -i "Holding" 506 DB2 数据库管理最佳实践 19720.72.000.stack.txt:Holding Latch type: (SQLO_LT_SQLB_POOL_CB__readLatch) - A ddress: (100d3689d48), Line: 2121, File: sqlbpacc.C 19720.72.000.stack.txt:Holding Latch type: (SQLO_LT_SQLB_POOL_CB__readLatch) - A ddress: (100d3689d48), Line: 2121, File: sqlbpacc.C 可以发现是 19720.72.000 持有该 latch,那么我们进入这个线程的堆栈,得到: Holding Latch type: (SQLO_LT_SQLB_POOL_CB__readLatch) - Address: (100d3689d48), Line: 2121, File: sqlbpacc.C ##### Object: /lib/sparcv9/libc.so.1 _syscall(0xffffffff5ea905c0,0xffffffff4effd154,0xffffffff5ea905c4,0x0,0x1,0xffff ffff4effcf58) + 0x70 ##### Object: /local/0/opt/IBM/db2/V9.5.FP2/lib64/libdb2e.so.1 __1cUSQdDLO_LIO_HANDLE_DATARsqloLioAIOCollect6MLpnXSQdDLO_LIO_COLLECT_STATUS_ppn LSQdDLO_IO_REQdD__i_(0xffffffff4effff58,0x4,0xffffffff4effd718,0x8,0x18780000,0x 3) + 0x33c sqloLioCollectNBlocks(0xffffffff4effd718,0x20,0x0,0xffffffff5ea90580,0x0,0x0) + 0x51c __1cWsqlbClnrCollectSomeAIO6FpnMSQdDLB_CLNR_CB_L_v_(0x100d3684c50,0x20,0x0,0x37b 00ec,0x0,0x100d3684f80) + 0x84 __1cVsqlbClnrCollectAllAIO6FpnMSQdDLB_CLNR_CB__v_(0x100d3684c50,0x9ece8,0x37af6a 4,0x0,0x9ec00,0xffffffff7e5aa5a8) + 0x74 __1cQsqlbClnrFindWork6FpnMSQdDLB_CLNR_CB__i_(0x100d3684c50,0x12d8,0x100d3684c60, 0xffffffff7e5aa5a8,0x100d3684e60,0x100d3684ec0) + 0xc4c __1cSsqlbClnrEntryPoint6FpCI_v_(0x18100000,0x100d3684d60,0x100d3684c50,0x2,0xfff fffffffffffff,0x1001cd1c290) + 0xd0 sqloEDUEntry(0xffffffff613eb230,0xffffffff4effff58,0xa10612,0xffffffff7f020a48,0 x18780000,0xffffffff7e5aa5a8) + 0x3a4 ##### Object: /lib/sparcv9/libc.so.1 很明显,我们在调用 sqloLioAIOCollect 时,该函数调用了一个系统调用(_syscall,并没 有 显示出是什么调用),不过根据函数名,应该可以想象是在进行 AIO 调用时的等待,而操作系统 一直无法返回给 DB2。 因此,该问题依然是操作系统的文件系统 AIO 造成的挂起,而从 DB2 部分,如果想要绕过 该问题则可以考虑设置: db2set DB2LIOAIODISABLED=true 这样的话 DB2 就会避免使用操作系统 AIO 特性,虽然会对性能有一些影响,但是可以绕过 一些对该特性支持不大好的平台(以上问题是在 SUN 中发现的)。 第 17 章 问题诊断 507 17.5 错误信息 错误信息是指当用户运行某些特定的命令(譬如说查询、备份或者 load),该命令返回一个 错误代码。 这类错误可以说是日常工作中最常见的问题,因此我们会用多个案例,从不同角度解释应 该用什么样的思路分析这种问题。 一般来说,错误信息可以分为两大类,一类是 SQLCODE,也就是 DB2 返回的标准错误代 码,一般由引擎中的什么地方出现错误,然后将它映射到 SQLCODE 返回给用户。另一类则是 其他的错误代码,比如一些 DB2 外围工具发生问题时,可能返回一些奇怪的数值,类似 1,2,3 之类的。 首先让我们来讨论 SQLCODE 错误。 17.5.1 SQLCODE SQLCODE 是 DB2 的标准错误代码,可以在信息中心中查找到每一个 SQLCODE 所对应的 含义。一般来说,使用命令“db2"? "”可以很轻易地得知该错误信息所代表的意义: /home/db2inst1 $ db2 ? sql911 SQL0911N The current transaction has been rolled back because of a deadlock or timeout. Reason code "". Explanation: The current unit of work was involved in an unresolved contention for use of an object and had to be rolled back. The reason codes are as follows: 2 transaction rolled back due to deadlock. 68 transaction rolled back due to lock timeout. 72 transaction rolled back due to an error concerning a DB2 Data Links Manager involved in the transaction. Note: The changes associated with the unit of work must be entered again. The application is rolled back to the previous COMMIT. User Response: To help avoid deadlock or lock timeout, issue frequent COMMIT operations, if possible, for a long-running application, or for an application likely to encounter a deadlock. Federated system users: the deadlock can occur at the federated server or at the data source. There is no mechanism to detect 508 DB2 数据库管理最佳实践 deadlocks that span data sources and potentially the federated system. It is possible to identify the data source failing the request (refer to the problem determination guide to determine which data source is failing to process the SQL statement). Deadlocks are often normal or expected while processing certain combinations of SQL statements. It is recommended that you design applications to avoid deadlocks to the extent possible. For more detailed information about preventing deadlocks or lock timeouts search the DB2 Information Center (http://publib.boulder.ibm.com/infocenter/db2luw/v9) using phrases such as "deadlock prevention", and terms such as "deadlocks" and "lock timeouts". sqlcode : -911 sqlstate : 40001 通过上面的例子,DB2 命令给出了该错误信息的详细解释。一般来说,通过阅读和理解该 错误解释,加上在网络上搜索相关的错误信息,可以帮助用户解决绝大部分的错误。 但是并不是所有的错误都可以得到直观的解释。有一些错误,类似 SQL1042 或者 SQL901, 它们所代表的意义便不是非常明确: SQL1042C An unexpected system error occurred. SQL0901N The SQL statement failed because of a non-severe system error. Subsequent SQL statements can be processed. (Reason "".) 一般来说,SQL1042 和 SQL901 错误都是当发生了一些 DB2 无法简单将其归结为某一类型 的问题时所给出的错误代码,而解决这类问题则需要更进一步的分析。 对于这种能够反复重现,并且给出特定错误代码的问题,分析的思路永远是先从 db2diag.log 入手。 由于很多用户并不定期清理 DB2 日志,因此当问题发生后,用户首先应该做的就是运行: db2diag -A 该命令将当前的 db2diag.log 改名并且压缩,然后用户可以重新运行命令得到错误信息,查 看是否有新的 db2diag.log 生成。如果有新的文件生成,则说明其中很有可能包含该错误相关的 信息。 首先我们从一个简单的例子开始: /home/db2inst1 $ db2 backup db sample online to /home/db2inst1/temp SQL2048N An error occurred while accessing object "4". Reason code: "6". 第 17 章 问题诊断 509 当我们使用 BACKUP 命令备份数据库时,SQL2048N 发生,我们需要明白为什么发生这个问题。 那么我们可以使用 db2 “? SQL2048N”得到相关信息: /home/db2inst1 $ db2 "? sql2048n" SQL2048N An error occurred while accessing object "". Reason code: "". Explanation: An error occurred while accessing an object during the processing of a database utility. The following is a list of reason codes: 1 An invalid object type is encountered. … 6 The object being accessed is a table space and either the table space is in such a state that the operation is not allowed or one or more containers of the table space is not available. (LIST TABLESPACES will list the current table space state.) Examples of such states are: quiesced, offline. 7 A delete object operation failed. 8 Trying to load/quiesce into a table that is not defined on this partition. The utility stops processing. User Response: 1 Ensure that "" is of valid type. … 6 The table space may be offline. Attempt to determine the underlying problem and correct it. Some examples of problems are: the filesystem is not mounted, which you can fix by mounting the filesystem then altering the table space switch to online, or table space files have been deleted, which you can fix by performing a restore operation. The table space may be quiesced. Use LIST TABLESPACES to check 510 DB2 数据库管理最佳实践 the table space state. Use QUIESCE RESET OR QUIESCE TERMINATE to make the table space available. Note that the userid holding the quiesce may be needed to perform the QUIESCE REST OR TERMINATE operation. 既然命令中已经提示了 reason code 为 6,那么我们着重研究 6 所代表的含义。 在错误解释中,6 代表给定的表空间状态不对。那么我们可以连接到数据库并且检查表空间 4 的状态: Tablespace ID = 4 Name = IBMDB2SAMPLEXML Type = Database managed space Contents = All permanent data. Large table space. State = 0x0080 Detailed explanation: Roll forward pending Total pages = 8192 Useable pages = 8160 Used pages = 0 Free pages = 0 High water mark (pages) = 0 Page size (bytes) = 4096 Extent size (pages) = 32 Prefetch size (pages) = 32 Number of containers = 1 很显然,该表空间处于 Roll forward Pending 状态,因此可能由于先前发生了表空间恢复后并没有运行 roll forward 命令导致,因此我们需要对该表空间进行 roll forward 以更改该状态,然后进行备份。 这个例子是一个非常直观的问题,用户可以在给出的信息中轻易地判断出问题的出处。 但是并非所有的情况都能够如此直观,让我们来再看一个相对有点难度的例子: 首先我们来对某一个数据库做重定向恢复: (db2inst1@db2host1) /home/db2inst1/temp $ db2 restore db sample into sampl redirect SQL1277W A redirected restore operation is being performed. Table space configuration can now be viewed and table spaces that do not use automatic storage can have their containers reconfigured. DB20000I The RESTORE DATABASE command completed successfully. (db2inst1@db2host1) /home/db2inst1/temp $ db2 "set tablespace containers for 5 using (file '/home/db2inst1/temp/ts2/data' 1000)" DB20000I The SET TABLESPACE CONTAINERS command completed successfully. (db2inst1@db2host1) /home/db2inst1/temp $ db2 restore db sample continue SQL1277W A redirected restore operation is being performed. Table space configuration can now be viewed and table spaces that do not use automatic storage can have their containers reconfigured. DB20000I The RESTORE DATABASE command completed successfully. 第 17 章 问题诊断 511 (db2inst1@db2host1) /home/db2inst1/temp $ db2 connect to sampl SQL0752N Connecting to a database is not permitted within a logical unit of work when the CONNECT type 1 setting is in use. 这是怎么回事?不是明明说“The RESTORE DATABASE command completed successfully.”了吗? 开启另外一个窗口,尝试连接数据库: (db2inst1@db2host1) /home/db2inst1/sqllib/db2dump $ db2 connect to sampl SQL1119N A connection to or activation of database "SAMPL" cannot be made because a previous restore is incomplete. SQLSTATE=57019 看来恢复过程中出现了问题,但是我们并没有看到太多相关的错误信息。 怎么办?我们按照刚才提到的思路,首先备份 db2diag.log 文件: (db2inst1@db2host1) /home/db2inst1/temp $ db2diag -A db2diag: Moving "/home/db2inst1/sqllib/db2dump/db2diag.log" to "/home/db2inst1/sqllib/db2dump/db2diag.log_2011-02-20-10.04.53" 然后重新做一遍 restore continue: (db2inst1@db2host1) /home/db2inst1/temp $ db2 restore db sample continue SQL1277W A redirected restore operation is being performed. Table space configuration can now be viewed and table spaces that do not use automatic storage can have their containers reconfigured. DB20000I The RESTORE DATABASE command completed successfully. 然后看一下 db2diag.log 中有什么信息: 2011-02-20-10.07.48.248041-300 E7542A1013 LEVEL: Warning PID : 1454084 TID : 1 PROC : db2agent (SAMPL) INSTANCE: db2inst1 NODE : 000 DB : SAMPL APPHDL : 0-67 APPID: *LOCAL.db2inst1.110220145541 AUTHID : DB2INST1 FUNCTION: DB2 UDB, database utilities, sqludCheckRedirectedStatus, probe:1162 MESSAGE : SQL1277W A redirected restore operation is being performed. Table space configuration can now be viewed and table spaces that do not use automatic storage can have their containers reconfigured. DATA #1 : SQLCA, PD_DB2_TYPE_SQLCA, 136 bytes sqlcaid : SQLCA sqlcabc: 136 sqlcode: 1277 sqlerrml: 0 sqlerrmc: sqlerrp : sqludChe sqlerrd : (1) 0x00000000 (2) 0x00000000 (3) 0x00000000 (4) 0x00000000 (5) 0x00000000 (6) 0x00000000 sqlwarn : (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) sqlstate: 2011-02-20-10.07.48.248262-300 I8556A771 LEVEL: Warning PID : 1454084 TID : 1 PROC : db2agent (SAMPL) INSTANCE: db2inst1 NODE : 000 DB : SAMPL APPHDL : 0-67 APPID: *LOCAL.db2inst1.110220145541 AUTHID : DB2INST1 512 DB2 数据库管理最佳实践 FUNCTION: DB2 UDB, database utilities, sqludCheckRedirectedStatus, probe:1165 DATA #1 : Insufficient space in tablespace TS1; you must have at least 4960 usable pages. (The "usable pages" total does not include pages used internally by DB2, so the value specified on the SET TABLESPACE CONTAINERS operation should be increased by one extent per container. Based on the latest SET TABLESPACE CONTAINERS values specifed, the tablespace should have 4992 pages in total.) “Insufficient space in tablespace TS1; you must have at least 4960 可以看到 usable pages.”。也就是说,我们的 TS1 所指定的空间太小,需要最少 4960 可用页,即 4992 页的表空间。 那么我们再来一次 SET TABLESPACE CONTAINERS: (db2inst1@db2host1) /home/db2inst1/temp $ db2 "set tablespace containers for 5 using (file '/home/db2inst1/temp/ts2/data' 5000)" DB20000I The SET TABLESPACE CONTAINERS command completed successfully. (db2inst1@db2host1) /home/db2inst1/temp $ db2 restore db sample continue DB20000I The RESTORE DATABASE command completed successfully. (db2inst1@db2host1) /home/db2inst1/temp $ db2 connect to sampl SQL1117N A connection to or activation of database "SAMPL" cannot be made because of ROLL-FORWARD PENDING. SQLSTATE=57019 这次又出了一个新的错误:SQL1117N。 我们来看一下这个错误又是什么: (db2inst1@db2host1) /home/db2inst1/temp $ db2 ? sql1117n SQL1117N A connection to or activation for database "" cannot be made because of ROLL-FORWARD PENDING. Explanation: The specified database is enabled for roll-forward recovery and it has been restored but not rolled forward. No connection was made. Federated system users: this situation can also be detected by the data source. User Response: Roll forward the database or indicate that you do not wish to roll forward by using the ROLLFORWARD command. Note that if you do not roll forward the database, the records written since the last backup of the database will not be applied to the database. Federated system users: if necessary isolate the problem to the 第 17 章 问题诊断 513 data source rejecting the request (see the problem determination guide for procedures to follow to identify the failing data source) and take recovery action appropriate to that data source to bring the data source to a point of consistency. sqlcode : -1117 sqlstate : 57019 意思是说我们需要 rollforward 这个数据库,那么我们来演示一下: (db2inst1@db2host1) /home/db2inst1/temp $ db2 rollforward db sampl complete Rollforward Status Input database alias = sampl Number of nodes have returned status = 1 Node number = 0 Rollforward status = not pending Next log file to be read = Log files processed = - Last committed transaction = 2011-02-20-14.55.21.000000 UTC DB20000I The ROLLFORWARD command completed successfully. (db2inst1@db2host1) /home/db2inst1/temp $ db2 connect to sampl Database Connection Information Database server = DB2/AIX64 9.1.8 SQL authorization ID = DB2INST1 Local database alias = SAMPL 现在我们看到了如何用 db2diag.log 诊断一些并不是非常直观的问题。但是 db2diag.log 是否 就是万能药呢?答案是否定的。 17.5.2 db2trc 有些时候,甚至 db2diag.log 都不能告诉我们太多信息,那么我们就要寻求别的工具的帮助 了。这个工具就是 db2trc,也就是 DB2 跟踪工具。 db2trc 工具在 DB2 中非常强大,不过想要读懂这个工具所产生的文件也是非常辛苦的。 db2trc 工具在设计时主要是为了 DB2 实验室的开发人员所准备的,很多情况下需要对照 DB2 的 源代码进行调试。一般情况下,db2diag.log 足以应付 99%以上的问题,而对于剩下的那 1%,用 户可以给 IBM 技术支持中心打电话获得帮助。而如果用户想要看一看 db2trc 中都有什么东西, 我们在这里进行一些简单的介绍。 实际上在前文中的性能章节我们已经给了一个使用 db2trc 的案例,不过只是走马观花。这 514 DB2 数据库管理最佳实践 里让我们来仔细讨论一下 db2trc 的功能及其设计理念和使用方法。 db2trc 在设计之初主要是用来跟踪 DB2 中函数调用的关系。譬如说我们有一个程序 fibonacci.c,实现“斐波那契数列”计算: #include FILE *ftrace; int fibonacci(int n) { int a = 0; int b = 1; int sum; int i; fprintf(ftrace, "fibonacci entry\n"); fprintf(ftrace, "fibonacci probe 1: %d\n", n); for (i=0;i <执行重现问题的命令> db2trc off db2trc flw -t <跟踪文件名> <跟踪文件名.flw> db2trc fmt <跟踪文件名> <跟踪文件名.fmt> 而第二种方法的格式为: db2trc on -t -l 128M <执行重现问题的命令> db2trc dmp <跟踪文件名> db2trc off db2trc flw -t <跟踪文件名> <跟踪文件名.flw> db2trc fmt <跟踪文件名> <跟踪文件名.fmt> 两者的区别在于开启与关闭时的参数。写入内存的选项需要指定-l 和内存池的大小,然后 再关闭之前需要将该内存写入的跟踪文件;而直接写入文件的选项就可以直接向文件中写入。 在本文的案例中,为了简便起见,我们使用第一种方法。不过用户切记在生产系统中一定 不要使用文件跟踪方式,以免对性能造成大幅度的影响。 我们使用一个笔者曾经给出结论的案例: 用户使用 32 位 Linux 操作系统,每天不定时在连接数据库时会出现 SQL1084C 的错误: [db2inst1@localhost ~]$ db2 connect to suzhou SQL1084C Shared memory segments cannot be allocated. SQLSTATE=57019 第 17 章 问题诊断 517 --日志如下: 2010-11-05-15.35.28.449778+480 E8617819G952 LEVEL: Warning PID : 4708 TID : 2949639056 PROC : db2sysc 0 INSTANCE: db2inst1 NODE : 000 DB : SUZHOU APPHDL : 0-79 APPID: 192.168.6.248.61993.10110507352 AUTHID : L_SZ_V5 EDUID : 20 EDUNAME: db2agent (SUZHOU) 0 FUNCTION: DB2 UDB, base sys utilities, sqeLocalDatabase::FirstConnect, probe:100 MESSAGE : ZRC=0x850F0005=-2062614523=SQLO_NOSEG "No Storage Available for allocation" DIA8305C Memory allocation failure occurred. DATA #1 : String, 299 bytes Failed to allocate the desired database shared memory set. The configured DATABASE_MEMORY plus desired overflow may have exceeded INSTANCE_MEMORY or the maximum shared memory on the system. Attempting to start up with a smaller overflow allowance. Desired database shared memory set size is (bytes): DATA #2 : unsigned integer, 4 bytes 1782579200 这个错误就是说,当数据库想要分配一块 1782579200 字节的内存时失败。 在 db2diag.log 中是唯一的结论,很难更加深入一步地去了解为什么失败。 我们检查了内核参数与当前内存剩余,发现一切指标正常。 因此我们抓 db2trc,其中错误信息点(在 FLW 文件中搜索 SQLO_NOSEG)得到: 6062 0.015829000 | | | | | | | | | | | | | | | | | sqloGetSharedMemoryFromOs entry [eduid 869 eduname db2agent] 6063 0.015830000 | | | | | | | | | | | | | | | | | | sqloMemCreateSingleSegment entry [eduid 869 eduname db2agent] 6064 0.015845000 | | | | | | | | | | | | | | | | | | sqloMemCreateSingleSegment exit 6065 0.015845000 | | | | | | | | | | | | | | | | | sqloGetSharedMemoryFromOs data [probe 2020] 6066 0.015846000 | | | | | | | | | | | | | | | | | | sqloMemAttachToSegments entry [eduid 869 eduname db2agent] 6067 0.015851000 | | | | | | | | | | | | | | | | | | sqloMemAttachToSegments SYSTEM ERROR [probe 100] [ ZRC = 0x850F0005 = -2062614523 = SQLO_NOSEG] 6068 0.015853000 | | | | | | | | | | | | | | | | | | | pdLogSysRC entry [eduid 869 eduname db2agent] 6069 0.015854000 | | | | | | | | | | | | | | | | | | | | pdIsDiagLevelOk entry [eduid 869 eduname db2agent] 6070 0.015855000 | | | | | | | | | | | | | | | | | | | | pdIsDiagLevelOk data [probe 10] 6071 0.015856000 | | | | | | | | | | | | | | | | | | | | pdIsDiagLevelOk data [probe 20] 6072 0.015857000 | | | | | | | | | | | | | | | | | | | | pdIsDiagLevelOk data [probe 500] 6073 0.015858000 | | | | | | | | | | | | | | | | | | | | pdIsDiagLevelOk exit 6074 0.015861000 | | | | | | | | | | | | | | | | | | sqloMemAttachToSegments 518 DB2 数据库管理最佳实践 SYSTEM ERROR [probe 200] 6075 0.015861000 | | | | | | | | | | | | | | | | | | | pdLogSysRC exit 6076 0.015862000 | | | | | | | | | | | | | | | | | | sqloMemAttachToSegments exit [rc = 0x850F0005 = -2062614523 = SQLO_NOSEG] 6063 entry DB2 UDB SQO Memory Management sqloMemCreateSingleSegment fnc (1.3.129.43.0) pid 6827 tid 1061153680 cpid 25803 node 0 sec 0 nsec 15830000 eduid 869 eduname db2agent bytes 48 Data1 (PD_TYPE_SET_SIZE,4) Current set size: 1782644736 Data2 (PD_TYPE_UINT,4) unsigned integer: 0 Data3 (PD_TYPE_HEXINT,4) Hex integer: 0x000007C1 Data4 (PD_TYPE_BITMASK,4) Bitmask: 0x02000000 6066 entry DB2 UDB SQO Memory Management sqloMemAttachToSegments fnc (1.3.129.45.0) pid 6827 tid 1061153680 cpid 25803 node 0 sec 0 nsec 15846000 eduid 869 eduname db2agent bytes 12 Data1 (PD_TYPE_PTR,4) Pointer: 0x00000000 6067 SYSTEM ERROR DB2 UDB SQO Memory Management sqloMemAttachToSegments fnc (5.3.129.45.0.100) pid 6827 tid 1061153680 cpid 25803 node 0 sec 0 nsec 15851000 probe 100 Error ZRC = 0x850F0005 = -2062614523 = SQLO_NOSEG Func.Called: shmat System Errno: 12 bytes 44 Data1 (PD_TYPE_PTR,4) Pointer: 0x00000000 Data2 (PD_TYPE_OSS_MEM_SET_ID,4) Memory set ID: 0880 4F14 ..O. Data3 (PD_TYPE_UINT,4) unsigned integer: 0 这一部分说明,当前的共享内存创建已经成功了,但是调用 shmat 将共享内存连接到进程 时失败。 这时基本上能说明一个问题了,就是当时 32 位的进程中不存在足够的连续空间用来连 接到共享内存。 因此我们的思路应该转向进程内存映像, 即 pmap,正常情况下我们有: 40010000 4 - - - ----- [ anon ] 第 17 章 问题诊断 519 40011000 1852 - - - rwx-- [ anon ] 40300000 1188 - - - rwx-- [ anon ] 40429000 860 - - - ----- [ anon ] 4063d000 1024 - - - rwx-- [ anon ] 4083d000 4100 - - - rwx-- [ anon ] 40cd6000 4 - - - rwx-- [ anon ] 40d3d000 4 - - - rwx-- [ anon ] 40d3e000 1740864 - - - rwxs- [ shmid=0x4fa30006 ] ab14e000 40064 - - - rwxs- [ shmid=0x4fa28005 ] ad86e000 1024 - - - rwx-- [ anon ] ad96e000 40 - - - r-x-- IBMOSauthserver.so ad978000 8 - - - rwx-- IBMOSauthserver.so ad97a000 788 - - - r-x-- libcrypto.so.0.9.7 ada3f000 64 - - - rwx-- libcrypto.so.0.9.7 ada4f000 12 - - - rwx-- [ anon ] ada52000 64 - - - r-x-- libicclib.so ada62000 4 - - - rwx-- libicclib.so ada63000 4 - - - r-x-- locale-archive 可以看到,40d3e000 1740864 - - - rwxs- [ shmid=0x4fa30006 ]这一 段就是正常情况下的数据库内存。通过计算 1740864*1024+0x40d3e000=0xAB14E000,我们发 现它刚好等于下一段共享内存的起始地址 ab14e000,这一段很显然也就是应用程序组内存,占 用到 0xAD86E000 地址。 而当分配失败的时候,该内存看起来为: 40d17000 24 - - - rwx-- [ anon ] ad82e000 128 - - - rwxs- [ shmid=0x6a858006 ] ad84e000 128 - - - rwxs- [ shmid=0x6a850005 ] 也就是我们所拥有的空间仅为 ad82e000-40d17000=1823567872 字节,通过观察当时的 db2trc,发现该空间略小于(差几 K 字节)数据库所需要的内存大小。 那么很明显,是由于我们的内存空间不足引起的。而关于那个 128KB 的内存到底是什么, 通过对比当时的 db2pd 输出,可以确定这两个内存为 LCL 内存,也就是本地连接所使用的共享 内存。 因此,想要解决这个问题,我们有以下几个方法: ①使用 64 位系统。 ②对数据库保持持续的连接,不要经常使数据库停止。 ③手工 activate 数据库。 ④每次 activate 数据库之前确保所有的 db2bp 进程被终止。 520 DB2 数据库管理最佳实践 17.5.3 strace 而另一种更为底层的跟踪方法是使用 OS 跟踪命令,例如 strace。 我们在这里给出一个案例但不进行深入讨论,因为其理论类似于 db2trc。 用户执行 db2start 时发生 SQL1042 错误: -bash-3.2$ db2start SQL1042C An unexpected system error occurred. SQLSTATE=58004 db2diag.log 中唯一的信息是: 2011-01-10-11.30.30.883268+480 I149139853E337 LEVEL: Severe PID : 6934 TID : 47545288465088PROC : db2start INSTANCE: db2inst1 NODE : 000 FUNCTION: DB2 UDB, base sys utilities, sqlestrt.C::main, probe:32 DATA #1 : Hexdump, 4 bytes 0x00007FFF86134E54 : EEFB FFFF … 对于这个问题,我们通过使用操作系统的 strace 跟踪 db2start,下面是其中一小部分相关的信息: 17208 execve("/home/db2inst1/sqllib/adm/db2start", ["db2start"], [/* 24 vars */]) = 0 17208 brk(0) = 0x9dd000 17208 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b6f9ebd4000 17208 uname({sys="Linux", node="szv-dev-pyd-rh1", ...}) = 0 … 17208 msgget(IPC_PRIVATE, IPC_CREAT|IPC_EXCL|0701) = 13238274 17208 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b6faa36eb50) = 17209 17209 close(0 17208 wait4(17209, 17209 <... close resumed> ) = 0 … 17209 rt_sigprocmask(SIG_UNBLOCK, ~[RTMIN RT_1], NULL, 8) = 0 17209 geteuid() = 507 17209 execve("/home/db2inst1/sqllib/security/db2chkau", ["db2chkau", "13205505"], [/* 24 vars */]) = 0 17209 brk(0) = 0x1ebaa000 17209 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2acf5dc8a000 17209 uname({sys="Linux", node="szv-dev-pyd-rh1", ...}) = 0 … 17209 mmap(NULL, 69490, PROT_READ, MAP_PRIVATE, 4, 0) = 0x2acf69b69000 17209 close(4) = 0 17209 open("/lib64/libnss_winbind.so.2", O_RDONLY) = 4 17209 read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\31\0\0\0\0\0\0"..., 832) = 83217209 fstat(4, {st_mode=S_IFREG|0755, st_size=23712, ...}) = 0 … 17209 close(4) = 0 第 17 章 问题诊断 521 17209 munmap(0x2acf69b69000, 69490) = 0 17209 lstat("/tmp/.winbindd", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 17209 lstat("/tmp/.winbindd/pipe", {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0 17209 socket(PF_FILE, SOCK_STREAM, 0) = 4 17209 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR) 17209 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 17209 fcntl(4, F_GETFD) = 0 17209 fcntl(4, F_SETFD, FD_CLOEXEC) = 0 17209 connect(4, {sa_family=AF_FILE, path="/tmp/.winbindd/pipe"...}, 110) = 0 … 17209 mmap(NULL, 159744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2acf69d86000 17209 select(5, [4], NULL, NULL, {5, 0}) = 1 (in [4], left {5, 0}) 17209 read(4, "oradba admins\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 157060) = 124504 17209 select(5, [4], NULL, NULL, {5, 0}) = 1 (in [4], left {5, 0}) 17209 read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 32556) = 32556 17209 --- SIGSEGV (Segmentation fault) @ 0 (0) --- 首先,第一部分: 17208 execve("/home/db2inst1/sqllib/adm/db2start", ["db2start"], [/* 24 vars */]) = 0 17208 brk(0) = 0x9dd000 17208 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2b6f9ebd4000 17208 uname({sys="Linux", node="szv-dev-pyd-rh1", ...}) = 0 这部分说的是 db2start 命令开始,进程号为 17208。 然后该进程执行了一段时间以后,调用了 clone 系统函数创建一个 17208 进程: 17208 msgget(IPC_PRIVATE, IPC_CREAT|IPC_EXCL|0701) = 13238274 17208 clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x2b6faa36eb50) = 17209 17209 close(0 17208 wait4(17209, 17209 <... close resumed> ) = 0 在下一段跟踪中,我们看到这个线程调用 execve 执行 db2chkau 程序: 17209 rt_sigprocmask(SIG_UNBLOCK, ~[RTMIN RT_1], NULL, 8) = 0 17209 geteuid() = 507 17209 execve("/home/db2inst1/sqllib/security/db2chkau", ["db2chkau", "13205505"], [/* 24 vars */]) = 0 17209 brk(0) = 0x1ebaa000 17209 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2acf5dc8a000 17209 uname({sys="Linux", node="szv-dev-pyd-rh1", ...}) = 0 此时我们可以看到打开了一个/lib64/libnss_winbind.so.2 的库,作为安全验证第三方软件: 522 DB2 数据库管理最佳实践 17209 mmap(NULL, 69490, PROT_READ, MAP_PRIVATE, 4, 0) = 0x2acf69b69000 17209 close(4) = 0 17209 open("/lib64/libnss_winbind.so.2", O_RDONLY) = 4 17209 read(4, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\31\0\0\0\0\0\0"..., 832) = 832 17209 fstat(4, {st_mode=S_IFREG|0755, st_size=23712, ...}) = 0 然后打开一个套接字,作为文件句柄 4: 17209 close(4) = 0 17209 munmap(0x2acf69b69000, 69490) = 0 17209 lstat("/tmp/.winbindd", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0 17209 lstat("/tmp/.winbindd/pipe", {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0 17209 socket(PF_FILE, SOCK_STREAM, 0) = 4 17209 fcntl(4, F_GETFL) = 0x2 (flags O_RDWR) 17209 fcntl(4, F_SETFL, O_RDWR|O_NONBLOCK) = 0 17209 fcntl(4, F_GETFD) = 0 17209 fcntl(4, F_SETFD, FD_CLOEXEC) = 0 17209 connect(4, {sa_family=AF_FILE, path="/tmp/.winbindd/pipe"...}, 110) = 0 过了一会,该进程得到信号 11,也就是非法内存访问: 17209 mmap(NULL, 159744, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x2acf69d86000 17209 select(5, [4], NULL, NULL, {5, 0}) = 1 (in [4], left {5, 0}) 17209 read(4, "oradba admins\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 157060) = 124504 17209 select(5, [4], NULL, NULL, {5, 0}) = 1 (in [4], left {5, 0}) 17209 read(4, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 32556) = 32556 17209 --- SIGSEGV (Segmentation fault) @ 0 (0) --- 在 sigsegv 之前的最后一步操作是从套接字中读取信息,而该套接字的 connect 指定的 path 为 path="/tmp/.winbindd/pipe",也就是说该套接字操作与第三方验证软件有关。因此我们下一步 要做的就是从操作系统层面停止使用这个验证软件,然后 DB2 实例就可以成功启动了。 17.6 分析数据收集工具 当我们在分析数据时,出于各种各样的原因,有可能 DBA 并不能直接登录出现问题的系统 (譬如公司的安全规定之类的)。这时我们需要一些工具来收集尽可能多的有用的数据,以供我 们线下分析。 在这一节中,我们将会介绍一些数据收集工具,以快速地收集分析数据。本节中将会介绍 db2support、db2fodc、db2service.perf1 与 db2service.mem1 工具,分别对应着不同类型的数据 收集。 第 17 章 问题诊断 523 1. db2support db2support 工具也许是最常用的系统信息收集工具了。如果读者曾经联系过 IBM 技术支持 小组,应该有过收集 db2support 文件的经验。 一般来说,db2support 的收集命令为: db2support . -d <数据库名> -c -g -s 而当数据库无法连接时(或者 hang),则 使用: db2support . -g -s 对于优化器相关的问题,当确定某一个 SQL 运行缓慢时,可以使用: db2support . -d <数据库名> -cl 1 -sf 注意在中,出现问题的 SQL 需要以分号结尾。 这个命令会在当前目录产生一个 db2support.zip 文件,其中包含非常多的实用信息。 例如,如图 17.2 所示为使用了-d -c -g -s 收集信息。 图 17.2 db2support 目录结构 其中,DB2CONFIG 目录主要包含一些 DB2 设置相关的信息,如图 17.3 所示。 图 17.3 db2support DB2CONFIG 目录 DB2DUMP 目录包含 db2diag.log 文件,如图 17.4 所示。 图 17.4 db2support DB2DUMP 目录 DB2MISC 目录主要是日志和 sqllib 目录结构,如图 17.5 所示。 524 DB2 数据库管理最佳实践 图 17.5 db2support DB2MISC 目录 DB2SNAP 是一些快照相关信息(或者有关 list applications 之类的),如图 17.6 所示。 图 17.6 db2support DB2SNAP 目录 STMM 目录,顾名思义,包含 STMM 日志信息,如图 17.7 所示。 图 17.7 db2support STMM 目录 如果使用了-s 参数,则会收集系统信息,这些信息存在于 db2support.zip 包中的 db2supp_system.zip 文件中,如图 17.8 所示。 图 17.8 db2support db2supp_system.zip 目录 其中比较重要的目录包括 DB2DUMP、OSCONFIG 与 OSSNAP。这里 的 DB2DUMP 包括除 了 db2diag.log 文件与 core 之外的所有 db2dump 目录下的信息,包括 FODC、dump、trap 文件等, 在宕机问题中非常重要。而 OSCONFIG 与 OSSNAP 则包含操作系统设置与一些快照信息,对 于了解系统配置有很大的帮助。 第 17 章 问题诊断 525 最后 ,在 db2support.zip 包中,最重要的文件 db2support.html 包含了该实例与数据库(如果 使用-d 参数)配置、实例版本、db2nodes.cfg 等重要的实例信息,可以帮助用户在最短时间内了 解系统的配置情况。 2. db2fodc 在 9.5 版本之前,当系统发生故障时,用户一般都需要尝试重现问题,然后在问题发生的过 程中使用一些自己写好的脚本收集数据。 但是,对于很多企业来说,重现问题也就意味着业务的中断。因此,db2fodc 就是为了解决 这个问题被创造出来的。db2fodc 工具的全称为 DB2 first occurrence data collection,即第一次问 题发生时的数据收集。 譬如说,用户在下午的业务高峰期发现系统 CPU 占用很高,如果用户是一个非常有经验处 理性能问题的专家,那么应该知道收集多次快照与进程堆栈信息是处理该类似问题的唯一途径。 但是并非所有的用户都有过大量的经验,因此,当用户不明确需要收集什么样的数据时,可以 简单地运行 db2fodc -perf 就可以收集与性能相关的数据了。 在 db2fodc 中,包括了类似性能、挂起等不同类型的问题。 现在,db2fodc 可以用来手工收集性能、挂起与索引错误 3 类问题: 而当一些其他问题发生时(譬如宕机等问题发生时),系统会在问题发生的第一时间自动调 用 db2fodc 收集关于宕机、数据损坏等不同类型的信息,以供将来需要时分析。 对于用户来说,db2fodc 工具主要可以在第一时间用来收集性能与挂起的信息。其中包含的 数据在很大程度上可以提供足够的信息给 IBM 技术支持专家,做到不需要重现问题就可以得到 分析结论。 类似 db2support 工具,db2fodc 在最后也会生成一个压缩文件,其中包含了快照等性能相关 信息。同时,在运行 db2fodc 工具之后用户需要再执行一遍 db2support,确保所有的进程栈信息 被包含进 db2support 文件包: db2fodc -perf full -db <数据库名> db2support . -d <数据库名> -c -g -s 526 DB2 数据库管理最佳实践 一般来说,db2fodc -perf 或者-hang 会运行大约半小时收集相关数据: (db2inst1@db2host1) /home/db2inst1 $ db2fodc -perf full -db sample **********************WARNING*********************** * This tool should be run with caution. * * It can cause some performance degradation, * * especially on busy systems with a * * high number of active connections. * **************************************************** You have 10 seconds to cancel this script with Ctrl-C Executing preparation for performance collection --------------------------------------------------------------- >> Output directory: /home/db2inst1/sqllib/db2dump/FODC_Perf_2011-02-22-08.23.3 >> This execution will monitor the system for approximately 17 minutes and 10 s >> Summary of the execution: Tool to run Number of samples run_vmstat 200 run_iostat 200 run_snapshot 6 run_stacktrace 5 run_db2perfcount 2 run_db2trc 1 Data Collection Start: Tue Feb 22 08:23:51 EST 2011 --------------------------------------------------------------- at 1 seconds: snapshot - Executing at 1 seconds: vmstat - Executing at 1 seconds: iostat - Executing at 2 seconds: snapshot - Finished at 80 seconds: stacktrace - Executing at 116 seconds: stacktrace - Finished at 159 seconds: snapshot - Executing at 159 seconds: snapshot - Finished at 238 seconds: stacktrace - Executing at 278 seconds: stacktrace - Finished at 317 seconds: db2perfcount - Executing at 327 seconds: db2perfcount - Finished at 396 seconds: snapshot - Executing at 396 seconds: snapshot - Finished at 475 seconds: stacktrace - Executing at 515 seconds: stacktrace - Finished at 554 seconds: snapshot - Executing at 555 seconds: snapshot - Finished at 633 seconds: stacktrace - Executing at 673 seconds: stacktrace - Finished at 712 seconds: snapshot - Executing 第 17 章 问题诊断 527 at 712 seconds: snapshot - Finished at 791 seconds: db2trc - Executing at 801 seconds: db2trc - Finished at 870 seconds: db2perfcount - Executing at 880 seconds: db2perfcount - Finished at 949 seconds: stacktrace - Executing at 989 seconds: stacktrace - Finished at 1002 seconds: iostat - Finished at 1002 seconds: vmstat - Finished at 1028 seconds: snapshot - Executing Finished all snapshots at 1028 seconds: snapshot - Finished /home/db2inst1/sqllib/bin/db2cos_perf Finished. Exiting at Tue Feb 22 08:42:17 EST 2011... Output directory is /home/db2inst1/sqllib/db2dump/FODC_Perf_2011-02-22-08.23.35.824594 Open db2fodc.log in that directory for details of collected data 3. db2service.perf1 刚才我们提到了,db2fodc 存在于 9.5 及以后的版本。那么对于 8/9.1 的用户,如果发生了性 能或者挂起的问题,有没有简单的脚本可以运行呢? 正如性能章节中所提到的,对于一般的长时间性能数据收集,每小时一次或者半小时一次 的快照会非常有效。但是当问题突然发生的时候,我们需要提高数据收集的频率做到更高的采 样,然后还需要一些其余类似跟踪、进程栈等信息。 这类问题发生时所捕捉的数据,在 9.5 以后可以使用 db2fodc 收集,但是在 9.5 之前,就需 要一些独立的脚本程序了。所幸的是,IBM 提供了这种脚本的下载,用户可以到 DB2 支持中心 的首页搜索 db2service.perf1(如图 17.9 所示): http://www-947.ibm.com/support/entry/portal/Overview/Software/Information_Manage ment/DB2_for_Linux,_UNIX_and_Windows 图 17.9 db2service.perf1 下载 表 17.1 是结果所对应的链接。 表 17.1 528 DB2 数据库管理最佳实践 Windows http://www-01.ibm.com/support/docview.wss?uid=swg21324857 HP-UX http://www-01.ibm.com/support/docview.wss?uid=swg21324853 Solaris http://www-01.ibm.com/support/docview.wss?uid=swg21324850 Linux http://www-01.ibm.com/support/docview.wss?uid=swg21322385 AIX http://www-01.ibm.com/support/docview.wss?uid=swg21324849 对于不同的平台,用户可以使用不同的脚本收集性能数据。 一般说来,该脚本所收集的数据类似 db2fodc,但由于主要为了 8/9.1 设计,在 9.5/9.7 中不 能针对线程模型收集所有有用的信息,因此在 9.5/9.7 中,用户需要使用 db2fodc,而不是 db2service.perf1。 4. db2service.mem1 对于性能相关信息,IBM 同样也有一个现成的数据收集脚本,即 db2service.mem1。但是由 于各种原因,该脚本并没有网络上的下载。 当用户需要收集内存相关信息时,请联系 IBM 技术支持人员获得针对当前平台的脚本程序。 17.7 IBM 服务支持体系 不知道各位是否寻求过 IBM 电话支持?如果购买了 IBM support 服务,当出现问题的时候, 可以向 IBM 寻求帮助,即开 PMR(Problem Management Record)。国内 DB2 服务流程如下: (1)打 800 电话:8008101818,根据电话提示,输入相应的产品支持按键。接线员核对 PPA 信息(即客户名称和购买的服务号,每个购买 IBM 电话支持的客户,IBM 授予一个 PPA 号码), 当核对无误后,转给 DB2 一线技术支持,或称 Level 1。 (2)Level 1 工程师与客户进行沟通,详细了解出现的问题,并给出建议(如果能给的话)。 当需要更多信息时,会请客户收集。有必要时,有时 Level 1 会协助开一个 PMR 号,用于追踪 问题。 (3)当 Level 1 对问题进行初步诊断后,如果可以解决,会直接反馈给客户。如果问题比 较棘手,就可能将问题升级到 Level 2 支持小组。大部分情况,Level 2 不会直接和客户沟通,而 是通过 Level 1。当然,有些复杂的问题,也不排除 Level 2 直接和客户交流。 (4)当 Level 2 发现问题可能是 DB2 的 bug 时,就会转向 Level 3,Level 3 根据情况进行 错误修复。 第 17 章 问题诊断 529 (5)国内 800 的正常工作时间是早 8:30-晚 5:30。对于紧急的 case,比如宕机,IBM 提供 7*24 小时支持。 当然,800 是远程服务,而且价格不便宜,可以根据实际情况向 IBM 或第三方服务公司购 买一些现场服务,以便当出现紧急问题时,可以获得快速响应。 17.8 小结 在本章中,我们主要讨论了几种不同类型问题的分析思路,其中包括日志信息、宕机、挂 起及命令错误信息。 针对不同类型的错误,其信息的收集与分析思路也有着很大的不同。我们并不能使用分析 宕机的思路来研究挂起的问题,也不能用分析死锁的思路来研究错误信息问题。 因此,在分析问题之初,就要根据问题的类型使用不同的思路来细化,如果让我们最后做 一个总结,则可以认为:  日志信息错误:从错误点起始向前,根据时间与 PID/TID 得到该进程(线程)的第一 个问题点,然后根据整体框架去理解问题的根源,千万不要仅仅依赖一两条错误信息 匆忙下结论。  宕机:要从错误点起始,找到第一个造成宕机或者坏页的日志,如果是非法内存访问 问题,则需要查看进程栈;如果是坏页,则需要恢复或者手工修复。  挂起:检查进程线程堆栈,主要的思路是要弄明白当前系统进程线程之间的等待关系。  命令错误:通过 SQLCODE、db2diag.log 和 db2trc 深入问题。 17.9 判断题 (1)阅读 DB2 日志时,所有的 WARNING 信息都需要额外关注。 T: 正确 F: 错误 (2)出现问题时,最好的办法就是重启机器。 T: 正确 F: 错误 (3)不一定所有的问题都会显示在 db2diag.log 日志中。 530 DB2 数据库管理最佳实践 T: 正确 F: 错误 (4)系统挂起时,DB2 会自动在后台收集诊断信息。 T: 正确 F: 错误 (5)开启 DB2 跟踪会对性能产生极大的影响。 T: 正确 F: 错误
还剩156页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 6 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

youngbird

贡献于2014-04-30

下载需要 6 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!