Oracle逻辑体系

上回我们主要是了解oracle的物理体系,我们可以清楚地看到数据文件,参数文件,控制文件等等文件的大小和存放位置,后台进程是如何被唤起而又任何退出的。

接下来我们来说说逻辑体系,偏抽象一点,不过这部分是重中之重,可以加深我们对体系的理解。

从上图不难看出,数据库(database)是由若干表空间组成,表空间(tablespace)由若干段组成,段(segment)由若干区组成,区(extemt)又是由oracle最小单元块(block)组成的。是不是感觉有种层层套娃的感觉。别急,我们接着往下说,其中表空间又分为系统表空间,用户表空间。回滚段表空间,临时表空间,除了用户表空间,其他表空间都有各自的用处,不能随意更改和破坏。

着重理解块的概念

块是oracle中最小的逻辑数据单位,我们就先从块下手,了解块的结构。

一般情况下操作系统的块容量为512字节或者其整数倍,而数据库一般被默认为8kb,可以通过该语句show parameter db_block_size查看,数据库的block一般要设置为操作系统块容量的整数倍,这样可以减少IO操作。具体原因我们来做以下分析:

假如IO大小设置为512字节,数据库的block为1kb刚好是其两倍。但如果设置为0.8kb,由于操作系统的单个块大小为0.5kb,这样就需要两个操作系统块才放得下,相当于占用了1kb的空间,浪费了0.2kb的空间。

下面我们重点来解析块的结构,如下图所示,一共有五个部分,我们来分别说明下:

数据块头:包括了此数据块的概要信息,例如块地址及数据块所属的段的类型。

一.永久性表空间:一般保存表、视图、过程和索引等的数据。

表目录区:只要有一行数据插入到数据块中,那该行数据所在表的信息将被存储在这个区域。

行目录区:存放你插入的行的地址。

以上这三部分被称为块开销,这部分的容量平均在84字节到107字节之间。

可用空间区:该区是块中的空余空间,由PCTFREE参数设置,默认是10,代表该块将会空余10%作用的空间,这部分我们后面当饭后谈来说说,为什么要有这个空余空间的存在,万物存在即合理,这里先不说。

行数据区:这里存储的是具体的行信息或者索引的信息,这部分占用了数据块绝大部分的空间。

区与段

理解了段,对于区就很好理解。连续的数据块组合在一起就形成了区。Oracle中这个被称为区的数据库逻辑存储分配单位就是这么形成的。

当用户创建了一张表时,实际上就是创建了一个数据段segment,只要数据段创建成功,数据库就一定为其分配了包含若干数据块的初始数据扩展(initalextent),此时表中还没有数据,但是这些初始化数据扩展中的数据块已经为即将插入的数据做准备了。

接下来往表中插入数据,很快数据扩展中的数据块就装满了,需要写入新的数据时,ORACLE会自动为这个段分配一个新增数据扩展,这个新增的数据扩展块容量大于或等于之前的数据扩展,这一点我们后面来说说为什么。

表空间的分类

一系列的段组成了表空间,表空间主要分为以下三种:

语法为:create tablespace 表空间名 datafile '路径' size 100M;

二.临时表空间:只用于保存系统中短期活动的数据。

语法为:create TEMPORARY 表空间名 TEMPFILE '路径' size 100M;

三.回滚表空间:用来帮助回退未提交的事务数据。

语法为:create UNDO 表空间名 datafile '路径' size 100M;

做实验来深入了解一下整个过程

我们来创建一张表,然后往里面插入数据,来探索一下区和段的变化。

create table t(in int) tablespace tbls_lib;
select t.segment_name,t.EXTENT_ID,t.tablespace_name,t.BYTES/1024/1024,t.BLOCKS from user_extents t where t.segment_name='T';

刚创建的表只有一个区,也就是我们之前说的初始数据扩展。

insert into t select rownum from dual connect by level<=1000000;

