Oracle
的逻辑结构 ( 表空间、段、区间、块 ) ——块
Oracle 存储数据的最小单位是数据块。Oracle 管理 数据库 数据文件的存储空间被称为数据块,一个数据块是数据库使用的最小数据单位。对应的操作系统层面上,所有数据都是以字节形式存储的。每一种操作系统都有自己的块大小。Oracle 对数据的请求是Oracle 数据块的倍数,而非操作系统块。
标准的块大小是通过初始化参数DB_BLOCK_SIZE 指定的,Oracle 允许指定最多5 种非标准块。为了避免不必要的I/O 开销,数据块大小应当是操作系统块大小的整数倍。
关于数据块的几个概念
磁盘扇区 大小 512bytes sector size ,操作系统空间分配块大小, OS space allocation block size
簇字节数 也就是 I/O chunk size ,通常所说的操作系统块大小
Oracle 数据块 即 DB_BLOCK_SIZE , DB_BLOCK_SIZE 应当是操作系统块大小的整数倍数 ,可以有效地避免不必要的 I/O ,这里的操作系统块指的是簇字节数 (I/O chunk size) ,事实上 Oracle 的 DB_BLOCK_SIZE 大小可以小于簇字节数 (I/O chunk size) ,所以 Oracle 官方文档写的是应当或者建议值。
The standard block size is specified by the initialization parameter DB_BLOCK_SIZE . In addition, you can specify of up to five nonstandard block sizes. The data block sizes should be a multiple of the operating system’s block size within the maximum limit to avoid unnecessary I/O. Oracle data blocks are the smallest units of storage that Oracle can use or allocate. (来自于 Oracle Concept )
Oracle 日志写入使用 512bytes 的 chunk 大小,因此最好把日志放到裸设备和相关硬件上,从而忽略文件系统。 ( 该观点来自于 itpub Yong Huang ,但是原文出处不可考证 ) , Oracle 日志和数据文件写入机制的不同,印证了 redo 文件最好与其它数据文件进行物理分离。
Since Oracle redo writing uses chunk size of 512 bytes (or 1k on HPUX and a few other OS'es), it's always better to place redo logfiles on raw partitions or equivalent (Veritas Quick I/O, Oracle ASM etc), bypassing filesystems.
正常情况下,可以认为服务器进程读取相应的 Oracle 块时,会调用 Oracle I/O 引擎,然后 Oracle I/O 引擎向操作系统发出请求,操作系统在通过内核进行相应的系统调用接口读取操作系统块;然后文件系统调用相应的块设备进行扇区数据读写。 ( 裸设备暂时无法理解其原理 )
参见《 关于 oracle 数据块和操作系统块的关系》
http://www.itpub.net/viewthread.php?tid=1081114&highlight=%CA%FD%BE%DD%BF%E9
chkdsk 可以查看操作系统的块大小 (windows 平台 )
fsutil fsinfo ntfsinfo c: 命令也可查看操作系统的块大小 (windows 平台 )
/sbin/tune2fs -l /dev/sda1 | grep Block 可以查看操作系统的块大小 (Linux 平台 )
show parameter db_block_size 显示 Oracle 缺省数据块大小
区间是一系列用来存储特定信息的连续的数据块
数据块格式
Oracle 数据块的格式无论是表、索引还是cluster 数据,格式都是很类似的,见图
块头( 公共和变量)
头部包含了通用块信息,例如块的地址和段的类型 ( 例如表或者索引 )
表目录
这部分信息包含了在这个块中该表或该索引的相关信息。
行目录
这部分包含了数据块中的实际行的信息 ( 包括行数据区域中每行的地址 ) ,一旦数据块头部的这个行目录的空间被分配了,那么即使该行删除了,这段空间仍然不能回收。
因此一个当前为空的数据块
此区域包含数据块中存储的数据行的信息(每个数据行片断( row piece ) 在行数据区( row data area )中的地址)。 [ 一个数据块中可能保存一个完整的数据行,也可能只保存数据行的一部分 ,所以文中使用 row piece] 。只有在数据块中插入( insert )新数据时,行目录区空间才会被重新利用。
头部信息区(Overhead )
块头( header/Common and Variable ),表目录( Table Directory ),行目录( Row Directory )这三部分合称为头部信息区( Overhead )。头部信息区不存放数据,它存放的整个块的信息。头部信息区的大小是可变的。一般来说,头部信息区的大小介于 84 字节( bytes )到 107 字节( bytes )之间
可用空间(Free Space )
可用空间是一个块中未使用的区域,这片区域用于新行的插入和已经存在的行的更新。可用空间也包含事务条目,当每一次 insert 、 update 、 delete 、 select ..for update 语句访问块中一行或多行数据,将会请求一条事务条目,事务条目的请求空间与操作系统相关,在多数操作系统中大约所需 23 个字节。
行数据
这部数据块包含了表或索引的数据,行也可能跨数据块,这也就是行迁移现象。
具体参见: oracle 数据文件内部 BLOCK 结构详解,初始原文已不可考
http://www.sosdb.com/article/datafile_block.htm
可用空间管理(Free Space Management)
可用空间可能是自动或人工管理。
可用空间是由 Oracle 内部的段自动管理的,段内的可用 / 已用空间用位图来跟踪。自动段空间管理提供了以下好处:
l 使用便捷
l 更好的空间利用率,特别是那些行大小变化很大的对象
l 并发访问的动态调整
l 性能 / 空间的平衡
数据块可用空间的利用和压缩
Delete 和 update( 把原值变小 ) 可增加数据块的可用空间。在以下情况下 insert 语句才能有效地利用已释放的空间。
假如 insert 语句在同一个事务中,而 insert 前面的语句刚好释放了相应的空间,这时候 insert 语句可以利用该空间
假如 insert 语句与释放空间的语句不在同一个事务中,那么只有当其他事务提交后并且刚好需要空间的时候, insert 语句才能利用该空间。
释放的空间也可能不是连续的,只有当 1 一个 insert 或 update 语句试图使用足够空间创建新行的时候,并且 2 自由空间是分散的以至于不能插入毗邻空间的时候, Oracl 才会合并和压缩数据块的可用空间。
行链接和行迁移(Row Chaining and Migrating )
行链接( Row Chaining ):如果我们往数据库中插入( INSERT )一行数据,这行数据很大,以至于一个数据块存不下一整行, Oracle 就会把一行数据分作几段存在几个数据块中,这个过程叫行链接( Row Chaining )。
如果一行数据是普通行,这行数据能够存放在一个数据块中;如果一行数据是链接行,这行数据存放在多个数据块中。
行迁移 (Row Migrating) :数据块中存在一条记录,用户执行 UPDATE 更新这条记录,这个 UPDATE 操作使这条记录变长,这时候, Oracle 在这个数据块中进行查找,但是找不到能够容纳下这条记录的空间,无奈之下, Oracle 只能把整行数据移到一个新的数据块。原来的数据块中保留一个“指针”,这个“ 指针”指向新的数据块。被移动的这条记录的 ROWID 保持不变。
PCTFREE 参数用于指定块中必须保留的最小空闲空间百分例。之所以要预留这样的空间,是因为 UPDATE 时,需要这些空间。如果 UPDATE 时,没有空余空间, Oracle 就会分配一个新的块,这会产生行迁移( Row Migrating )。
PCTUSED 也是用于设置一个百分比,当块中已使用的空间的比例小于这个百分比的时候,这个块才被标识为有效状态。只有有效的块才被允许插入数据
创建数据表
create table TEST
(
tablespace
pctfree 10 --初始化保留的最小10%的空闲空间比例,低于90%是,均可插入 pctused 40 --当已使用空间比例小于这个百分比时,重新允许插入数据 initrans 1
maxtrans 255
storage
(
initial 64K
minextents 1
maxextents
);
--插入测试 数据,并观察其相应的rowid
insert into test values('1');
insert into test values('2');
insert into test values('3');
commit;
select t.*,t.rowid from test t;
update test set name=lpad(name,4000,'1') where name='1';
update test set name=lpad(name,4000,'2') where name='2';update test set name=lpad(name,4000,'3') where name='3';commit;
select t.*,t.rowid from test t;\
@F:\OracleForVista\product\10.2.0\db_1\RDBMS\ADMIN\utlchain.sql;
--分析相关表
ANALYZE TABLE test LIST CHAINED ROWS;
--查询链接行或迁移行
SELECT * FROM CHAINED_ROWS where table_name='TEST';
select t.*,t.rowid from test t;
行迁移解决办法:其实没有太好的办法,只能是行迁移数据删除后,重新插入回来间Create table new_table as select * from old_table;
Drop table old_table;
Rename new_table to old_table;
本文出自 “不胜人生一场醉” 博客http://baoqiangwang.blog.51cto.com/1554549/312722