Oracle内核技术揭密. 1.1 区:表空间中的基本单位

1.1 区:表空间中的基本单位

区,Extent,逻辑上连续的空间。它是表空间中空间分配的基本单位。如果在某表空间中创建一个表,哪怕只插入一行,这个表至少也会占一个区。

具体来讲,在Oracle 10g中,如果创建一个新表,初始至少为这个表分配一个区。而在Oracle 11.2.0.3以上版本中,创建新表时默认一个区都不会分配,也就是说,这个表此时不占存储空间。只有在向表中插入第一行数据时,才会默认为表分配第一个区。

无论是Oracle 10g还是Oracle 11GR2,如果表原有区中的空间用完了,Oracle就会默认为表一次分配一个区的空间。

可以通过DBA_EXTENTS数据字典视图查看表所属区。假设有一个表Table1,想要查询它所在的区,可通过如下方式:

select extent_id, file_id, block_id, blocks from dba_extents where

segment_name='TABLE1' order by extent_id;

上面语句中,每个列的意义都很简单,这里不再介绍,如有问题,可以查看Oracle联机文档Oracle  Database Reference中的视图介绍。

说了这么多,大家会不会有一个疑问:既然区这么重要,是空间分配的基本单位,那么,区的大小是如何定义的呢?

Oracle专门设定了两种类型的表空间:统一区大小表空间和系统管理区大小表空间。区的大小就是由这两种表空间决定的。下面,先从统一区大小讲起。

1.1.1 统一区大小表空间和区的使用规则

统一区大小的表空间理解起来很简单,顾名思义,就是创建表空间时,设定区大小为一个统一的值。如下命令创建一个区大小为1MB的表空间:

create tablespace tbs_ts1 datafile '/u01/Disk1/tbs_ts1_01.dbf' size 50m uniform size 1m;

tbs_ts1表空间包含一个50MB的数据文件,区大小为1MB。在此表空间中创建一个测试表:table1,观察一下区大小。

SQL> create table table1(id int,name varchar2(20)) tablespace tbs_ts1;

Table created.

 

SQL> insert into table1values(1,'VAGE');

 

1 row created.

 

SQL> commit;

 

Commit complete.

 

SQL> select extent_id, file_id, block_id, blocks from dba_extents where

segment_name='TABLE1' order by extent_id;

EXTENT_ID    FILE_ID   BLOCK_ID     BLOCKS

--------------------- --------------------------

0            4         128          128

可以看到,table1表目前只包含一个区,它从4号文件的第128号块开始,大小为128个块。笔者这里的块大小为8KB,128个块,正好就是1MB。

从上面的结果可以看到,表table1从4号文件的128号块开始占用空间。从128~257号块是table1的第一个区,那么,0~127号块又是干什么用的呢?

事实上,每个文件的前128个块,都是文件头,被Oracle留用了。在Oracle 10g中是0至8号块被Oracle留用。而从Oracle 11GR2开始,一下就留用128个块,真是大手笔,不是吗?

这一部分文件头又分两部分,其中0号、1号块是真正的文件头,2~127号块是位图块。而在Oracle10g中,2~8号块则是位图块。

这个位图块又是干什么用的呢?

很容易理解,是用来记录表空间中区的分配情况的。位图块中的每一个二进制位对应一个区是否被分配给某个表、索引等对象。如果第一个二进制位为0说明表空间中第一个区未分配,如果为1说明已分配;第二个二进制位对应第二个区,以此类推,如图1-1所示。

 

图1-1 位图块示意图

在图1-1中,上面的二进制位就是位图块中的数据,下面表格表示区,其中灰色区是已分配区,白色区是未分配区。第1个区对应的二进制位是1,代表此区已分配,第2个区对应的二进制位是0,表示此区未分配,等等。

位图块又分两部分,其中第一个位图块又被当作位图段头,可以在DUMP文件中找到Oracle对此块类型的说明:Bitmapped File Space Header。从第二个位图块也就是3号块开始,就是真正的位图数据了,DUMP文件中这些块的类型说明为:Bitmapped File Space Bitmap。