插入数据之后我们来看一下现在区的使用情况。1~16号区,大小为8个块64KB,从第17号区开始,区大小变为了128个块1MB大小。如果我们再进行插入数据,区的大小会继续加大,在这里我们就不做演示了。也就是说,表的大小小于1MB时,表的每个区都是64KB,当表的大小超过1MB,再分配新区时,区的大小将是1MB。也就是我们上面说的新增的数据扩展块容量大于或等于之前的数据扩展。这里我们来探索一下原因。

空间的利用率上讲,小区节省空间,大区可能会浪费空间。比如,当区大小是10MB时,为一个表分配了一个10MB的区,哪怕它只使用了这10MB中的1个字节,这10MB空间也完全属于这个表了,其他表无法再使用这部分空间。从这个角度上讲,小区的空间利用率无疑是高的。但从性能角度上讲,对于随机访问,大区、小区没有影响。但对于全表扫描这样的操作,大区又是更合适的。因为连续空间更多,可以减少磁头在区间的定位。

在系统管理区大小的方式下,当表比较小时,区也比较小,当表大时,区也随之变大,这种方式无疑可以在空间的利用率、全扫描的性能之间找到一种平衡。因此建议大多数情况下,都可以采用系统管理区大小的方式。除非有某个表,已明确地知道它会很大,为了保证全扫描的性能,直接建一个统一区大小,并且区比较大的表空间,以便将表存放其中。

很多数据库都使用统一区大小,而且其大小为1MB。原因是在大部分的操作系统中,一次I/O操作的最大的读、写数据量是1MB。即使使用8MB的区,一个区也必须分8次进行I/O操作,超过1MB的区大小,并不能减少I/O操作的次数。

但是,我们要考虑一点,8MB的区连续的空间更多。读取8MB内的第1MB和第2MB数据虽然必须要分两次I/O操作,但这两次I/O操作很可能是连续I/O,因为第1MB和第2MB数据有可能是相连的。如果区大小仅为1MB,虽然读取表的第1区和第2区也是两次I/O操作,但这两次I/O操作很可能不相连,是随机I/O操作。连续I/O操作的性能当然比随机I/O操作的要高。

因此,出于全表扫描性能的考虑,即使使用统一区大小,大点的区(如8MB大小)是很合适的选择。

接下来让我们看看段在数据库中的变化

select t.segment_name,t.segment_type,t.tablespace_name,t.extents,t.BYTES/1024/1024,t.BLOCKS from user_segments t where t.segment_name='T';

插入数据后

逻辑结构之二次体会

上面我还有说到块的大小默认为8KB,那能不能修改为16KB,32KB呢?答案是可以的,不过是要在建表空间时指定,我们不可能更改原有的已经建好的表空间。

下面我们来学一下怎么设置指定块为16KB,操作如下:

show parameter cache_size;        --查询设置
alter system set db_16k_cache_size=100M;    

当你把db_16k_cache_size设置为100M时,意味着SGA中的Data Buffer数据缓存区将会有100MB的大小让内存块可以以16KB大小进行访问,同时也意味着16KB大小的设置此处生效了。

然后创建表空间时,切记加上blocksize 16K关键字。示例如下:

create tablespace TBS_LJB_16k blocksize 16k datafile '路径' size 100 M autoextend on;
select tablespace_name,block_size from dba_tablespaces where tablespace_name='TBS_LJB_16k';

上面我们有讲到区的大小是随着插入数据改变而改变,小于1MB是是0.0625MB,大于1M时,是1MB。那么我们能不能自己设置固定呢?答案是肯定的。

大家记下uniform size这个参数,这个表示统一尺寸。

create tablespace TLB_LJB2 datafile '/home/ORADATA/TBS_LJB_02.dbf' size 100M extent management local uniform size 10M segment space management auto;
create table test(id int) tablespace TLB_LJB2;
select t.segment_name,t.EXTENT_ID,t.tablespace_name,t.BYTES/1024/1024,t.BLOCKS from user_extents t where t.segment_name='TEST';

