从块结构谈表的存储参数与性能之间的关系

摘要:自从开始在微信公众号写点小东西,老白每天早上到公司的第一件事是洗手、泡茶、看方方的武汉日记,然后就想今天写点

自从开始在微信公众号写点小东西,老白每天早上到公司的第一件事是洗手、泡茶、看方方的武汉日记,然后就想今天写点啥呢。确实可以写的东西太多,多到经常让我想不起来写点啥好。很多内容是头天晚上就构思好的,写起来也比较顺利,不过也有很多时候是坐在电脑前发呆的时候突然想到的,就像今天一样。

今天我想和大家分享的是“表”,可能有很多朋友已经知道“表”是什么了,但是对表的定义可能不一定很清楚。Oracle数据库是一种关系型数据库,关系型数据库里,一组关系的实体化就可以组成一张表。表里面有一个或者多个字段,这些字段按照某种规则(我们称为关系)组成一个集合,每个关系我们就称为一行数据,也就是我们常说的一条记录。实际上只要你已经有了关系型数据库的概念,那么就不需要我多说表、记录、关系之类的术语了。实际上表是oracle数据库中十分重要的对象,实际上DBA很大部分的工作就是和不同的表,不同的数据打交道。

Oracle数据库的表是怎么存储的呢?要想了解这个就需要学习一些oracle数据存储中的基本概念。首先我们要了解的是BLOCK(块),BLOCK是Oracle访问的最小物理单位,oracle数据库的BLOCK的大小一般是2K-32K,因此Oracle的块大小和操作系统的块大小是不同的,操作系统的块大小往往要远小于Oracle块的大小,因此Oracle的块不是基本的IO单位,而只是Oracle的基本访问单位。对于Oracle来说每个表空间都有唯一的块大小,Oracle8i或者更早版本的数据库,整个数据库的所有表空间都有唯一的块大小。从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  一张分区表可能存储在N个独立的段中,这些段甚至可能存储在不同的表空间中

l  在一个CLUSTER(簇)中,可能存储多张表,而不是一张表。因此存储于簇中的表,表和SEGMENT不是一一对应的

l  一张索引组织表可能包含了多个段,比如OVERFLOW段

l  一张表中,如果有LOB字段,那么LOB字段可能存储在独立的段中,那么这张表也可能包含多个段

通过上面的介绍大家知道了表的数据实际上是作为“段”的形式存储在表空间里的。当创建一个表的时候,Oracle自动会为这张表创建段,并分配第一个扩展(当然,从11g开始,可以通过参数设置创建表的时候是否创建一个初始段)。我们已经知道了每个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在处理这种情况的时候,会把这条记录整个迁移到另外一个存在足够空闲空间的数据块中,而在原来记录的地方登记一个指向新位置的指针,这种情况就是我常说的行迁移。行迁移的产生,会加大系统的开销,影响对数据访问的性能。谈到行迁移,又想起另外一个概念-行链。行链是行迁移的表弟,如果一个数据块是4K,而经常有些记录的长度超过4K,无法在一个数据块中存储,那么这条记录就会被分成两部分,存储在两个数据块中,访问这条记录的开销就比访问普通的数据大了不少。所以对于大型的宽表,我们需要通过存储在更大块大小的表空间中,从而避免出现较多的行迁移现象。

前面我们已经了解了过多的技术细节,可能有些人要呼呼大睡了。实际上,下面才是我们今天要学习的精华。对于一张表,我们在设计的时候要注意一些什么呢?从我们今天学到的关于段的知识,我们可以得到以下的结论:

  •  创建表的时候,选择合适的EXTENT大小是十分重要的,过小的EXTENT大小会导致过多的扩展,影响数据插入的性能。这对于存在大量并发插入的情况尤为重要,这个规则在早期的数据库设计中十分重要,随着现在自动段管理技术的发展,Oracle数据块已经能够很好的对段进行管理了,不过如果我们使用的不是Oracle数据库,这方面还是要考虑的;

  •   设置合理的PCTFREE也是十分重要的,老白碰到的客户中,能够根据表和业务的特点来设计PCTFREE的情况少之又少。不合理的PCTFREE可能导致大量行链的出现,影响访问性能。如何来规划PCTFREE参数呢?如果一张表插入后基本上不修改,只是用来查询,那么这张表的PCTFREE尽可能少,少于5,甚至设置为0都是可以的,如果一张表中存在大量的VARCHAR字段需要UPDATE,字段长度可能出现较大的变化,那么PCTFREE设置大一些是十分必要的;

  • 对于可能会出现大并发访问的表,为了避免过多的热块冲突(BUFFER BUSY WAIT),可以使用较大的PCTFREE,甚至高达50%;

  • 对于经常做增删改的表上的索引,设置大一点的PCTFREE有助于减少索引分裂导致的问题;

  • 对于写并发量较大的表,设置大一些的Initrans参数有助于避免ITL锁冲突的出现;

  • 对于一些极端的系统,为了提高性能,使用不同块大小的表空间来存放不同的数据是一种比较好的设计,比如容易出现热块冲突的索引存储在4K表空间里,普通数据存储在8K表空间里,经常做全表扫描的统计数据存储在16K的表空间里,BLOB/CLOB等大字段存储在32K的表空间里。这种设计需要开发商有较高的设计能力,不过如果能很好的使用,效果会十分不错;

 

 

一不小心又写了3000多字了,关于这个话题,还有很多可聊的,我们下次再说吧。

 

原文链接:https://www.modb.pro/db/22713

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值