数据文件信息记录(DBA_DATA_FILES)
我们说DBA_TABLESPACES记录的是表空间的逻辑信息,而DBA_DATA_FILES视图则记录的是物理数据文件的信息,这些信息包括数据文件的名称、大小、所属表空间等信息:
SQL> select file_id,relative_fno,file_name,tablespace_name,round(bytes/1024/1024/1024,2) GB 2 from dba_data_files; FILE_ID RELATIVE_FNO FILE_NAME TABLESPA GB -------- ------------ -------------------------------------------------- -------- ------- 1 1 +DATADG/smsboss/datafile/system.261.577731551 SYSTEM .98 2 2 +DATADG/smsboss/datafile/undotbs1.260.577731573 UNDOTBS1 5.86 3 3 +DATADG/smsboss/datafile/sysaux.262.577731593 SYSAUX 1.07 4 4 +DATADG/smsboss/datafile/users.256.577731607 USERS .10 5 1024 +DATADG/smsboss/datafile/bossmgr.271.577809233 BOSSMGR 205.08 6 6 +DATADG/smsboss/datafile/dbmon.5417.595530625 DBMON .01 6 rows selected.
对于使用文件系统的数据库,如果希望降低数据库在操作系统上的空间耗用,可以通过Resize Datafile的方式,数据文件能够Resize的最小大小依赖于数据文件上的对象存储。
数据段信息(DBA_SEGMENTS)
段(Segment)是Oracle中数据对象的存在形式,可以通过DBA_SEGMENTS查询数据库中段的信息,这些信息包括对象名称、类型、所属表空间、空间使用等信息。下面通过一个简单的查询来统计不同表空间中对象的空间使用情况:
SQL> select tablespace_name,count(*) nobjects,round(sum(bytes)/1024/1204/1024,2) GB, 2 sum(blocks),sum(extents) from dba_segments 3 group by rollup(tablespace_name); TABLESPA NOBJECTS GB SUM(BLOCKS) SUM(EXTENTS) -------- ---------- ------- ----------- ------------ BOSSMGR 1208 169.70 6538080 40863 DBMON 7 .00 44 8 SYSAUX 3162 .79 30344 4723 SYSTEM 1255 .70 26842 2613 UNDOTBS1 10 .04 1352 40 USERS 4 .01 212 23 5646 171.22 6596874 48270 7 rows selected.
可以注意到,对于最大的一个表空间BOSSMGR,通过上一节的查询,这个表空间的数据文件占用了205GB的空间,而真正被使用的空间约为170GB。
这里引入一个新的概念:高水位标记(High Water Mark,HWM)。简单来说,对于以MSSM方式管理的Segment,HWM就是指这个Segment中已使用和未使用过的Block的分界线,HWM之上的空间在格式化之前不能被使用,即在HWM以上的数据块均为未格式化的块,这些未格式化的块在格式化之前是不能被insert数据的;在数据库事务中,当请求新的空闲块并且现有空闲列表(free list)中的块不能满足要求时,HWM将会向上移动,然后格式化一组数据块并加入Free List提供使用。在HWM之下的数据块也可能存在空闲情况,当数据被删除之后,数据块被释放重新回到空闲列表,又可以被其他数据变更所使用,HWM通常只会向上增长,不会自动收缩。
图5-7是一个数据段的HWM示意图,描述了HWM及空间使用的关系。
图5-7 一个数据段的HWM示意图
如果把图5-7扩展到一个数据文件上去,数据文件也存在一个类似的HWM概念,在HWM之上的空间是尚未分配给任何对象的,如果希望缩减数据文件的大小(通过Resize的方式),这个HWM就是数据文件能够收缩到的极限值。
回顾一下之前的数据文件转储信息,我们很容易在段头发现Highwater信息,这个信息就是HWM值:
Extent Control Header ----------------------------------------------------------------- Extent Header:: spare1: 0 spare2: 0 #extents: 2 #blocks: 255 last map 0x00000000 #maps: 0 offset: 4128 Highwater:: 0x00c00056 ext#: 0 blk#: 76 ext size: 127
HWM会影响Oracle执行全表扫描时的读取行为,对于全表扫描操作,Oracle必须读取HWM下的所有数据块,如果一个数据表由于DELETE操作删除了大部分记录,但是HWM并不会降低,所以再次执行全表扫描时,Oracle仍然需要读取对象段中的所有数据块,这显然会影响性能,来看以下测试,创建一个测试表,并用autotrace跟踪全表扫描的数据读取:
SQL> connect eygle/eygle Connected. SQL> create table test as select * from dba_objects; Table created. SQL> set autotrace trace stat SQL> select count(*) from test; Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 80 consistent gets 77 physical reads
删除数据,刷新Buffer,再次执行全表扫描查询:
SQL> delete from test where rownum <2001; 2000 rows deleted. SQL> commit; Commit complete. SQL> alter session set events 'immediate trace name flush_cache level 1'; Session altered. SQL> select count(*) from test; Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 80 consistent gets 78 physical reads
注意到两次查询的consistent gets没有改变,这就是HWM的影响。对于通常的对象,我们不太需要关注其HWM的影响,但是如果表的删除操作非常频繁,表中的大部分Block已经为空,那么可能就需要关注其空间及性能问题。
对于FLM下的本地管理表空间,可以通过Analyze数据表的方法来评估HWM下有多少blocks是不包含数据的,以下测试说明了这个过程,创建一个包含10万数据的测试表:
SQL> CREATE TABLE test NOLOGGING 2 AS 3 SELECT o.* FROM 4 (SELECT * FROM dba_objects WHERE ROWNUM <10001) o, 5 (SELECT * FROM dba_objects WHERE ROWNUM <11) o2 6 ORDER BY o.object_id; Table created.
然后分析该表,可以看到该表共使用了1261个Block,有18个空的数据块,这时HWM应该位于1261个数据块附近:
SQL> analyze table test compute statistics; Table analyzed. SQL> select table_name,num_rows,AVG_ROW_LEN,BLOCKS,EMPTY_BLOCKS from dba_tables 2 where table_name='TEST'; TABLE_NAME NUM_ROWS AVG_ROW_LEN BLOCKS EMPTY_BLOCKS -------------------- ---------- ----------- ---------- ------------ TEST 100000 89 1261 18
删除数据之后,记录数减少,但是Oracle使用的Block数量并不会变化,也就是HWM并未移动:
SQL> delete from test where rownum <50001; 50000 rows deleted. SQL> commit; Commit complete. SQL> analyze table test compute statistics; Table analyzed. SQL> select table_name,num_rows,AVG_ROW_LEN,BLOCKS,EMPTY_BLOCKS from dba_tables 2 where table_name='TEST'; TABLE_NAME NUM_ROWS AVG_ROW_LEN BLOCKS EMPTY_BLOCKS -------------------- ---------- ----------- ---------- ------------ TEST 50000 92 1261 18
可以计算共有多少数据块被使用以及空数据块占HWM下数据块的百分比:
SQL> select count(distinct(substr(rowid,1,15))) blocks from test; BLOCKS ---------- 654 SQL> select 654/1261*100 from dual; 654/1261*100 ------------ 51.8636003
注意:如果这个比例过高,也并不一定意味着需要调整,如果有新的记录插入该表,那么这些空间是可以重用的。如果表的空间空置率一直偏高,并且数据读取倾向于使用全表扫描,则可以考虑重整表的存储。
对于以ASSM方式管理的Segment,在其段头里会存在两种高水位标记,它们分别是LHWM(Low HWM)和HHWM(High HWM),即低高水位和高高水位,如下所示:
Extent Control Header ----------------------------------------------------------------- Extent Header:: spare1: 0 spare2: 0 #extents: 1 #blocks: 128 last map 0x00000000 #maps: 0 offset: 2716 Highwater:: 0x028266c9 ext#: 0 blk#: 64 ext size: 128 #blocks in seg. hdr's freelists: 0 #blocks below: 60 mapblk 0x00000000 offset: 0 Unlocked -------------------------------------------------------- Low HighWater Mark : Highwater:: 0x0282668d ext#: 0 blk#: 4 ext size: 128
可以看到这里高高水位是0x028266c9,低高水位是0x0282668d。
SQL> select getbfno('0x028266c9') bfno from dual; BFNO ------------------------------ datafile# is:10 datablock is:157385 SQL> select getbfno('0x0282668d ') bfno from dual; BFNO ------------------------------ datafile# is:10 datablock is:157325
也就是说上述Segment的高高水位是file 10,block 157385;其低高水位是file 10,block 157325。
由于ASSM管理方式下不存在Free List ,所以LHWM和HHWM概念被引入用于实现段空间管理。在ASSM管理模式下,当一个会话向表中插入数据时,数据库首先格式化一个位图块(而不是像以前那样格式化一组数据块),这个位图块代替Free List用于跟踪段中的数据块的状态变化,数据库通过位图块去寻找空闲块并在使用前对其进行格式化。正是由于这个格式化的变化,所以在LHWM和HHWM之间可能存在格式化和未格式化的数据块。
ASSM管理方式下的Segment的HHWM和LHWM的特点是:
(1) 所有在HHWM以上的数据块一定是未格式化的块;
(2) 所在在LHWM以下的数据块一定是格式化的块;
(3) 在LHWM和HHWM之间的数据快既可能是格式化的块,也可能是未格式化的块;
(4) Oracle在全表扫描一个Segment的时候,会一直扫描到HHWM为止;
如果一个Segment的LHWM下出现了未格式化的块,那么Oracle在访问这个表的时候会报错ORA-08103,此时数据读取、建索引等操作将不能正常执行。下面来构造一个ORA-08103的例子,深入理解一下Oracle的内部原理。
对于以下这种以CTAS方式创建的表而言,其初始的LHWM是等于HHWM的:
SQL>create table t1 tablespace users as select * from dba_objects where rownum<1001; Table created. SQL>select count(*) from t1; COUNT(*) ---------- 1000 SQL>set wrap off SQL>set linesize 2000 SQL>select dbms_rowid.rowid_relative_fno(rowid)||'_'|| \ 2 dbms_rowid.rowid_block_number(rowid) location,count(*) from t1 3 group by dbms_rowid.rowid_relative_fno(rowid)||'_'||dbms_rowid.rowid_block_number(rowid); LOCATION COUNT(*) -------------------------------- ---------- 9_3724 70 9_3725 70 9_3726 69 9_3727 69 9_3728 69 9_3729 69 9_3730 69 9_3731 70 9_3732 70 9_3733 69 9_3734 69 9_3735 69 9_3736 69 9_3738 70 9_3739 29
现在手工将上述表t1的LHWM下的数据块(9_3739,即file 9,block 3739)用dd命令清零,即达到了在LHWM下构造一个未格式化的数据块的目的:
SQL>conn / as sysdba; Connected. SQL>shutdown immediate Database closed. Database dismounted. ORACLE instance shut down. [P550]#dd if=/dev/zero of=/dras20/testdb/users01.dbf bs=8192 seek=3739 count=1 conv=notrunc 1+0 records in. 1+0 records out. [P550]#sqlplus '/ as sysdba'; SQL*Plus: Release 9.2.0.6.0 - Production on Tue Dec 21 10:26:49 2010 Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved. Connected to an idle instance. SQL>startup pfile=/dras20/testdb/inittestdb.ora ORACLE instance started. Total System Global Area 504858456 bytes Fixed Size 743256 bytes Variable Size 285212672 bytes Database Buffers 218103808 bytes Redo Buffers 798720 bytes Database mounted. Database opened.
重新启库后执行查询会发现ORA-08103已经如期而至:
SQL>select count(*) from scott.t1; select count(*) from scott.t1 * ERROR at line 1: ORA-08103: object no longer exists SQL>create unique index scott.idx_t1 on scott.t1(object_id); create unique index scott.idx_t1 on scott.t1(object_id) * ERROR at line 1: ORA-08103: object no longer exists
ORA-08103是指,当扫描数据块读取LHWM下的所有数据块时,发现某个数据块上不存在表信息(或者索引信息等)时,即违反了数据库的一致性,随即抛出错误,提示数据块上本应该存在的对象“消失”了。
转储一个数据块,我们可以看到数据块头部存在的元数据信息,以下示例选择了OBJ$对象,转储其中的一个数据块:
SQL> col segment_name for a15 SQL> select segment_name,file_id,block_id from dba_extents where segment_name='OBJ$'; SEGMENT_NAME FILE_ID BLOCK_ID --------------- ---------- ---------- OBJ$ 1 63497 OBJ$ 1 50569 OBJ$ 1 38793 OBJ$ 1 34569 OBJ$ 1 30345 OBJ$ 1 29857 OBJ$ 1 29081 OBJ$ 1 27553 OBJ$ 1 26769 OBJ$ 1 19993 OBJ$ 1 16737 SQL> alter system dump datafile 1 block 63506; System altered.
然后找到跟踪文件:
SQL> show parameter dump NAME TYPE VALUE ------------------------------------ ----------- ------------------------------ user_dump_dest string /u01/admin/cndd/udump p55a@/home/oracle> cd /u01/admin/cndd/udump p55a@/u01/admin/cndd/udump> ls -sort|tail -1 72 -rw-r----- 1 oracle 71010 Feb 11 10:53 cndd_ora_590288.trc
摘录一下数据块头部的信息,可以看到其中重要的内容:
Block header dump: 0x0040f812 Object id on Block? Y seg/obj: 0x12 csc: 0x01.80e716f4 itc: 1 flg: - typ: 1 - DATA fsl: 0 fnx: 0x0 ver: 0x01
这里的对象号12是16进制,即10进制的18,对应的对象就是OBJ$,ORA-08103错误就是指找不到数据块头部的这些对象信息:
SQL> select object_name from dba_objects where object_id=18; OBJECT_NAME -------------------------------------------------------------------------------- OBJ$
数据块接下来的部分记录了ITL、表数量、行数量及偏移量信息等:
Itl Xid Uba Flag Lck Scn/Fsc 0x01 0x000a.000.00118ac4 0x00801d7b.b3ec.21 --U- 1 fsc 0x0000.80e716f5 data_block_dump,data header at 0x11041a044 =============== tsiz: 0x1fb8 hsiz: 0xc8 pbl: 0x11041a044 bdba: 0x0040f812 76543210 flag=-------- ntab=1 nrow=91 frre=1 fsbo=0xc8 fseo=0x699 avsp=0x5d3 tosp=0x5d3 0xe:pti[0] nrow=91 offs=0 0x12:pri[0] offs=0x1f5e 0x14:pri[1] sfll=2 0x16:pri[2] sfll=4 0x18:pri[3] offs=0x1f10 0x1a:pri[4] sfll=5 0x1c:pri[5] sfll=7 0x1e:pri[6] offs=0x1ec5 0x20:pri[7] sfll=61 0x22:pri[8] offs=0x1e6e 0x24:pri[9] offs=0x1e15 0x26:pri[10] offs=0x1dbb 0x28:pri[11] offs=0x1d6c 0x2a:pri[12] offs=0x1d1f 0x2c:pri[13] offs=0x1cd2 0x2e:pri[14] offs=0x1c80 0x30:pri[15] offs=0x1c39 。。。。。。。。。。。 block_row_dump: tab 0, row 0, @0x1f5e tl: 90 fb: --H-FL-- lb: 0x0 cc: 17 col 0: [ 4] c3 07 2e 4c col 1: [ 4] c3 07 2e 4c col 2: [ 2] c1 3a col 3: [27] 43 4f 4e 54 52 41 43 54 54 42 4c 5f 42 41 43 4b 5f 50 52 49 4d 41 52 59 4b 45 59 col 4: [ 2] c1 05 col 5: *NULL* col 6: [ 2] c1 02 col 7: [ 7] 78 69 0b 18 10 1a 08 col 8: [ 7] 78 69 0b 18 10 1a 08 col 9: [ 7] 78 69 0b 18 10 1a 08 col 10: [ 2] c1 02 col 11: *NULL* col 12: *NULL* col 13: [ 1] 80 col 14: *NULL* col 15: [ 1] 80 col 16: [ 4] c3 07 38 24
以上的这部分信息,在数据块内部,被定义为块头(Block Header)信息,这些信息和数据结构是非常重要的,例如通过ITL数据块实现了行级锁定,下图是常规的数据块结构图:
了解了以上原理,对于一些故障的判断也就可以从容的进行了,在某些情况下,truncate对于读取的会话同样会产生ORA-08103的错误,Oracle也有些Bug与此有关。
对于ASSM管理的Segment,可以通过Oracle提供的系统包DBMS_SPACE直接来计算其空间使用情况,为方便使用,可以创建如下过程:
CREATE OR REPLACE PROCEDURE show_space_assm ( p_segname IN VARCHAR2, p_owner IN VARCHAR2 DEFAULT USER, p_type IN VARCHAR2 DEFAULT 'TABLE' ) AS l_fs1_bytes NUMBER; l_fs2_bytes NUMBER; l_fs3_bytes NUMBER; l_fs4_bytes NUMBER; l_fs1_blocks NUMBER; l_fs2_blocks NUMBER; l_fs3_blocks NUMBER; l_fs4_blocks NUMBER; l_full_bytes NUMBER; l_full_blocks NUMBER; l_unformatted_bytes NUMBER; l_unformatted_blocks NUMBER; PROCEDURE p (p_label IN VARCHAR2, p_num IN NUMBER) IS BEGIN DBMS_OUTPUT.put_line (RPAD (p_label, 40, '.') || p_num); END; BEGIN DBMS_SPACE.space_usage (segment_owner => p_owner, segment_name => p_segname, segment_type => p_type, fs1_bytes => l_fs1_bytes, fs1_blocks => l_fs1_blocks, fs2_bytes => l_fs2_bytes, fs2_blocks => l_fs2_blocks, fs3_bytes => l_fs3_bytes, fs3_blocks => l_fs3_blocks, fs4_bytes => l_fs4_bytes, fs4_blocks => l_fs4_blocks, full_bytes => l_full_bytes, full_blocks => l_full_blocks, unformatted_blocks => l_unformatted_blocks, unformatted_bytes => l_unformatted_bytes ); p ('free space 0-25% Blocks:', l_fs1_blocks); p ('free space 25-50% Blocks:', l_fs2_blocks); p ('free space 50-75% Blocks:', l_fs3_blocks); p ('free space 75-100% Blocks:', l_fs4_blocks); p ('Full Blocks:', l_full_blocks); p ('Unformatted blocks:', l_unformatted_blocks); END; /
通过这个过程可以计算ASSM下表中空间的使用情况。
同上例,创建测试表并删除部分数据:
SQL> CREATE TABLE test NOLOGGING 2 AS 3 SELECT o.* FROM 4 (SELECT * FROM dba_objects WHERE ROWNUM <10001) o, 5 (SELECT * FROM dba_objects WHERE ROWNUM <11) o2 6 ORDER BY o.object_id; Table created. SQL> analyze table test compute statistics; Table analyzed. SQL> select table_name,num_rows,AVG_ROW_LEN,BLOCKS,EMPTY_BLOCKS from dba_tables 2 where table_name='TEST'; TABLE_NAME NUM_ROWS AVG_ROW_LEN BLOCKS EMPTY_BLOCKS -------------------- ---------- ----------- ---------- ------------ TEST 100000 89 1291 5 SQL> delete from test where rownum <50001; 50000 rows deleted. SQL> commit; Commit complete.
现在可以通过show_space_assm过程来计算这个对象的空间使用情况:
SQL> set serveroutput on SQL> exec show_space_assm('TEST') free space 0-25% Blocks:................0 free space 25-50% Blocks:...............0 free space 50-75% Blocks:...............0 free space 75-100% Blocks:..............609 Full Blocks:............................654 Unformatted blocks:.....................0
表中Full的数据块共有654个,而75%~100% Free的Block有609个,这说明表中大部分数据块处于空闲和基本空闲状态,也可以直接再验证一下:
SQL> select count(distinct(substr(rowid,1,15))) nums from test; NUMS ---------- 655
注意:关于Show_space脚本在网络上有很多个版本,最初的版本来自Thomas Kyte的著作,该脚本的本质是调用DBMS_SPACE系统包,通过简化达到易于使用的目的。
以下是在SAP系统的Oracle数据库中,清理数据表XI_AF_MSG的实际案例。在客户的系统中,该表的存储空间占用了1.8G,除了表数据之外,该表还存在3个LOB字段,其中之一占用了10G的存储空间:
SQL> col segment_name for a30 SQL> select segment_name ,bytes/1024/1024/1024 2 from dba_segments where segment_name='XI_AF_MSG'; SEGMENT_NAME BYTES/1024/1024/1024 ------------------------------ -------------------- XI_AF_MSG 1.81640625 SQL> select column_name,segment_name from dba_lobs where table_name='XI_AF_MSG'; COLUMN_NAME SEGMENT_NAME ------------------------------ ------------------------------ MSG_BYTES SYS_LOB0000053939C00020$$ TRAN_HEADER SYS_LOB0000053939C00032$$ PP_HASH SYS_LOB0000053939C00038$$ SQL> select segment_name ,bytes/1024/1024/1024 GB from dba_segments 2 where segment_name in (select segment_name from dba_lobs where table_name='XI_AF_MSG'); SEGMENT_NAME GB ---------------------------------------- ---------- SYS_LOB0000053939C00038$$ .000061035 SYS_LOB0000053939C00032$$ .000061035 SYS_LOB0000053939C00020$$ 10.8710938
这个表完全使用(Full Blocks)了17万个数据块,75%-100%的数据块有5万个:
SQL> set serveroutput on SQL> exec show_space_assm('XI_AF_MSG','SAPSR3DB','TABLE'); free space 0-25% Blocks:................7 free space 25-50% Blocks:...............2012 free space 50-75% Blocks:...............3213 free space 75-100% Blocks:..............53591 Full Blocks:............................170657 Unformatted blocks:.....................712
在不断删除了部分数据之后,可以看到Full Blocks的数量降低,75%-100%的块数量增加:
SQL> exec show_space_assm('XI_AF_MSG','SAPSR3DB','TABLE'); free space 0-25% Blocks:................13 free space 25-50% Blocks:...............8 free space 50-75% Blocks:...............18 free space 75-100% Blocks:..............66127 Full Blocks:............................163314 Unformatted blocks:.....................712
自Oracle Database 10g开始,还可以使用Oracle内置的空间管理工具-段顾问(Segement Advisor)来分析指定段,从而Oracle会根据内置的专家系统给出调整建议。
下面调用DBMS_ADVISOR包,来执行建议收集工作:
SQL> VARIABLE taskid number SQL> DECLARE 2 NAME VARCHAR2 (100); 3 descrb VARCHAR2 (100); 4 obj_id NUMBER; 5 BEGIN 6 NAME := 'Check Test Table of EYGLE'; 7 descrb := 'Check EYGLE.TEST'; 8 dbms_advisor.create_task ('Segment Advisor', :taskid, NAME, descrb, NULL); 9 dbms_advisor.create_object (NAME, 10 'TABLE', 11 'EYGLE', 12 'TEST', 13 NULL, 14 NULL, 15 obj_id 16 ); 17 dbms_advisor.set_task_parameter (NAME, 'RECOMMEND_ALL', 'TRUE'); 18 dbms_advisor.execute_task (NAME); 19 END; 20 / PL/SQL procedure successfully completed. SQL> set serveroutput on SQL> print taskid TASKID ---------- 49618 SQL> select * from dba_advisor_findings where task_id=49618; OWNER TASK_ID TASK_NAME FINDING_ID TYPE PARENT OBJECT_ID --------- ---------- ------------------------ ---------- ------ ----------- ---------- IMPACT_TYPE IMPACT -------------------------------------- MESSAGE -------------------------------------------------------------------------------- MORE_INFO -------------------------------------------------------------------------------- EYGLE 49618 Check Test Table of EYGLE 1 PROBLEM 1 1 The free space in the object is less than 10MB. Allocated Space:10616832: Used Space:6221251: Reclaimable Space :4395581:
提示我们这个表使用的空间数量,以及可以回收的空间数量。如果空间使用存在问题,则可以查询dba_advisor_recommendations获得Oracle的推荐举措,查询dba_advisor_actions获得Oracle推荐采取的行为。当然更为简单的方式是使用Oracle 10g的Database Control,一切都可以通过图形界面简便快速地进行操作。
如果经过评估HWM的问题已经影响到了性能和存储,那么可以有多种措施来解决HWM的问题。
1.导出/导入与Truncate结合
如果要删除的是表中的所有记录,那么使用Truncate可以降低HWM;但是能使用Truncate的情况并不常见,如果表中剩余的是少量数据,可以通过EXP导出数据或者使用CTAS创建一张临时表,然后Truncate表,再导入或INSERT回数据,即可解决HWM问题。但是这种方法对于不间断服务的数据表并不适合。
2.RENAME+INSERT结合
对于连续使用的数据表,如果数据是以写为主的日志类数据,则可以通过RENAME将数据表更名,然后按原来的结构重建数据表,此时插入操作可以恢复,这个过程非常迅速,对于数据库影响极小,然后可以将RENAME表中的数据插入回来,这就完成了数据整理,HWM可以降低。这种方法适用于写为主的业务类型,不适合增删改查频繁的对象。
3.在线重定义(DBMS_REDEFINITION)
从Oracle 9i开始,Oracle引入了在线重定义新特性,通过DBMS_REDEFINITION包可以对表进行在线重定义,如修改字段名称、增加字段等,当然也可以借用这个包进行空间重整。
在重定义过程中,Oracle通过中间的临时表来记录中间变化数据,完成重定义后可以将数据整合到重定义的表中,数据库的正常操作可以继续进行,这种重组方式和方法2中介绍的过程类似,其原理如图5-8所示。
图5-8 在线重定义示意图
因为数据最终要整合到新的“结果表”中,所以HWM问题可以得到解决。
4.Shrink新特性
不管通过哪一种方法,空间重整一直是一个难题,所以从Oracle 10g开始,Oracle引入了一个新的功能用于支持在线的空间重整,这就是联机段空间回收(Shrinking Database Segments Online)的功能。
联机段收缩仅对ASSM(自动段空间管理)表空间中的表有效,Shrink的本质就是对表执行一系列的DML操作,删除表末端的稀疏行,并在表的顶端重新插入。通过这样的一系列操作,可以填满表段中的“漏洞”空间,逐步将所有剩余空间留在表的末端,然后Oracle可以重置该表的HWM,释放自由空间。由于Shrink是针对数据行进行处理的,在表上会获得行级排他锁,所以并不会影响全表的DML操作,这也是Online的意义所在(但是Shrink可能会产生大量Redo,影响归档量,在操作时需要考虑)。
降低了HWM之后,全表扫描(Full Table Scan)需要扫描的数据块就可以减少,从而性能可以得到提升。图5-9说明了段空间回收的方式:
图5-9 段空间回收
由于回收段空间需要移动行数据,数据的ROWID会发生变化,索引会被同时维护,也因此在执行Shrink之前,需要设置表的ENABLE ROW MOVEMENT属性:
alter table <table_name> enable row movement; alter table <table_name> shrink space;
但是需要注意的是,由于段空间重整是通过DML操作来完成的,所以会产生额外的redo,这个redo信息需要评估,如果数据表非常大,那么产生的redo可能是无法接受的。来看一个测试,执行以下命令将创建一个数据量为100万的数据表:
SQL> create table t tablespace eygle nologging 2 as 3 select o.* from 4 (select * from dba_objects where rownum <10001) o, 5 (select * from dba_objects where rownum <101) o2 6 order by o.object_id;
这个表约耗用了100MB的空间:
SQL> select bytes/1024/1024 from user_segments where segment_name='T'; BYTES/1024/1024 --------------- 99.875
查询一下记录全表扫描的读取统计信息:
SQL> select count(*) from t; Statistics ---------------------------------------------------------- 28 recursive calls 0 db block gets 12839 consistent gets 12623 physical reads
删除表中部分数据:
SQL> delete from t where mod(object_id,2)=0; 499800 rows deleted. Elapsed: 00:03:13.67 SQL> commit; Commit complete.
此时查询,再次执行全表扫描(注意第一次查询可能受延迟块清除影响,再次执行可用获得准确的全表扫描信息统计):
SQL> select count(*) from t; Statistics ---------------------------------------------------------- 189 recursive calls 0 db block gets 12855 consistent gets 10157 physical reads
注意到由于HWM的影响,全表扫描仍然需要读取所有的数据块。接下来执行Shrink操作,同时记录一下redo信息:
SQL> alter table t enable row movement; Table altered. SQL> select a.name,b.value 2 from v$statname a,v$mystat b 3 where a.statistic#=b.statistic# and a.name='redo size'; NAME VALUE ------------------------------ ---------- redo size 228555548 SQL> alter table t shrink space; Table altered. SQL> select a.name,b.value 2 from v$statname a,v$mystat b 3 where a.statistic#=b.statistic# and a.name='redo size'; NAME VALUE ------------------------------ ---------- redo size 421362176 SQL> select (421362176 - 228555548)/1024/1024 from dual; (421362176-228555548)/1024/1024 ------------------------------- 183.874729
注意到为了收缩这个空间,共产生了近200MB的redo信息,如果表上包含索引,那么Shrink产生的redo将会更多。所以能否在生产环境上对大数据量的表使用这一特性仍然是值得斟酌的。以上数据表Shrink之后空间耗用下降到47MB左右。
SQL> select bytes/1024/1024 from user_segments where segment_name='T'; BYTES/1024/1024 --------------- 46.875
那么再看此时的全表扫描的逻辑读:
SQL> select count(*) from t; Statistics ---------------------------------------------------------- 181 recursive calls 0 db block gets 6066 consistent gets 5268 physical reads
注意到逻辑读此时已成功地降低下来。
继续前面的实践案例,在清理了XI_AF_MSG表之后,对该表进行SHRINK SPACE,释放其存储空间,降低HWM,注意这个SHRINK动作是非常缓慢的,该表的收缩动作用时1小时左右:
SQL> alter table sapsr3db.xi_af_msg enable row movement; Table altered. Elapsed: 00:00:00.14 SQL> alter table sapsr3db.xi_af_msg shrink space ; Table altered. Elapsed: 01:03:17.09
在收缩过程中,用show_space_assm 检查空间使用情况,可以该表使用数据块的数量不断减少:
SQL> exec show_space_assm('XI_AF_MSG','SAPSR3DB','TABLE'); free space 0-25% Blocks:................1 free space 25-50% Blocks:...............2054 free space 50-75% Blocks:...............2970 free space 75-100% Blocks:..............117344 Full Blocks:............................107111 Unformatted blocks:.....................712 SQL> exec show_space_assm('XI_AF_MSG','SAPSR3DB','TABLE'); free space 0-25% Blocks:................1 free space 25-50% Blocks:...............2054 free space 50-75% Blocks:...............2970 free space 75-100% Blocks:..............117369 Full Blocks:............................107086 Unformatted blocks:.....................712
最终该表仅占用了近9万个数据块,空闲空间被释放了出去:
SQL> exec show_space_assm('XI_AF_MSG','SAPSR3DB','TABLE'); free space 0-25% Blocks:................5 free space 25-50% Blocks:...............0 free space 50-75% Blocks:...............0 free space 75-100% Blocks:..............1 Full Blocks:............................88744 Unformatted blocks:.....................0
5.Move对象
通过Move操作移动对象,可以降低HWM,但是Move之后,索引需要重建,而且在Move的过程中会影响在线应用,所以这种方法的使用较为有限。
继续之前的案例,为了回收LOB对象占用的大量存储空间,我们对LOB对象进行了一次在线MOVE操作,该MOVE操作花费了4个小时时间,将大对象的存储空间降低到6G左右:
SQL> alter table sapsr3db.xi_af_msg move lob(MSG_BYTES) store as (tablespace PSAPLOB); Table altered. Elapsed: 04:04:47.04 SQL> select segment_name,bytes/1024/1024 MB from dba_segments where tablespace_name='PSAPLOB'; SEGMENT_NAME MB ---------------------------------------- ---------- SYS_IL0000053939C00020$$ .0625 SYS_LOB0000053939C00020$$ 6079 在MOVE的过程中(Move完成之前),新的表空间中的LOB对象以临时段的形态存在: SQL> select segment_name,bytes/1024/1024 MB 2 from dba_segments where tablespace_name='PSAPLOB'; SEGMENT_NAME MB ------------------------------ ---------- 37.11 168 37.19 .0625
OVE操作会使相关索引失效,需要检查状态,重建相关索引:
SQL> select index_name,status,last_analyzed from dba_indexes where table_name='XI_AF_MSG'; INDEX_NAME STATUS LAST_ANALYZE ------------------------------ ---------------- ------------ SYS_IL0000053939C00020$$ VALID SYS_IL0000053939C00032$$ VALID SYS_IL0000053939C00038$$ VALID I_XI_AF_STARTUP_1 UNUSABLE 04-MAR-11 IND_SENT_RECV_TIME UNUSABLE 04-MAR-11 I_XI_AF_EOIO UNUSABLE 04-MAR-11 I_XI_AF_STARTUP_3 UNUSABLE 04-MAR-11 I_XI_AF_STARTUP_2 UNUSABLE 04-MAR-11 SYS_C00142245 UNUSABLE 07-MAR-11 SQL> alter index I_XI_AF_STARTUP_1 rebuild online; Index altered.
6.其他的考虑
空间的重整始终是一个难题,各种方法都是以一定的性能牺牲为代价的,这就使得很多方法在实际的生产环境中并不可用。所以更好的办法应该是从应用入手、从规划入手,从最开始就能够避免一些可能出现的问题。
在最常采用的方法中,分区是一个常用的手段,涉及大量数据变更的数据表,很多可以通过分区来处理,由于分区表可以针对分区进行诸如DROP、TRUNCATE等操作,从而可以很容易地对分区进行维护,进而解决一系列的空间问题。
当然分区并不是万能的,其适用环境也是有限,所以真正能够解决问题的方法还是来自我们自己,通过对Oracle各种技术手段的认识和了解之后,我们才能够制定出适合我们需要的空间维护手段。