查看表空间的情况

接下来我们来学习一下怎么查看表空间的使用情况之类,后面会单纯整理成一篇笔记,我们先来简单了解一下即可。

select sum(bytes)/1024/1024 from dba_free_space t where t.TABLESPACE_NAME='TBS_LJB';   --查看某个表空间的剩余大小
select sum(bytes)/1024/1024 from dba_data_files t where t.TABLESPACE_NAME='TBS_LJB';   --查看某个表空间的整体大小


--查看整体表空间使用情况
SELECT UPPER(F.TABLESPACE_NAME) "表空间名",     
D.TOT_GROOTTE_MB "表空间大小(M)",     
D.TOT_GROOTTE_MB - F.TOTAL_BYTES "已使用空间(M)",     
TO_CHAR(ROUND((D.TOT_GROOTTE_MB - F.TOTAL_BYTES) / D.TOT_GROOTTE_MB * 100,2),'990.99') "使用比",     
F.TOTAL_BYTES "空闲空间(M)",     
F.MAX_BYTES "最大块(M)"    
FROM (SELECT TABLESPACE_NAME,     
ROUND(SUM(BYTES) / (1024 * 1024), 2) TOTAL_BYTES,     
ROUND(MAX(BYTES) / (1024 * 1024), 2) MAX_BYTES     
FROM SYS.DBA_FREE_SPACE     
GROUP BY TABLESPACE_NAME) F,     
(SELECT DD.TABLESPACE_NAME,     
ROUND(SUM(DD.BYTES) / (1024 * 1024), 2) TOT_GROOTTE_MB     
FROM SYS.DBA_DATA_FILES DD     
GROUP BY DD.TABLESPACE_NAME) D     
WHERE D.TABLESPACE_NAME = F.TABLESPACE_NAME     
ORDER BY 4 DESC; 

表空间的大小是有限的,当大量插入数据后,表空间满了怎么处理呢?这个我单独作为一篇文章去写了。

行连接与行迁移

当往数据库中INSERT数据的时候,块中的自由空间会减少;当对块中已经存在的行UPDATE的时候(使记录长度增加),块中的自由空间也会减少。

DELETE语句和UPDATE语句会使块中的自由空间增加。当使用DELETE语句删除块中的记录或者使用UPDATE语句把列的值更改成一个更小值的时候,Oracle会释放出一部分自由空间。释放出的自由空间并不一定是连续的。通常情况下,Oracle不会对块中不连续的自由空间进行合并。因为合并数据块中不连续的自由空间会影响数据库的性能。只有当用户进行INSERT或者UPDATE操作,却找不到连续的自由空间的时候,Oracle才会合并数据块中不连续的自由空间。

行链接

如果我们往数据库中插入(INSERT)一行数据,这行数据很大,以至于一个数据块存不下一整行,Oracle就会把一行数据分作几段存在几个数据块中,这个过程叫行链接。如果一行数据是普通行,这行数据能够存放在一个数据块中;如果一行数据是链接行,这行数据存放在多个数据块中。

行迁移

数据块中存在一条记录,用户执行UPDATE更新这条记录,这个UPDATE操作使这条记录变长,这时候,Oracle在这个数据块中进行查找,但是找不到能够容纳下这条记录的空间,无奈之下,Oracle只能把整行数据移到一个新的数据块。原来的数据块中保留一个“指针”,这个“指针”指向新的数据块。被移动的这条记录的ROWID保持不变。

行迁移和行链接都会导致Oracle性能下降,解决行链接和行行迁移最好的方法就是数据重建,就是创建一个中间表,drop原本表,再把中间表改名为原本表alter table table1 rename to test2。

写到这里,oracle的逻辑体系可以说暂时结束了,之后会继续查漏补缺。