关于位图块的DUMP格式描述,大家可以在网上找一下,此处不赘述。下面讨论一个网上较少讨论的话题:当要分配区时,Oracle如何在位图块中搜索可用区。

大家可以考虑一个问题,如果块大小为8KB,0号、1号块是文件头,2号块是位图头,在Oracle 10g中,3~8号块是位图数据块,共6个位图块,大小是48K字节,每个字节8个二进制位,一共393216个二进制位。每个二进制位对应一个区,一共就393216个区。如果是Oracle 11GR2,这个数字又要多出好多倍。那么,如果某个表要在数据文件中分配一个新区,Oracle如何在这30多万个二进制位中,确定哪个二进制位对应的区可以分配给表呢?

Oracle用的方法其实很简单,在位图块中,找一个标记位,如果0~2号区被占用了,标记位的值为3;如果3~4号区又被占用了,标记位增加为5。假设此时2号区被释放了,标记位变为2。

如果需要分配新的区,从这个标记位处开始查找即可。假设目前标记位值为5,有进程需要4个未分配区,Oracle就从5号区开始向下查找。

需要注意的是,如果开启了闪回Drop,而且Drop了表,那么,区并不会被释放,因此标记位不会下降。因为Drop只是改名,而并不会真正删除表。

1.1.2 系统管理区大小

刚才介绍了统一区大小,并且测试了区的分配规则。在系统管理区中,区的分配规则是一样的,但是,区大小的设定不再由人为决定,Oracle会根据表大小自动设置。

Oracle如何设定区大小呢?只需要创建一个表空间,并进行很简单的测试,就能搞明白这点。命令如下所示:

create tablespace tbs_ts2 datafile '/u01/Disk1/tbs_ts2_01.dbf' size 50m reuse;

上面的命令创建了一个tbs_ts2表空间,至于区大小的管理方式,这里没有指定,这样Oracle默认创建的,就会是系统管理区的大小。

现在在tbs_ts2中创建表,并观察它的区大小。

SQL> create table table_lhb1(id int,name varchar2(20)) tablespace tbs_ts2;

Table created.

 

SQL> select extent_id, file_id, block_id, blocks from dba_extents where segment_

name='TABLE_LHB1' order by extent_id;

 

 EXTENT_ID    FILE_ID   BLOCK_ID     BLOCKS

---------- ---------- ---------- ----------

         0          5        128          8

可以看到TABLE_LHB1目前有一个区,大小只有8个块,也就是64KB。插入一些数据,将表撑大点再观察。

SQL> insert into table_lhb1 select rownum,'aaa' from dba_objects;

 

12650 rows created.

 

SQL> select extent_id, file_id, block_id, blocks from dba_extents where segment_

name='TABLE_LHB1' order by extent_id;

 

 EXTENT_ID    FILE_ID   BLOCK_ID     BLOCKS

---------- ---------- ---------- ----------

         0          5        128          8

         1          5        136          8

         2          5        144          8

         3          5        152          8

上面向表中插入了12650行,表目前涉及4个区,每个区都是8个块64KB。提交后继续插入,命令如下:

SQL> insert into table_lhb1 select * from table_lhb1;

 

12650 rows created.

 

SQL> insert into table_lhb1 select * from table_lhb1;

25300 rows created.

 

SQL> insert into table_lhb1 select * from table_lhb1;

 

50600 rows created.

 

SQL> insert into table_lhb1 select * from table_lhb1;

 

101200 rows created.

 

SQL> commit;

 

Commit complete.

 

SQL> select extent_id, file_id, block_id, blocks from dba_extents where segment_

name='TABLE_LHB1' order by extent_id;

 

 EXTENT_ID    FILE_ID   BLOCK_ID     BLOCKS

