http://blog.sina.com.cn/s/blog_6ff05a2c0100mjrw.html
ORACLE]关于delete释放表存储空间的中度研究
(2010-11-17 16:47:10)
标签: |
分类: Oracle |
在给QA部门做Oracle基础原理分享时,有位同学提问,他说他在测试的时候造了一批数据,例如有1000万,然后按照一定的条件删掉了100万,接着又新插入了100万,结果却报表空间不足的错误。他的疑问是新插入的100万难道不能重用之前删除的100万的存储空间吗?
我这里由浅入深地按照问答的形式来进行研究解答。
1.insert操作能否重用delete操作释放的表存储空间?
答案是肯定的,如果Oracle连这一点都做不到那现在肯定早已关门大吉了。简单实验如下:
ETL@RACTEST> create table ttt1 (a char(2000));
Table created.
ETL@RACTEST> Select
owner,segment_name,Sum(bytes)/1024/1024||'MB' as sizes
OWNER
-----
ETL
ETL@RACTEST> insert into ttt1 select 'a' from dual connect by
level<=100;
100 rows created.
Elapsed: 00:00:00.00
ETL@RACTEST> commit;
Commit complete.
Elapsed: 00:00:00.00
ETL@RACTEST> Select
owner,segment_name,Sum(bytes)/1024/1024||'MB' as sizes
OWNER SEGME SIZES
----- ----- ------------------------------------------
ETL
ETL@RACTEST> delete from ttt1;
100 rows deleted.
Elapsed: 00:00:00.01
ETL@RACTEST> commit;
Commit complete.
Elapsed: 00:00:00.00
ETL@RACTEST> Select
owner,segment_name,Sum(bytes)/1024/1024||'MB' as sizes
OWNER SEGME SIZES
----- ----- ------------------------------------------
ETL
ETL@RACTEST> insert into ttt1 select 'a' from dual connect by
level<=100;
100 rows created.
Elapsed: 00:00:00.01
ETL@RACTEST> commit;
Commit complete.
Elapsed: 00:00:00.00
ETL@RACTEST> Select
owner,segment_name,Sum(bytes)/1024/1024||'MB' as sizes
OWNER SEGME SIZES
----- ----- ------------------------------------------
ETL
2.Jack提的问题,为什么删除100万,再插入100万会报表空间不足。
有几个原因,我这里先讲两个简单的,下面再讲主要原因。
第一个原因:退一万步讲,新插入的100万数据通常和之前删除的100万数据不完全一样,有可能新插的100万行数据比之前删除的100万行数据占用的存储空间多,这是正常的。例如一个字段定义为varchar2(4000),那么存'a'和存'abcdefg'所占用的存储空间就不同。
第二个原因:表的一个数据块有可利用的存储空间后会被放入freelist链表中,通常会被放到链尾。而申请新块时可能从freelist的链头申请,因此可能不用之前释放的块而申请新块,因为高水标记只能增长不能减小,因此表的存储占用也只会增长不会减小,这样报表空间不足也是可能的。
3.是不是delete释放的存储一定会被重用?
答案是否定的,先举个例子,后面再说明原因。
ETL@RACTEST> create table ttt5(a char(800),b CHAR(2));
Table created.
ETL@RACTEST> insert into ttt5 select 'a',level from dual connect
by level<=20;
20 rows created.
Elapsed: 00:00:00.00
ETL@RACTEST> commit;
Commit complete.
ETL@RACTEST> select b,dbms_rowid.rowid_block_number(rowid) rn
from ttt5 order by to_number(b);
B
-- ----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
20 rows selected.
ETL@RACTEST> ETL@RACTEST> delete from ttt5 where
b<=2;
2 rows deleted.
Elapsed: 00:00:00.00
ETL@RACTEST> commit;
Commit complete.
之后我删除掉2条数据。
ETL@RACTEST> select b,dbms_rowid.rowid_block_number(rowid) rn
from ttt5 order by to_number(b);
B
-- ----------
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
18 rows selected.
ETL@RACTEST> insert into ttt5 select 'a','b' from dual connect
by level<=10000;
10000 rows created.
Elapsed: 00:00:00.08
ETL@RACTEST> commit;
Commit complete.
然后我向表插入大量数据。
ETL@RACTEST> select b,dbms_rowid.rowid_block_number(rowid) rn
from ttt5 where dbms_rowid.rowid_block_number(rowid)=47016;
B
-- ----------
3
4
5
6
7
8
6 rows selected.
4.那delete释放的存储空间什么时候会被重用?
再举个例子,建表操作都一样:
ETL@RACTEST> create table ttt4(a char(800),b CHAR(2));
Table created.
Elapsed: 00:00:00.00
ETL@RACTEST>
ETL@RACTEST>
20 rows created.
Elapsed: 00:00:00.00
ETL@RACTEST>
ETL@RACTEST> commit;
Commit complete.
Elapsed: 00:00:00.00
ETL@RACTEST> select b,dbms_rowid.rowid_block_number(rowid) rn
from ttt4 order by to_number(b);
B
-- ----------
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
20 rows selected.
ETL@RACTEST> delete from ttt4 where b<=3;
3 rows deleted.
Elapsed: 00:00:00.00
ETL@RACTEST> commit;
Commit complete.
然后还是插入大量数据
ETL@RACTEST> insert into ttt4 select 'a','b' from dual connect
by level<=10000;
10000 rows created.
Elapsed: 00:00:00.49
ETL@RACTEST> commit;
Commit complete.
再查一下:
ETL@RACTEST> select b,dbms_rowid.rowid_block_number(rowid) rn
from ttt4 where dbms_rowid.rowid_block_number(rowid)=47000;
B
-- ----------
b
b
3
4
5
6
7
8
好,我解释一下原因。原来一个表有两个参数:PCT_FREE、PCT_USED。PCT_FREE大致为一个块剩余空间占总块的比重,PCT_USED大致为一个块使用的空间占总块的比重。我们来看一下:
ETL@RACTEST> select dbms_metadata.get_ddl('TABLE','TTT4','ETL')
from dual;
DBMS_METADATA.GET_DDL('TABLE','TTT4','ETL')
--------------------------------------------------------------------------------
这两个参数有什么用呢?ITPUB上有人这样解释:
INSERT、UPDATE的时候如果BLOCK剩余空间小于PCT_FREE相对应的大小,就从FREELIST中去掉,如果DELETE的时候小于PCT_USED相对应的大小,就加入FREELIST中。
这样就应该很明确了吧,上面的实验删除TTT5的2行时,块使用的空间占总空间的比重仍然大于40%,所以块没有被放到freelist中,而TTT4的表删了3行,块使用的空间占总空间的比重小于40%了,所以被放到freelist中,就可以被重用了。好了,真相大白了!
最后给个建议,Oracle的这两个默认参数并不一定在任何情况下都是最理想的。例如如果我们的表从来不做update操作,那么在建表时PCT_FREE可以设置为0,如果我们想充分利用delete释放的空间,那么PCTUSED可以设置得高一点,70,80,甚至90都可以!