Hi,
We have a table with a BLOB column, general observation - about 50% to 60% of total DB space is occupied by this table.
In order to reduce overall DB space, we run purge program which will selectively delete older entries. However we observed that space is not free and new inserts consuming more space.
In my understanding, Once LOB space is allocated it does not reclaim unless table is dropped or truncated. After deleting rows with LOB, subsequent insert will reuse the space free by delete. However we do not see that is happening.
We have millions of entries in this table and purge will delete not more than 50% in a single run. We are daily adding new entries. We have INSERT, SELECT and DELETE operation happening in this table.
To simulate this, we have tried a test program and found that Oracle is not reusing the space. Explicit SHRINK SPACE can reclaim space.
Questions -
1. In which case Oracle will reuse space released by deleting rows having LOB?
2. Is there any recommendation to how to reclaim/reuse LOB space?
3. Is there any recommendation to perform periodic SHRINK SPACE?
4. Will Automatic Segment Advisor maintenance task will reclaim this space?
5. Which oracle edition does not include Automatic Segment Advisor automated task?
Thanks a lot.
-Sam
Test Program-- Create table and SPCREATE TABLE test_lob (id NUMBER, file_name VARCHAR2(45), image BLOB, timestamp DATE);
CREATE SEQUENCE test_lob_seq;
CREATE OR REPLACE DIRECTORY images_dir AS <some directory path having an image file>;
CREATE OR REPLACE PROCEDURE Load_BLOB_From_File_Image
AS
dest_loc BLOB;
file_name TEST_LOB.FILE_NAME%TYPE := 'iDevelopment_info_logo_2.tif';
src_loc BFILE := BFILENAME('IMAGES_DIR', file_name);
BEGIN
INSERT INTO test_lob (id, file_name, image, timestamp)
VALUES (test_lob_seq.nextval, file_name, empty_blob(), sysdate)
RETURNING image INTO dest_loc;
DBMS_LOB.OPEN(src_loc, DBMS_LOB.LOB_READONLY);
DBMS_LOB.OPEN(dest_loc, DBMS_LOB.LOB_READWRITE);
DBMS_LOB.LOADFROMFILE(
dest_lob => dest_loc
, src_lob => src_loc
, amount => DBMS_LOB.getLength(src_loc));
DBMS_LOB.CLOSE(dest_loc);
DBMS_LOB.CLOSE(src_loc);
COMMIT;
END;
-- Insert 1000 rowsBEGIN
FOR i IN 1 .. 1000
LOOP
Load_BLOB_From_File_Image();
END LOOP;
END;
-- find usage sizeSELECT table_name, column_name, segment_name, a.bytes FROM dba_segments a JOIN dba_lobs b USING (owner, segment_name) WHERE b.table_name = 'TEST_LOB';
TABLE_NAME COLUMN_NAME SEGMENT_NAME BYTES
------------ -------------- ---------------------------- ----------
TEST_LOB IMAGE SYS_LOB0000152544C00003$$
25165824-- Delete half rows DELETE FROM test_lob where MOD(ID,2)=0;
-- find usage sizeSELECT table_name, column_name, segment_name, a.bytes FROM dba_segments a JOIN dba_lobs b USING (owner, segment_name) WHERE b.table_name = 'TEST_LOB';
TABLE_NAME COLUMN_NAME SEGMENT_NAME BYTES
------------ -------------- ---------------------------- ----------
TEST_LOB IMAGE SYS_LOB0000152544C00003$$
25165824-- Insert 1000 rowsBEGIN
FOR i IN 1 .. 1000
LOOP
Load_BLOB_From_File_Image();
END LOOP;
END;
-- find usage sizeSELECT table_name, column_name, segment_name, a.bytes FROM dba_segments a JOIN dba_lobs b USING (owner, segment_name) WHERE b.table_name = 'TEST_LOB';
TABLE_NAME COLUMN_NAME SEGMENT_NAME BYTES
------------ -------------- ---------------------------- ----------
TEST_LOB IMAGE SYS_LOB0000152544C00003$$
50331648-- Explicit shrink ALTER TABLE test_lob MODIFY LOB (image) (SHRINK SPACE);
-- find usage sizeSELECT table_name, column_name, segment_name, a.bytes FROM dba_segments a JOIN dba_lobs b USING (owner, segment_name) WHERE b.table_name = 'TEST_LOB';
TABLE_NAME COLUMN_NAME SEGMENT_NAME BYTES
------------ -------------- ---------------------------- ----------
TEST_LOB IMAGE SYS_LOB0000152544C00003$$
37617664
Thanks for the awesome test case.
If you're planning heavy duty transactional activity on lobs, then you really want to be moving to securefiles. Basicfile are "old school" :-)
By default, securefiles will do a balancing act between space growth and space reuse. EXample
SQL> select segment_name, bytes from dba_segments where tablespace_name = 'DEMO';
no rows selected
SQL>
SQL> CREATE TABLE test_lob (id NUMBER, file_name VARCHAR2(45), image BLOB, timestamp DATE)
2 lob ( image ) store as securefile
3 tablespace demo;
Table created.
SQL>
SQL> CREATE SEQUENCE test_lob_seq;
Sequence created.
SQL>
SQL> CREATE OR REPLACE DIRECTORY images_dir AS 'c:\temp';
Directory created.
SQL>
SQL> CREATE OR REPLACE PROCEDURE Load_BLOB_From_File_Image
2 AS
3 dest_loc BLOB;
4 file_name TEST_LOB.FILE_NAME%TYPE := '12c_itnerface.jpg';
5 src_loc BFILE := BFILENAME('IMAGES_DIR', file_name);
6 BEGIN
7 INSERT INTO test_lob (id, file_name, image, timestamp)
8 VALUES (test_lob_seq.nextval, file_name, empty_blob(), sysdate)
9 RETURNING image INTO dest_loc;
10 DBMS_LOB.OPEN(src_loc, DBMS_LOB.LOB_READONLY);
11 DBMS_LOB.OPEN(dest_loc, DBMS_LOB.LOB_READWRITE);
12 DBMS_LOB.LOADFROMFILE(
13 dest_lob => dest_loc
14 , src_lob => src_loc
15 , amount => DBMS_LOB.getLength(src_loc));
16 DBMS_LOB.CLOSE(dest_loc);
17 DBMS_LOB.CLOSE(src_loc);
18 COMMIT;
19 END;
20 /
Procedure created.
SQL>
SQL> BEGIN
2 FOR i IN 1 .. 200
3 LOOP
4 Load_BLOB_From_File_Image();
5 END LOOP;
6 END;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> select segment_name, bytes from dba_segments where tablespace_name = 'DEMO';
SEGMENT_NAME BYTES
------------------------------ ----------
TEST_LOB 65536
SYS_IL0000081936C00003$$ 65536
SYS_LOB0000081936C00003$$ 126025728
3 rows selected.
SQL>
SQL> DELETE FROM test_lob where MOD(ID,2)=0;
100 rows deleted.
SQL> commit;
Commit complete.
SQL>
SQL>
SQL> BEGIN
2 FOR i IN 1 .. 200
3 LOOP
4 Load_BLOB_From_File_Image();
5 END LOOP;
6 END;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> select segment_name, bytes from dba_segments where tablespace_name = 'DEMO';
SEGMENT_NAME BYTES
------------------------------ ----------
TEST_LOB 65536
SYS_IL0000081936C00003$$ 65536
SYS_LOB0000081936C00003$$ 201523200
3 rows selected.
SQL>
SQL>
So I had 125M of data, so with half removed, I'd expect to grow out to around 188M and I grew to 200M instead. So I had reasonable but not complete reuse, because I'm holding on to some of the older chunks for read consistency.
But I can also get to choose how "brutal" I want to be - I can decide "Hey, I dont have any read consistecy issues here, I want full reuse", by explicitly stating the retention
SQL> select segment_name, bytes from dba_segments where tablespace_name = 'DEMO';
no rows selected
SQL>
SQL> CREATE TABLE test_lob (id NUMBER, file_name VARCHAR2(45), image BLOB, timestamp DATE)
2 lob ( image ) store as securefile ( retention none) <<<=====
3 tablespace demo;
Table created.
SQL>
SQL> CREATE SEQUENCE test_lob_seq;
Sequence created.
SQL>
SQL> CREATE OR REPLACE DIRECTORY images_dir AS 'c:\temp';
Directory created.
SQL>
SQL> CREATE OR REPLACE PROCEDURE Load_BLOB_From_File_Image
2 AS
3 dest_loc BLOB;
4 file_name TEST_LOB.FILE_NAME%TYPE := '12c_itnerface.jpg';
5 src_loc BFILE := BFILENAME('IMAGES_DIR', file_name);
6 BEGIN
7 INSERT INTO test_lob (id, file_name, image, timestamp)
8 VALUES (test_lob_seq.nextval, file_name, empty_blob(), sysdate)
9 RETURNING image INTO dest_loc;
10 DBMS_LOB.OPEN(src_loc, DBMS_LOB.LOB_READONLY);
11 DBMS_LOB.OPEN(dest_loc, DBMS_LOB.LOB_READWRITE);
12 DBMS_LOB.LOADFROMFILE(
13 dest_lob => dest_loc
14 , src_lob => src_loc
15 , amount => DBMS_LOB.getLength(src_loc));
16 DBMS_LOB.CLOSE(dest_loc);
17 DBMS_LOB.CLOSE(src_loc);
18 COMMIT;
19 END;
20 /
Procedure created.
SQL>
SQL> BEGIN
2 FOR i IN 1 .. 200
3 LOOP
4 Load_BLOB_From_File_Image();
5 END LOOP;
6 END;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> select segment_name, bytes from dba_segments where tablespace_name = 'DEMO';
SEGMENT_NAME BYTES
------------------------------ ----------
TEST_LOB 65536
SYS_IL0000081940C00003$$ 65536
SYS_LOB0000081940C00003$$ 126025728
3 rows selected.
SQL>
SQL> DELETE FROM test_lob where MOD(ID,2)=0;
100 rows deleted.
SQL> commit;
Commit complete.
SQL>
SQL>
SQL> BEGIN
2 FOR i IN 1 .. 200
3 LOOP
4 Load_BLOB_From_File_Image();
5 END LOOP;
6 END;
7 /
PL/SQL procedure successfully completed.
SQL>
SQL> select segment_name, bytes from dba_segments where tablespace_name = 'DEMO';
SEGMENT_NAME BYTES
------------------------------ ----------
TEST_LOB 65536
SYS_IL0000081940C00003$$ 65536
SYS_LOB0000081940C00003$$ 184745984
So you can choose whatever suits your needs best.