---------- ---------- ---------- ----------

         0          5        128          8

         1          5        136          8

         2          5        144          8

         3          5        152          8

         4          5        160          8

         5          5        168          8

         6          5        176          8

         7          5        184          8

         8          5        192          8

         9          5        200          8

        10          5        208          8

        11          5        216          8

        12          5        224          8

        13          5        232          8

        14          5        240          8

        15          5        248          8

        16          5        256        128

        17          5        384        128

 

18 rows selected.

多次插入后,表已经占了很多空间,我们来看一下现在区的使用情况。0~15号区,大小为8个块64KB,从第16号区开始,区大小变为了128个块1MB大小。也就是说,表的大小小于1MB时,表的每个区都是64KB,当表的大小超过1MB,再分配新区时,区的大小将是1MB。读者可以继续测试,当表进一步变大,区大小将会变成8MB,表继续扩大,更大的区也有,这里就不测了。

可见,在系统管理区大小表空间中,区的大小随表的增大而增大。

到这里,我们已经发现了系统管理区大小的秘密。很简单吧?有时候探索Oracle也不是件复杂的事情,只要多动手就行了。我在做培训的时候,也常跟学员讲:探索的过程,就是熟悉Oracle的过程。你探索出的结果不一定很有用,但探索的过程,会加深对Oracle的熟悉程度,这才是研究、探索Oracle的真正目的。研究Oracle,很多时候结果不是目的,过程更有意义。

我们已经了解了统一区大小和系统管理区大小的不同,那么,什么时候使用统一区大小,什么时候使用系统管理区大小呢?

从空间的利用率上讲,小区节省空间,大区可能会浪费空间。比如,当区大小是10MB时,为一个表分配了一个10MB的区,哪怕它只使用了这10MB中的1个字节,这10MB空间也完全属于这个表了,其他表无法再使用这部分空间。从这个角度上讲,小区的空间利用率无疑是高的。

但从性能角度上讲,对于随机访问,大区、小区没有影响。但对于全表扫描这样的操作,大区又是更合适的。因为连续空间更多,可以减少磁头在区间的定位。

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

如果使用统一区大小,几百KB甚至1MB的区,都有点小。其实可以参考系统管理型的表空间,当段的大小超过64MB时,区大小为8MB。在使用统一区大小时,也可以将所有区都固定为8MB。

我见到过很多数据库都使用统一区大小,而且其大小为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大小)是很合适的选择。

还有一个问题,不知道大家有没考虑到。这个问题涉及统一区大小表空间的位图块。每个二进制位对应一个区的使用情况,这是没问题的,但系统管理区大小呢?就比如刚才创建的TABLE_LHB1表,前16个区大小为64KB,之后的区大小为1MB。区的大小不同,如何用二进制位来反映区的使用情况呢?

Oracle的处理方法是这样的,以64KB(也就是8个块)为准,每个二进制位对应64KB。1MB的区,对应16个二进制位。每分配一个1MB的区,Oracle将对应的16个二进制位(也就是两个字节)设置为1。释放一个区也同样,将16个二进制位设置为0。这样就解决了区大小不统一的问题,Oracle的解决方法还是很巧妙的!

1.1.3 碎片:少到可以忽略的问题

最后,来想这样一个问题:在表空间级别有碎片吗?

答案是:有,但也要看情况。在统一区大小表空间中,因为区的大小一致,不会出现碎片问题。但在系统管理区中,由于区的大小不一致,仍会存在碎片。比如说,有很多个64KB的区,互相不连续,分布在数据文件的各个角落。当需要1MB、8MB大小的区时,这些不连续的64KB区无法被重用,这就是典型的碎片了。但是,这种情况很少出现。因为区不会被频繁地分配、释放。一个表创建之后,很少会去对它进行Drop、Truncate操作。

没有频繁的分配、释放操作,碎片也就很少出现了。所以,在表空间层,碎片已经是一个可以忽略的问题了。当然,在表层、索引层,还有可能存在碎片。

上一篇:Redis(九):Redis的Java客户端Jedis


下一篇:.net 实体类与json转换(.net自带类库实现)