资源描述
DBA日记第三部1. 前言-写作初衷最近一直在考虑DBA日记的第三部该写点什么,不少网友也 提出了很多好的建议,不过我觉得总是没有抓住要领。老白写DBA日记的本意是写一系列介绍方法的书,而不希望DBA日记写成介绍技术的,因为介绍技术的书实在是太多了,老白目前公务缠身,没有那么大的精力来编写一 本精益求精的技术书籍。DBA日记一直以来都是把老白的经历介绍给大家,把老白的一些处理问题的思路介绍给大家,我想第三部也应该是如此。今年的元旦我是在客户现场度过的,为一家银行的年终决算做护航,期 间我和客户的IT部的两位老总分别聊了半个多小时,两位老总都提出了在我们的合作中,希望我能够给他们的技术人员多传 授一些方法,让他们的技术人员思考问题的能力有所提升。我和很多客户的IT部门的领导交流过,大多数领导都希望我们能够帮他们提升运维能力,这还是第一次有人希望我能够教会 他们如何思考。1号 下午,在飞机上昏昏沉沉的我突然有了一个想法,我应该写一本书,通过这本书,让读者能够学会思考问题的方法。一直以来,我都认为Oracle是有生命,有自己的思维的。要 想学好ORACLE数 据库,就一定要学会按照ORACLE来思考问题。一个DBA对ORACLE的基本原理理解的越深刻,那么他在处理问题的时候越容易抓住要点,少走弯路。记得几年前也是冬天的时候,外面的路边积着厚厚的积雪,我和阿风在 中原的一个省城为一个客户做数据库优化,那也是阿风第一次参加优化项目。在项目刚刚开始的时候,阿风对于数据库优化十分迷茫,他希望我能够给她讲讲如何做 优化。我并没有给他讲优化的技术和技巧,而是带着他一起回顾了数据库的一些基本的组件、基本的原理和算法。记得那次我们住在开发区的一家不错的酒店里,不 过出了酒店就是黑乎乎的一片,了无生机。外面是零下5、6度的严寒,屋内还是暖融融的。我们两个坐在沙发上,喝着茶,上着网,聊着ORACLE。有一天,阿风突然兴奋的叫了起 来:“我领悟到了,我领悟到了”。于是我问他领悟到了什么,阿风把一些ORACLE的基本组件、对象和基本算法串在一起给我讲解了一通。我知道这半个月时间我的心血并没有白费,阿风 终于学会了用ORACLE的思维来思考问题,这意味着他终于突破了一道很难突破的瓶颈。我告诉阿风,这个项目我终于可以放心的交给你来做了。阿风很奇怪的 问我,你还没有教我如何做优化呢,这个项目我一个人能完成吗?我说难道你这些天学习的不正是优化的方法吗?你现在对如何优化这个系统还是一头雾水吗?这个项目最后完成的很完美,虽然这是阿风的处女作,不过在我的帮助 下,他终于独立完成了大多数的优化工作。通过这个项目也让阿风在技术上获得了一次大的飞跃。技术的完善是可以靠勤奋的学习来实现的,经验的积累是可以靠岁 月来堆积的,然而为什么大多数DBA都只能止步于某个阶段,无法成为高手和大师呢?这是因为大家并没有懂得如何用ORACLE的方式来思考问题,不幸的是, 如果你没有学会真正的用ORACLE的方式来思考问题,你就永远无法成为一个真正的高手。DBA日记第三部仍然准备以日记的形式编写,用日记的形式主要是因为我的写作比较随意,所以也无法在初期就 很严谨的编写大纲,不过这种写作方式也有不好的地方,就是内容会略显杂乱。在DBA日记正式出版前,我都会对内容进行修订,在修订的时候我会把日记进行重新的调整,使之更为严谨,结 构更为合理。DBA日记第三部会以每天一个技术要点的形式一个一个的分析ORACLE的技术点,采用深入浅出的方式, 逐步剖析ORACLE的技术要点的基本原理以及思路。让大家通过对这些技术要点的理解,学会分析问题,处理问题的方法。至于知识点的来源,我会以ORACLE CONCEPTS为主,我认为理解基本概念是成为一个高手的起点,这些看似简单的基本概念,你真的搞明白了吗?也许你 看了老白的日记,会有不同的感受。希望我能带给大家一次愉快的阅读。1 前言每个来应聘DBA的 人我都会问他们一个问题:“Oracle到底是什么?”,有些人会用数据库基础的理论来回答我:“数据库是数据的集合”,也有些人会感到茫 然,不知道我问这个问题是什么意思。实际上很多Oracle DBA从来没有思考过这个问 题。“Oracle就是Oracle,是一个产品,还能有 什么意思呢?我不知道Oracle到底是什么也没有影响到我做一个合格的DBA”, 很多人都会这么想。实际上对于Oracle我们确实还需要重新去认识认识,每个DBA在学习Oracle的时候都往往注重于学习如何建库、如何管理、如何编程、如何优化。虽然说这也是学习Oracle数据库最为常见的一种方法,但是这样学习下去,我们总是在记忆一些枯燥的语法和脚本,虽然经过数年我 们积累下了大量的经验,但是我们还是无法真正的理解Oracle,数据库升级了,系统变化了, 我们就必须从头去学习。常年累月,我们总是在一次一次的循环往复的重复着同样的事情,直到我们筋疲力尽,对Oracle失去往日的激情,最终DBA成 为一个职业,Oracle成为我们谋生的手段。事实上,我们可以换一种方式来学习Oracle,让Oracle的精神融入DBA的 血液中,让DBA像Oracle一样思考问题,Oracle作为我们的爱好,作为我们生活的一部分存在。对于大多数DBA来说,这也许只是一个乌托邦式的理想,对于绝大多数DBA来说,我们需要有一份工作,需要靠这份工作来生存,娶妻生子,享受生活。并不是所有的人希望让Oracle成为生活的一部分,这是很现实的,不过我们虽然可以仅仅把Oracle当做是生活的一部分,当做是谋生手段,但是我们也可以同时尝试了解更多的Oracle的本质,让我们像Oracle一样思考。像Oracle一样思考虽然不能带给 你更多的生活乐趣,但是通过这样的方式去学习和思考,我们会更加精确的了解Oracle的精髓,让我们在DBA的成长过程中少走弯路。10多年 前我第一次接触JAVA的时候,感到十分头痛。不是自夸,10多 年前,我是一个相当不错的C程序员,最高纪录是一天之内编写500多 行复杂的代码,而且一次性编译通过,一次性测试通过,这样的记录的诞生是基于十分良好的过程思维能力的。不过当我这个自认为的编程高手第一次接触JAVA的时候,却感到十分吃力。我无法用面向对象的思想去编写程序,所以我学习JAVA的过程十分痛苦,几次学习,最后都放弃了。直到有一天我看到了一本英文的书籍Thinking in JAVA,通过这本书,我掌握了JAVA和 面向对象设计、编程的主要思路。自从看了这本书之后,我再次面对JAVA程 序的时候,发现一切都是那么的简单。很快我就掌握了JAVA编程。现在我虽然还仍然只是一个三 流的JAVA程序员,不过粉丝网的一修修补补的工作我完全能够胜任了,而且在一些和开发人员交流的时候,我 也能够很快的理解他们的思路。后来我总结了一下,在看Thinking in JAVA这本书之前,我在编写JAVA程 序的时候,并没有理解面向对象编程的概念,只能是照猫画虎,拿着一个例子在上面修改,实际上我的编程风格还是面向过程的,因此写出来的代码质量很差。而通 过Thinking in JAVA的阅读,我终于学会了硬面向对象的方法,用JAVA本身的思想去考虑问题,因此我能够更加准确的抓住问题的本质。我想,学习Oracle数据库也是这样,如果我们通过一个案例一个案例的去学习Oracle,那么我们将永远停留在表层上,哪怕我们干上10年20年DBA,也可能只能学到Oracle的一些皮毛,一旦碰到一个我们没有见到的案例,可能我们就会感到手足无措。这些年里我接触过大量的DBA, 我一般把这些DBA分为四大类。第一类DBA是 经验型的,他们处理问题的主要方式取决于以往的经验,他们往往都有很好的习惯,会把每一个处理过的案例整理出来,今后再碰到这类案例的时候,他们会很快的 解决问题。这类DBA随着工作时间的增长,他们的技术也会相应的提高。第二类DBA是理论型的,他们具有很深的理论基础,经常探讨一些“Oracle Internal Only”的高深问题,但是他们在某些方面的研究很深,比如他们能够很清晰的告诉你共享池分配的算法,告诉 你checkpoint的工作原理,但是这些DBA往 往缺乏实际的工作经验,他们研究Oracle但是很少有机会接触大型的数据库系统,因此他们实际解决问题的能力并不强。第三类DBA是技巧型的,他们并不注重理论的学习和经验的积累,他们在处理问题的时候往往能够利用metalink和谷歌百度之类的工具去搜索解决方案,这类DBA是 我见到过的最多的,这类DBA处理问题的时候,往往取决于运气。第四类DBA是 虚心请教型的,这类DBA无论碰到什么问题,甚至连错误信息都没有看明白,就开始到处叫“我的系统出问题了”,然后到处去问 如何解决。实际上,这四类DBA都 是有缺陷的,第一类DBA可能经过多年的工作,有十分丰富的经验,处理问题的能力很强,而且对分析问题十分敏感,很容易抓到 问题的关键,但是由于缺乏深入理解Oracle的理论,因此在碰到一些较为深入的问题的时候,在初期总是很难把握住问题的关键,虽然凭借着自身丰 富的经验和问题分析排查能力,他们最终也能解决大部分的问题,但是很多时候问题解决后还是无法真正的弄明白为什么会解决问题,下一次碰到类似的问题,可能 还是要花很大的代价。第二类DBA在某些方面的理论知识很 强,总是喜欢研究一些十分高深的原理性的东西,但是这类DBA的主要精力都放在了研究一些Oracle内部原理上了,他们没有更多的时间去实践,去把他们学到的理论融合到实践中去。这类DBA往往知识面较为狭窄,仅精通于自己研究比较深入的领域,在实际工作中也很难发挥出自身对理论研究的成 果来。第三类DBA实际上在我们的现实生活中 是最常见的,“万事不明问百度,百度不明就抓瞎”,确实谷歌百度和Metalink能够帮助我们解决不少问题,但是这类DBA往往在问题解决后没有好好思考一下, 为什么这个方法能够帮助我们解决问题,更没有认真总结和归纳一下,于是下一次碰到类似的问题,还是无法依靠自己的思考去解决问题,于是再google一把,也许这一次运气没有这么好了,google出来的资料不是上回的那个了,于是结果很可能是很悲惨的。第四类DBA在我们现实生活中也经常出 现,网络社会十分发达,打个电话或者在qq群里,msn里问问,也许就有人帮我解 决问题,久而久之,这些人放弃了自己思考问题,碰到一点点小问题都首先问起再说。看到这里,大家可能明白了,老白实际上说的不是四类DBA,而是DBA的四种性格,这四种性格可能 会集中在某一个人身上,以老白学习DBA的经验来看,理论结合实践是十分重要的。在2000年 前,老白虽然做了很多项目,也是很多人眼里的Oracle数据库高手,但是在2000年前,老白就是第一类DBA的 典型,没有经过多少理论学习,几乎所有的Oracle数据库的技能都是从实践中获得 的。虽然在实践中我总结出大量的经验,甚至有很多客户建议我写一本书,把我对Oracle的理解写出来。不过当我自信满满的开始写书的时候,我突然发现,我的一些知识需要进行确认,否则写出来就贻笑大方了。于是我开始大量的学习Oracle的一些理论知识,随着写书的过程的深入,我越发感到自身理论水平的不足。Oracle数据库深度历险这本书我写了3年,实 际上2002年我就彻底放弃了出版这本书的念头,因为我发现我的理论知识确实还需要进一步的梳理,但是我并没有 放弃写书,因为我发现通过写书,我更为系统的将Oracle的理论知识梳理了一遍,这次梳 理是通过我以前的知识体系、工作经验,用Oracle Concepts的理论基础 进行了一次完整的整合。通过这3年的写作,我终于完全疏通了Oracle的理论体系,好像一个练武术的人,终于打通了任督二脉,感到无比的畅快。听老白说了这么一大通,是不是很多人都感觉到手脚发凉,难道成为一个合格的DBA有这么难吗?如果我没有打通任督二脉,就不算一个合格的DBA吗?实际上DBA成 长的道路是很多的,并不一定要走老白这一条路,老白仅仅是根据自身的经历,通过这本书来帮助大家梳理Oracle的一些基础知识而已,还是那句话,如果Oracle是你的爱好,那么你无论花多大 代价去研究它都是值得的,如果Oracle只是你职场生涯中的一个工作而已,那么只要你认真对待他就可以了,没必要像老白那样执着。在这本书里,老白会把Oracle数据库深度历险中的一些内容,结合老白的实际工作经验,剖析起原理,并结合案例来说明这些理论知识如何在实践中实际应用,展现给大家,希望老 白的这次写作经历,能够给大家带来一些帮助。每个来应聘DBA的人我都会问他们一个问题:“Oracle到底是什么?”,有些人会用数据库基础的理论来回答我:“数据库是数据的集合”,也有些人会感到茫然,不知道我问这个问题是什么意思。实际上很 多Oracle DBA从来没有思考过这个问题。“Oracle就是Oracle,是一个产品,还能有什么意思呢?我不知道Oracle到底是什么也没有影响到我做一个合格的DBA”,很多人都会这么想。实际上对于Oracle我们确实还需要重新去认识,很多朋友都会游泳,那么我会问一个简单的问题,什么叫做真正的学会了游泳?每个学游泳的人都有这样的感受,刚刚学习 游泳的时候最大的问题是不会换气,学会换气后再往后学习就容易了很多,不过可能我们游上几十米、百把米就会觉得很累。后来我们发现原来我们游泳的姿势还不 太标准,而如果我们学会了踩水,游多少米就不是个问题了,这个时候我们就如鱼得水了。学习Oracle其实和学习游泳十分类似,刚刚开始的时候,我们处于入门阶段,我们要克服对Oracle的恐惧心理,只要我们想学好Oracle,我们就一定能做到,实际上Oracle在入门阶段是十分容易的。我们从头学习Oracle,学会了安装数据库,学会了管 理表空间,学会了日常的故障处理。就像我们学游泳的时候刚刚学会换气的时候一样,我们虽然感觉已经掌握了Oracle数据库,但是碰到稍微复杂一些的问题我们还觉得很难入手,我们能看清楚一些东西,但是我们感觉还是看 不远。实际上上面这种情况是每个初学者都会碰到的,学Oracle和学游泳一样,刚开始的时候我们只是仅仅学会了游泳的外部表现,学会了像别人一样划水、换气,但是我 们并没有真正掌握游泳的本质。如果要消除初学期的迷茫,我们一定要真正的掌握Oracle数据库的概念,一个DBA如果连认真学习Oracle的基本概念都不能做到,那么我觉得他也很难成为一个真正的高手。好了不多说了,我们开始近体的学习。今天我们要学习的内容是“表”,可能有很多朋友已经知道“表” 是什么了。Oracle数据库是一种关系型数据库,关系型数据库里,一组关系的实体化就可以组成一张表。表里面有一个或者 多个字段,这些字段按照某种规则(我们称为关系)组成一个集合,每个关系我们就称为一行数据,也就是我们常说的一条记录。实际上只要你已经有了关系型数据 库的概念,那么就不需要我多说表、记录、关系之类的术语了。实际上表是oracle数据库中十分重要的对象,实际上DBA很大部分的工作就是和不同的表,不同 的数据打交道。Oracle的表是怎么存储的呢?那么我们今天就需要学习一些oracle数据存储中的基本概念。首先我们要了解的是BLOCK(块),BLOCK是Oracle访问的最小物理单位,oracle数据库的BLOCK的大小一般是2K-32K,因此Oracle的块大小和操作系统的 大小是不同的,操作系统的块大小往往要远小于Oracle块的大小,因此Oracle的块不是基本的IO单 位,而只是Oracle的基本访问单位。对于Oracle来说每个表空间都有唯一的块大小,Oracle 8i或者更早版本的数据库, 整个数据库的所有表空间都有唯一的块大小。从Oracle 9i开始,不同的表空间可以 拥有不同的块大小,因此参数DB_BLOCK_SIZE定义的就不再是数据库块大小,而是缺省表空间块大小。虽然BLOCK是Oracle的最小访问单位,但是BLOCK太 小了,如果表的存储数据按照一个BLOCK一个BLOCK来分配,那么效率就太低 了。这里我们继续介绍一个更大的单位,就是extent,中文可以翻译成扩展。一个extent是由一组连续的block组 成的。Extent是Oracle最小的分配单位,Oracle在给对象分配空间的时候,最少分配一个extent。每个EXTENT是由1个或者1个以上的连续的BLOCK组 成的,这种连续是指在文件上的连续,在目前存储技术充分使用条带化技术的前提下,这是一种逻辑上的连续,在物理磁盘上并不一定就是连续的。比EXTENT更大的单位就是SEGMENT,也就是我们常说的“段”。一组结构相同的EXTENT就组成了一个SEGMENT。我们最常见的SEGMENT包括表、索引、表分区、索引分区、回滚段等等。SEGMENT是存储在某个表空间 中的,表空间是个逻辑的概念,一个表空间一般来说包含1个或者多个数据文件,当然一个表空间可 以不包含任何文件,这样的表空间是不能使用的。实际上SEGMENT是存储在数据文件里的,但 是这种存储是按照表空间来组织的,一个段可能存储在一个数据文件中,也可能存储在多个数据文件中,不过这些数据文件必须属于同一个表空间。下面的图是一个 很好的逻辑示意图:我们今天学习的表在Oracle数据库中就是存储在Segment里的。一张普通的表就对应一个SEGMENT,不过还有一些特例:l 一张索引组织表可能包含了多个段,比如OVERFLOW段l 一张表中,如果有LOB字 段,那么LOB字段可能存储在独立的段中,那么这张表也可能包含多个段l 一张分区表可能存储在N个独立 的段中,这些段甚至可能存储在不同的表空间中l 在一个CLUSTER(簇)中,可能存储多张表,而不是一张表。因此存储于簇中的表,表和SEGMENT不是一一对应的通过上面的介绍大家知道了表的数据实际上是作为“段”的形式存储在表空间里的。当创建一个表的时 候,Oracle自动会为这张表创建段,并分配第一个扩展(EXTENT)。我们已经知道了每个EXTENT是由多个连续的BLOCK组 成的,数据就存储在这些BLOCK中,当这些BLOCK都 使用完了,那么当下一次需要插入数据的时候,Oracle会自动为这张表扩展一个EXTENT。所以随着数据量的增长,这张表在不断的扩展。某张表的扩展的数量受到表空间容量和表的MAXEXTENTS参数的限制。对于早期版本的数据库,MAXEXTENTS的缺省值是255,这 种情况下,某些较大的表就很容易达到了MAXEXTENTS的极限,这个时侯再插入数据,我们可能就会看到ORA-01631: Max # Extents (%s) Reached in TABLE %s.%s错误信息。 这个时候就必须去加大MAXEXTENTS参数了。了解了表和SEGMENT,下面我们来看看表的内部,我们知道表中的数据是存储在BLOCK里 的,那么BLOCK的结构是什么样的呢?实际上在一个BLOCK里, 头部是BLOCK的HEADER,存储了一些数据块的 信息、SCN、事务槽等信息。尾部的4个字节 是块尾,块尾的目的是和块头的数据进行校验,确保这个数据块是一致的(具体的算法对于大多数人来说是不需要了解的,大家可以先放一放,在今后的进阶内容中 我们再学习吧)。块头和块尾中间的部分就是存储数据的。表中的数据是从数据块的底部开始存储的。比如我们在某个BLOCK中存储了3条记录,那么第一条记录的尾部 正好和块尾相,存储在BLOCK的最后,第三条记录存储在靠近块头的地方,前面就是这个块的空闲空间。随着数据块中数据的增长,数 据区域逐渐接近块头的位置,也就是块中空闲的部分越来越少。如果这个块的空闲空间不足以插入一条新纪录了,那么就会寻找别的可以插入数据的块,如果所有的 数据块都已经满了,那么就会扩展一个新的EXTENT。关于表中的空闲块管理,是个 十分复杂的话题,我们将在后面找一天来专门讨论,如果对这方面还有疑问的朋友,就先收起这个疑问,继续其他的内容吧。Oracle 的知识点相当广,而且关联性十分强,因此在学习的时候,不一定非要一次性把所有的知识点都搞清楚,循 序渐进才是最关键的,大家请不要心急。现在我们的表中都设计了大量的VARCHAR字段,VARCHAR字段使用起来十分灵活,而且不会浪费空间,所以很好用。不过VARCHAR字段的使用也给BLOCK中 的数据重组带来了麻烦。比如说我们在一张包含VARCHAR字段的表中插入一条数据的时 候,如果某个字段首先我们插入了1个字节,而后来我们通过UPDATE将这个字段的值修改为200个字节,那么这条记录就需要重组,从而导致整个BLOCK进行重组。而如果这个时候我们修改的这条记录所在的BLOCK已经没有200个空闲的字节了,那么这条 记录修改后就无法存储在这个BLOCK里了,Oracle在处理这种情况的时 候,会把这条记录整个迁移到另外一个存在足够空闲空间的数据块中,而在原来记录的地方登记一个指向新位置的指针,这种情况就是我常说的行链。行链的产生, 会加大系统的开销,影响对数据访问的性能。前面我们已经了解了过多的技术细节,可能有些人要呼呼大睡了。实际上,下面才是我们今天要学习的精 华。对于一张表,我们在设计的时候要注意一些什么呢?从我们今天学到的关于段的知识,我们可以得到以下的结论:创建表的时候,选择合适的EXTENT大小是十分重要的,过小的EXTENT大小会导致过多的扩展,影响大数据量插入的性能。这对于存在大量并发插入的情况尤为重要。不过我们 也不需要过于担心EXTENT大小带来的性能问题,我们讨论的性能下降可能是千分之一,甚至更小,实际上,一个段拥有小于1024个EXTENT对段的访问性能的影响 很小下一个问题就是表空间碎片的问题,由于一个表空间中的表的EXTENT大小不同,因此表空间使用一段时间后,表空间的使用就出现了不连续,在EXTENT之间会出现一些“洞”,这种情况我们一般叫做表空间碎片。一些老DBA总是告诫别人,注意整理表空间碎片,否则会影响数据库的性能。实际上,从根本上来说,表空间碎片最大 的危害是浪费了空间,而不是影响了性能。所有的数据访问都会在有效地EXTENT中进行,根本不会去扫描碎片所载的数据块,所以说表空间碎片会影响性能是一种以讹传讹的观点。事实上,对于表的参数定义来说,设置合理的PCTFREE值比减少表空间碎片要重要的多,老白碰到的客户中,能够根据表和业务的特点来设计PCTFREE的情况少之又少。不合理的PCTFREE可能导致大量行链的出现,影响访问性能。在创建表的时候,还有一个十分重要的参数就是INITRANS,这个参数指定了初始化事务槽的数量。事务槽是一个十分重要的对象,每个事务要修改某个数据块的时候,需要在这个数据块中分配一个事务槽。如果 当前没有空闲的事务槽,就需要动态扩展一个,每个事务槽需要24个字节,讲到事务槽,大家可能 明白了Oracle分配空间为什么要从底部向顶部分配空间了,事务槽是从顶部向底部分配空间的,这样的话,两个分配方 式不会冲突。一般情况下,缺省的事务槽参数并不会带来明显的性能问题,不过对于一些并发修改较大的表,如果PCTFREE参数设置过低,就会导致事务槽扩展的时候无法分配空间,从而导致事务等待事务槽。我们可以通过如下脚 本来检查系统中那些表产生了较为严重的事务槽等待:set line 132col statistic_name format a30 truncSELECT t.OWNER, t.OBJECT_NAME, t.OBJECT_TYPE, STATISTIC_NAME, t.VALUE value FROM v$segment_statistics t WHERE t.STATISTIC_NAME = ITL waits AND t.VALUE 10 order by value;如果上面的查询查出来的事务槽等待较为严重的段,那么我们就需要考虑对这些段增加initrans参数的值了。不过我们查看到的值是数据库启动以来的总的统计值,因此我们需要通过一个时间段中多次执 行这个SQL,从而判断是否某个段真正经常出现ITL等 待。在修改initrans参数的时候,我们也要注意,我们修改了这个参数后,这个段中新增的数据块会增加初始化事务槽的数 量,老的数据块是不会改变的,如果要彻底解决问题,还需要对这个段进行一次重组。昨天我们了 解了表的基本结构,从表的存储结构上我们讨论了几个建表的参数。实际上在绝大多数项目中,开发团队并没有对表进行合理的设计,从而导致了系统上线后出现大 量的性能问题。一个最常见的问题就是行链和行迁移。行链的出现在绝大多数系统中是由于我们的设计不合理,比如说某 张表的行长度超过了一个数据块的大小,那么这个表的部分行就会出现行链。这种行链的出现实际上是设计者选择了不合理的数据块大小导致的。从Oracle 9i开始,不同的表空间可以使用不同的块大小,因此我们完全可以设计块大小较大的表空间来存放这些表。和行链不同,有些行链是不可避免的,绝大多数的行迁移是可以避免的。行迁移是怎么产生的呢?Oracle数据库支持varchar字段,varchar字段使用极为灵活,但是正是灵活的varchar字段导致了行迁移的产生。在创建一张表的时候,我们一般不会太关注PCTFREE这个参数。这个参数的缺省值是10%的含义是,当某个数据块的使用率小于 (100%-PCTFREE)这个百分比的时候,这个数据块是可以被插入数据的,一旦块使用率达到了这个指标,这个数据块 就不能再插入数据了。Oracle预留这部分空间的目的就是为VARCAHR字段的扩展提供空间。大家是不是还记得,Oracle的数据块是从块的底部开始使用 的,空闲空间在块头和数据之间。下面我们通过一个例子来说明表数据的存储,我们首先创建一张测试表,并且插入3条记录:create table test1 ( a integer,b varchar2(100),c varchar2(100);insert into test1 values (1,null,aaaa);insert into test1 values (2,null,bbbb);insert into test1 values (1,11111,null);commit;然后我们查找一下这个EXTENT所在的位置:SQL select extent_id,file_id,block_id from dba_extents where segment_name=TEST1 AND OWNER=SCOTT;EXTENT_ID FILE_ID BLOCK_ID- - - 0 10 497我们先查找一下10号文 件是什么:SQLselect file_name from dba_data_files where file_id=10;FILE_NAME-/opt/oracle/oradata/orcl/users02.dbf下面通过dd命令 将499这个数据块dump出 来:dd if=/opt/oracle/oradata/orcl/users02.dbf of=a.dmp bs=8192 skip=501 count=1上述命令从文件中拷贝出了第501号 块,这个块就是存储这三条记录的数据块,我们来看这个数据块的尾部:其中2c 01 03 02 c1 02 FF 04 61 61 61 61就是我们插入 的第一条记录(1,null,aaaa),2c是行头,这是一个标准的数据行,01表 示该行使用了1号ITL槽,03表示这一条记录共有多少个字段(本例子有3个字 段),02 C1 02是第一个字段的值,这 个字段是数值类型,02表示该字段的长度。C1 02就 是10进制的1。后面的FF表示第二个字段是空值,后面的04 61 61 61 61是最后一个字段aaaa。 从第3条记录来看(最上面的那条记录,地址是00001fd0h开始的那条2C 01 02开头的),我们 知道每一行的第三个字节表示这一行的字段数量,什么这个一行只有2个字段 呢?我们可以看看前面的INSERT语句,这一条记录的最后一个字段是null, 如果某一行的最后几个字段都市NULL,那么Oracle在存储的时候,直接 省略了这些NULL的字段,以节约存储空间。如果在这种情况下,如果我们执行“UPDATE TEST1 SET B=ABC WHERE A=1”,会出现什么情况呢?这个时候 数据块会重组,在第一条记录的原本只有一个字节“FF” 的地方插入三个字节,同时这个数据块的数据占用的顶部也上升了。从上面的例子我们可以看出,一旦VARCHAR字段发生改变,需要在数据块中额外分配空间,因此如果我们要UPDATE某条记录的时候,发现这个数据块已经满了,在原有的数据块中就无法存储这条记录了。那么这条记录就被迫迁移到别的数据块中,而在这条记录原本存 放的位置,放入一个指针。在这种情况下,如果我们要访问这条记录,就需要读取了这个指针后,再访问另外一个数据块。行迁移的存在降低了数据访问的性能。也 许有朋友要问,为什么把原来的行迁移走了还要留个指针干什么?不留指针不是不会存在这个性能隐患吗?我们可能忘记了一点,可能这张表存在几个索引,如果我 们将这一行直接迁移而不留下指针,那么所有索引中和这一行相关的数据都需要进行重组,这个重组的代价远高于行迁移访问的开销。下面我们做一个实验:首先我们将 这张表插入一定的数据,为了证明在某些情况下,我们使用缺省的10%的PCTFREE是多么危险,我们将表的PCTFREE设置为10%,然后TRUNCATE原有的表,重新插入数据:drop sequence seqt;create sequence seqt;alter table test1 pctfree 10;truncate table test1;begin for i in 1.1000 loop insert into test1 values (seqt.nextval,abc,null); end loop;end;/commit;这个时候我们执行查询语句SQL select a,b from test1 where a=1; A B- - 1 abcExecution Plan-Plan hash value: 4122059633-| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |-| 0 | SELECT STATEMENT | | 2 | 12 | 2 (0)| 00:00:01 |* 1 | TABLE ACCESS FULL| TEST1 | 2 | 12 | 2 (0)| 00:00:01 |-Predicate Information (identified by operation id):- 1 - filter(A=1)Statistics- 1 recursive calls 0 db block gets 8 consistent gets 0 physical reads 0 redo size 459 bytes sent via SQL*Net to client 400 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed由于我们没建索引,所以这个查询走了全表扫描,产生了8个CR GET。下面我们通过UPDATE字段使数据块产生行迁移:update test1 set c=123456789012345678901234567890abcdefghg;commit;这个时候我们再来看数据块中发生了什么:我们看到了部分原来是数据行的地方变成了一些类似20 02 00 02 80 01 f7 00 04这样的数据,这就是发生了行迁移,这一行已经被迁移到RDBA=0x028001f7的数据块中的0x04这 一行中了。这个时候如果访问这条数据,就需要再到0x028001f7做一次查询才能完成。我们再来执行刚才的SELECT语句,看看发生了什么变化:SQL set autotrace on;SQL col b format a50 truncSQL set line 132SQL select a,b from test1 where a=1; A B- - 1 abcExecution Plan-Plan hash value: 4122059633-| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |-| 0 | SELECT STATEMENT | | 2 | 12 | 2 (0)| 00:00:01 |* 1 | TABLE ACCESS FULL| TEST1 | 2 | 12 | 2 (0)| 00:00:01 |-Predicate Information (identified by operation id):- 1 - filter(A=1)Statistics- 0 recursive calls 0 db block gets 17 consistent gets 0 physical reads 0 redo size 459 bytes sent via SQL*Net to client 400 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed我们看到由于行迁移,原本8个CR GET变成了17个CR GET,看样子行迁移的负面作用还是挺明显的。当然这个例子是个特例,做了全表扫描,但是行迁移会产生负面 影响是肯定的。了解了行迁移的产生原因,以及危害性后,我们再回过头来认真考虑一下PCTFREE这个参数就会发现,这样一个小小的参数,里面包含了数据库优化的大道理。如果一张表中的数据插入后 经常需要进行UPDATE,那么我们必须要把PCTFREE参数设置的大一些,以避免行迁移的出现。如果一张表插入数据后不做修改和删除,那么我们是不是可以把PCTFREE设置的小一些,比如5,甚 至更小,这样的话这张表的每一个数据块中可以包含更多的记录,从而减少访问这张表带来的开销。而如果有一张表,数据块的热块冲突严重,我们也是否可以 通过加大PCTFREE来减少每个数据块中的记录数,从而缓解热块冲突呢?实际上,减少热块冲突的更好的办法是将这张表放 在B LOCK_SIZE较小的表空间里,不过在实际生产环境中 我们往往难以这么幸运,当发现热块冲突存在的时候,我们只能通过权宜之计来解决问题了。在调整PCTFREE的时候,我们要注意的是,这个参数是可以动态调整的,但是我们调整PCTFREE参数只能对新的数据插入起作用,对于已经填充过满的老数据块,是无法起作用的,要想彻底解决行迁移的问题,必须调整参数后,对表进行重组,才能 对表中的所有数据块都起作用。对表重组的办法有很多,比如ALTER TABLE . MOVE 或者EXP/IMP。有几个参数 可能随着Oracle数据库版本的变化逐渐的逝去了,不过那些参数可能承载了很多老DBA的荣誉和苦泪。它们是pctused、freelists、freelist groups。 随着自动段空间管理(ASSM)在9i的推出,现在新的数据库很少还 在使用手工段空间管理(MSSM)。自动段空间管理和手工段空间管理最大的区别于空闲块的管理上,自动段空间管理出现之前,Oracle的空闲块是通过FREELIST机制来管理的,表和索引在被创建的时候必须指定PCTFREE、PCTUSED、FREELISTS、FREELIST GROUPS 等参数。一个被格式化后的数据块被挂在FREELIST上,这样需要插入数据的前台进程可以在FREELIST上找到可以插入数据库的 块,插入相关的数据,数据插入后,如果数据块中的空闲空间小于PCTFREE指定的比例,那么 这个数据块就不能再次被插入数据了,这个数据块将从FREELIST上摘除,下次插入数据的时 候,就会插入到其他的数据块中。这个数据块中如果有数据被删除后,空闲空间低于PCTUSED的指标,那么这个数据块将会再次被放到FREELIST上,这样这个数据块就可以 再次插入数据了。有人可能会有疑问,一个比较“满”的数据块,如果删除了数据,那么不就马上可以被插入数据了吗?这个时候马上就放回FREELIST上不就行了,还用PCTUSED干什么呢?实际上Oracle的这个设计是十分优秀的,如果不设计PCTUSED个参数,我们会碰到这样的情况,一个数据块刚刚插入数据后被从FREELIST中摘除,马上又因为删除数据被再次放入FREELIST,那么就可能会产生抖动,FREELIST的性能就会因为频繁重组而急剧下降。同样的道理,在一个MSSM管 理的段中,如果PCTFREE和PCTUSED两个参数设置的不合 理,那么这种抖动现象还将会出现。比如说我们设置PCTFREE为30,设置PCTUSED为65,如果行长度较大的话,如果一个数据块中刚刚插入一条记录后马上又删除了2、3条记录,这种情况下,这个数据块 可能出现刚刚被从FREELIST上摘除,就马上被再次放入FREELIST的现象。从对MSSM管理空块的方法,我们可以看到设置合理的PCTUSED参数的重要性,如果PCTUSED参数设置过高,可能导致FREELIST的抖动,如果PCTUSED参数设置过低,可能导致数据块中存储的记录数过少,影响全表扫描操作的性能。讨论完PCTUSED我们再来看看另外 一个重要的段空间管理相关的参数FREELISTS,FREELISTS是用于存放可插 入数据的数据块的,FREELISTS存在于perment data segment、temporary data segment、index segment、rollback segment中, 一个段中有几种类型的FREELISTS:l MASTER FREELISTSl PROCESS FREELISTSl TRANSACTION FREELISTS一个段中只有一个MASTER FREELISTS,这个FREELISTS在段创建的时候 就创建了,段中的高水位以下的空闲块都挂在MASTER FREELISTS上。当某 个前台进程需要插入数据的时候,就可以到MASTER FREELISTS上去查找 空闲块,查找操作从FREELISTS的头部开始,直到好到可以插入数据的块为止。明眼人看到这里马上就会看出问题来了,FREELISTS是一个串行的机构,如果有大量并发的会话需要插入数据的话,FREELISTS就会成为一个瓶颈。实际上ORACLE对此也早有对策,Oracle存在另外一种Freelists:Process Freelists。Process Freelists和Master Freelists是 一种主从协同机制,每个Process Freelists上链接了一组空闲块,这些空闲块是从Master Freelists上摘取的,前台进程要插入数据的时候,首先必须锁定一个Process Freelists,然后从这个Process Freelists上查找一个空闲的数据块用于插入数据。当Process FreeLists上也没有可用的空闲块的时候,会锁定Master Freelists,从中分配一组空闲块到Process Freelists上,然后再从Process Freelists上分配空闲的空间。听起来似乎很不错,Oracle的Process Freelists的设计避免了Freelists成为并行插入操作的瓶颈。不过大家要注意的是,Oracle缺省的Freelists参数值是1,在Freelists参数为1的时候,段头中只有一个Master Freelists,而不存在我们希望的Process Freelists。不幸的是,绝大多数用户在使用MSSM的时候并没有设置Freelists参数,他们并没有留意到这种设置带来的负面影响。这种负面影响到底有多大呢?老白遇到过的一个最为极端的案例是一个移动公司的短信平台,这个平 台每天需要插入的数据量在几千万条记录到几亿条记录,由于Oracle数据库使用的是8i,因此只能使用MSSM, 在这个平台上我们进行了一个测试,在8个并发插入进程的情况下,如果FREELISTS设置为8,性能比FREELISTS设置为1 提高了5.7%。在一半的情况下,可能 性能提升没有这么明显,不过如果Freelists参数设置不合理,造成1-5%的 性能影响还是很可能的。Freelists的机制设计的确实比较巧妙,似乎我们的所有问题都已经被解决了,不过细心的读者还会有一些疑问,那 些已经标志为“满”的块怎么再次回到Freelists上呢?难道只要数据被删除了,块使用率低于PCTUSED,这个数据块就马上被放到Freelists上吗?这么做似乎也不太稳妥,一旦这个删除操作回滚了,那么岂不是又要再次把这个块从Freelists上摘除吗?实际上Oracle早就为这种操作设计了算法,这就是Transaction Freelists。 一个段缺省有16个Transaction Freelists,如果某个事物要修改这个段,首先会搜索Transaction Freelists, 如果找到了这个会话的Transaction Freelists,那么就继续使用,否则这个会话就会 继续找一个空闲的槽位,如果找不到空闲的槽位,这个会话就会试图去扩展一个新的Transaction Freelists,如果要扩展的时候,正好Segment Header中已经没有空闲空间了,那么这个会话就只能等待其他会话提交后腾出空闲的槽位。这种等待有点类似于我 们前面说到的事务槽的等待,不过事务槽的等待产生于数据块中,而Transaction Freelists的等待是存在于Segment Header中。如果我们使用MSSM, 并且Segment Header的等待较为严重的话,我们就应该检查检查是否是由于这个原因引起的等待。当某个会话申请到Transaction Freelists后在某个数据块中删除了一些数据,这个数据块的使用率低于pctused参数后,这个数据块就被挂到Transaction Freelists上了,这个时候这个数据块可以被插入数据了,不过由于这个数据块只是在本会话的Transaction Freelists上,因此在这个事物提交之前,只有本事务可以在这个数据块中插入数据,只有等这个事物提交后,Transaction Freelists上的数据块才会被挂到Master Freelists的尾部,这个时候其他的会话也可以使用这些空闲块了。是不是很巧妙的算法?Oracle通过这三种Freelists,十分好的解决了插入数据时的性能问题,将Freelists机制的瓶颈降到了最 低。对于MS
展开阅读全文