oracle数据库的物理存储结构
1、spfile:参数文件
2、controlfile:控制文件
3、datafile:数据文件
4、redo log
5、arch:归档日志
oracle数据库的datafile(数据文件)
datafile:oracle有多个数据文件
图解:
数据库的数据文件被格式化成一个一个大小相等的block(2k、4k、8k、16k、32k),一般都是8k
block(数据块)
1、数据文件被格式化成一个一个大小相等的block;
2、是IO的最小单元
表空间
表空间:一个或者多个数据文件组成的一个组;如果表空间不够了,可以向它加入数据文件;如果一个表非常大的话,一个数据文件就存储不了,就需要多个数据文件来存储,因为数据文件的大小是有限制的,所以有了一个表空间的概念,如果一个数据文件存储不了了,可以向表空间里面扩充多个数据文件
图解:
一个或者多个数据文件组成一个表空间,将来在表空间里要建一个表,这个表可能被建在不同的数据文件上(数据文件4、数据文件5或者数据文件4和数据文件5上...)
表是建在表空间上的
oracle数据库表空间的类别
1、system表空间(系统表空间):放着字典表(核心的字典表)
2、systemaux表空间(系统辅助表空间,oracle版本10g开始):放着辅助的字典表(存着历史性能信息)
3、temp表空间(临时表空间):放着临时数据
4、undo表空间:放着修改过的数据(比如:将1改为了2,就把1存到undo表空间里,避免操作失误的时候可以rollback回来,把2再改回1)
除了这些系统相关的表空间以外,我们还可以建一些别的表空间
oracle数据库支持表空间级别,block的大小不一样的情况(建议不这么做),这种情况需要在buffer cache里面有相同大小的buffer与其对应:8k的block对应8k的buffer,16k的block对应16k的buffer,32k的block对应32k的buffer
段区块
图解:
一个表空间,现在要在表空间1(tb1)上建一个表(t1),从存储的层面上来说,我们要建的表(t1)叫做表段(我们建了一个表,就是说我们建了一个段(表段));从存储的层面上来讲,一个表是一个段,一个索引是一个段;
现在要在tb1上建一个段t1,在oracle版本10g的时候,在建t1的时候,先分配几个物理上连续的块(就是一个区)(比如分了4个块);如果不分的话,一个一个的给,这时候就会出现一个问题:此时在tb1上又建了一个表t2,这时候第一个块给t1,第二个块给t2,......就会出现在一个表空间里,里面有好多表,这些表参差不齐,块之间互相交叉;将来如果对t1做全表扫描的话,只能先读第一个块,然后隔一个才能读下一个块,就会发生多次IO读;如果一次性分4个块给t1,全表扫描的时候就可以一次性读完,只发生一次IO读,基于这个,oracle提出了一个区(extent)的概念
段(segment)分为
1、表段
2、索引段
区(extent)的概念
1、物理上连续的多个块,是一个区(extent)
2、区(extent)是段分配空间的最小单位
段区块的关系
图示:
一个extent区由多个块组成,一个段由多个区组成,一个表空间有多个段,一个表空间由多个数据文件组成
段区块的管理
有两个管理:
1、区的管理(表空间的管理)
两种方式:
1、local(本地)
2、字典
2、段的管理
两种方式:
1、manual(手动)
2、auto(自动)
区(表空间)的管理
图解:
现在在表空间里面建一个表t1,首先:分配区,在表空间里面找空的extent区;t1表需要8个block,fet表的第二行记录着表空间里面的第二行有9个空的block,就把这一行的8个block给t1,之后fet表的第二行的记录还在,但是是记录着表空间里面的第二行有1个空的block,uet表里面就加上对应那一行被用的block的数量
区的管理方式(表空间的管理方式):
在表空间里面,对extent区进行空间的记录(哪些区用了,哪些还没有用)
1、字典管理方式
oracle在数据库的system表空间里面记录两张表:fet表(free extent表)、uet表(used extent表)
对于字典的管理方式,第一、需要写SQL来找空的extent;
第二、需要修改两张记录表(fet表:delete或者update,uet表:inster),还会产生redo、undo
这种方式的坏处:
1、需要8个block,一行有9个block,还剩下1个block,产生了小碎片;
2、使用SQL来访问,性能比较差
3、随着时间的推移,小碎片会越来越多,有一个进程smonitor,会将在一块的小碎片合并(由于时间的不同,相邻的block,记录的信息也不在一块,有的记录在表的前面,然后跟它相邻的block的信息因为时间的关系被记录在表的后面,就出现的虚块的现象,就是说现在需要16个block,前面记录了8个block,后面记录了8个block,就找不到一个16个block的空间),因为合并,需要把fet表全表扫描一遍,又因为随着时间的推移,小碎片越来越多,所以smonitor清理起来很麻烦,会造成smonitor会hang住的情况:
图示:
2、local本地管理方式
每一个数据文件里面有很多的extent,每一个数据文件,自己管理自己里面的extent
图解:
在每一个数据文件的头部,找出六个块来,64kbit的空间来记录extent:被分配的extent记录为1,没有被分配的extent记录为0,一个位表示一个extent(8个block),这样,要找一个空的extent(8个block),就在数据文件的头部里找,找一个0,如果要找一个16个block的extent,就找两个0的,这样的好处:
1、不需要使用SQL了,找到空的extent要分配,分配的时候,只要把0改为1就行了,不管是找空的extent,释放extent,还是修改记录,都是一些位操作了
2、要找16个block时,找两个0的就可以,也不存在虚块的现象,也不会产生小碎片,也不需要合并了
区的管理方式:建议使用local本地管理的方式
段的管理
图解:
一个表t1(8个block),分配了一个区,那么这个表怎么用这个extent里面的block?
要在t1里面做inster,插入一行数据(占800bit),第一个块:段头块,里面记录着相关段的信息,里面不能存放数据,第二个开始做inster,现在因为第二个块是空的(8k),做inster显然空间够;但是随着时间的推移,每一个块里都有好多行,当然还有就是这个extent用完了的情况;现在假设用了六个块,然后中间的块,删了一些行,然后每个block还剩一些空间,这时候对t1表做inster(1000bit),oracle的需求是:在这已经使用的六个block里面,需要一个能够容纳1000bit的block;每一个块是8k,需要1000bit,就是需要一个块大约13%的空间,所以只要剩余的空间大于13%,都可以用。
对于区(表空间)来说,段里面所有的extent用完了,需要新的extent的时候,才涉及到区(表空间)的管理;一个段被分配了很多区了,无论做inster、update、delete,只要不需要新的extent就不会涉及到区的管理;所以DML操作绝大部分不会涉及到区的管理(表空间的管理);
但是对于段来说,无论做inster、update、delete,每一次的DML操作,都会涉及到段的管理;所以如果段的管理方式选错了,对DML的影响还是很大的;
oracle怎么记录一个段已经分配给extent里面的每一个block的空间使用情况呢?两种方式:
段的管理方式:
1、manual管理方式:
pctfree(空闲百分比)
这里有一个参数:pctfree(空闲百分比)(默认是:10%)
一个block,要做inster,插入数据,oracle会尝试着在block里面尽量的留大于等于10%的一个空闲空间作为pctfree,但是也有可能小于10%,当block的空间剩余10%的时候,就说这个block满了,用的话,就要用下一个block了(也就是说,当一个块的空闲空间大于10%的时候,还有空闲空间;空闲空间小于10%的时候,就没有空闲空间了)
图解:
oracle会在段头块里面记录着一个地址,指向后面的第一个空闲空间大于10%的块的地址,第一个空闲空间大于10%的块也记录着下一个空闲空间大于10%的块的地址,以此类推;
然后在t1里面做inster,假设要插入2000bit,大约空闲空间25%,然后找空闲空间大于25%的块,段头,然后找,找到第一个有空闲空间的块,剩余80%,满足了,就用它;然后剩余55%了;
在做inster的时候,oracle会锁住t1这个表的段头块,锁住以后,遍历free list(链表),找空闲空间;
现在server process1在对t1做inster,这时候,server process2也要对t1表做inster,这时候,t1这个表的段头块已经被锁住了,就会产生free list争用,就会产生等待事件;
所以oracle就会做一件事:使用多个free list,每个链表链一部分空闲空间,减少一部分free list征用
段管理使用manual的话,就使用free list链表
使用free list(链表)来管理这些空闲空间大于10%的块,将这些空闲空间大于10%的块链起来
pctused(使用百分比)
这里有一个参数:pctused(使用百分比)
图解:
现在有一个链表,现在要做inster,插入数据,假设对第二个块做inster,inster的话,第二个块的空闲空间就小于10%了,然后把它拿下来了;然后删除一行之后,又得挂上去;拿下来,挂上去就需要锁住链,然后去修改,所以就出现一个问题:插入一行,拿下来,修改链表;删除一行,挂上去也要修改链表,所以oracle就定义了一个参数:pctused;
假设pctused = 40%,inster以后,空间剩余8%,被拿下来了;delete一行,空间剩余又变回12%了,这时候还不把它挂上去,又删了一行,剩余22%,这时候也还不把它挂上去,直到空闲空间大于等于40%才挂上去
2、auto管理方式:
图解:
oracle在前面的这些块,又拿出一部分块来,然后在下面组成新的块;这部分使用位图的方式来记录下面组成的新块,使用位的方式来记录下面的块的空间的使用情况;假设以后要在这个段里面找一个1000bit的空闲空间,只需要读位图就可以,因为在位图里面以位的方式记录着所有块的空间使用情况,计算机对位的操作是最擅长的,这里位的方式也不存在锁的概念;
假设现在要找一个1000bit的空闲空间,pctfree = 10%,block = 8k,需要大约23%的空闲空间,就找空闲空间大于等于23%的块,就满足这个需求;现在找,找到一个块剩余30%,可以满足,再找,找到一个剩余24%的块,它就用剩余24%的,因为用剩余30%的产生小碎片,用24%的刚好,然后如果有23%的,那么更好;
使用位图的方式的好处:
1、计算速度快
2、找的时候可以找到正好的,而manual方式是找到满足需求的就用,不管剩余空间有多大
段的管理方式:建议使用auto的方式
oracle数据块(block)的结构
图解:
数据块有:块的头部,从上往下记录,下面是数据行,从下往上存储数据,中间是free;
有一个表t1插入一行数据:第一个字段数据:num,第二个字段数据:varchar(60),第三个字段数据:varchar(40),......
插入数据: 1 abc xkj
在数据块里面怎么存的这些数据呢?
一行,头部留出一个字节来,然后后面:第一字段列的宽度9,后面跟着存上1,再接着第二字段列的宽度3,后面存上abc,第三字段列的宽度3,后面存上xkj;然后下一行,紧接着在后面加一个行头,列宽,值,列宽,值......
存储的时候,行与行之间是没有空隙的,是紧密连在一起的
访问块的时候,怎么访问的呢?
在块的头部,有一个行目录;假设这个块有10行,行目录里面记录着这10行,每一行的起始位置;找的时候,通过行目录找到行头,再找到列的位置取出数据;现在要做update,将xkj改为xkjagr,这时候,就要更改这一列的宽度,将3改为6,这一列数据之后的所有数据就要往后挪3个字节,将列宽改为6,这时候数据行的空间不够了,就要压缩free部分的空间,所以在inster的时候,不希望被挤出这个块,就使用pctfree = 10%,让它有一个预留的空间来存数据
行地址(行迁移)
在这里,每一行有一个行地址(rowid),oracle数据库里面,默认的每一行的行地址(rowid)是不能改变的;如果数据往后挪的时候,挤出一行来了,但是行地址是不能改变的,这时候就会出现行迁移(一般都是update造成的),就是说,假设现在要update一行数据,free就只有10%的空闲空间,但是对于要更新的这一行来说,10%的空闲空间不够用,oracle就会把这一行拿出来放到另外一个块里,然后,这一行原来的位置不变,在这个位置上记录着被迁移到另外一个块这一行的地址
行地址(rowid)由三部分组成:
1、文件号
2、块号(文件里面哪个块)
3、行号(文件里面第几行)
rowid是一个物理地址,可以快速的找到对应的数据行
行链接
假设,现在一个block是8k,数据库里一个表里的一行是12k,然后oracle将一行12k的数据分别存在两个块里面,一个块里面存6k,然后第一个块里面还存着在另外一个块里面的行地址
行链接是因为行数据太大,而block的空间太小,所以要使用多个block存储
行迁移和行链接都不是很好,访问性能会降低
高水位线
图解:
一个段(t1),分配了多个区,第一、二个区都用完了,第三个区用到第三个块,然后前面的块,空间大小可能不一样,有的剩余80%,有的剩余10%,有可能还有空块(被删除了),这里面的块有的空的,有的满的;然后这个区的最后一块的地址会被记录在段头块里面(表示这个段,最后一个块的地址),这个地址叫做高水位线
高水位线的含义:
假设将t1里面所有的数据全删了,然后提交,里面所有的块都是空的,但是高水位线还在那个位置;对t1插入数据的时候,高水位线往上涨,但是删除数据的时候,高水位线不变,还在原来的位置;现在t1是空的,对t1做select:select * from t1;全表扫描t1,全表扫描就是对高水位线以下所有的块扫描一遍(高水位线对全表扫描的行为影响比较大),现在t1是空的,但还是要扫描所有的块,因为高水位线还在那个位置,所以有时候我们就需要对t1做一个segment shrink(段收缩),就是将高水位线以下那些有空闲空间的块收缩,把数据收缩到一起(假设收缩到了三个块里),让它们之间没有空闲的块,然后将高水位线降下来,然后再select * from t1;的时候,就只需要访问这三个块了;
但是对一个段DML很频繁的情况下,段收缩的效果不是很好;还有一个命令:move命令,也是段收缩,这个效果很好,但是风险最大;还有就是truncate命令,就是将整个表清空了,也就是将表的段头块进行了修改,修改了高水位线,但是其实数据没有被删除,数据还在,只是高水位线指在了第一个块的位置