I said in my previous post that I put LOB space scripts in my GitHub repository and I wanted to explain a little more here. I have two databases that were growing rapidly and the top segment in each was a LOB segment. For one database the top LOB was a CLOB and for the other it was a BLOB. In both cases there were many inserts and deletes against the tables with the largest LOB segment. I was trying to find out if space was wasted and unusable in the LOB segments. Best I can tell these applications are reusing space when LOBs are deleted. These were both BasicFiles LOBs in 11.2 Oracle. And for the CLOB the character set was such that each character used one byte. Also, both databases had an 8192-byte block size.
A lot of this work was inspired by this Oracle support document:
LOB space not released after delete (Doc ID 2285007.1)
The title made me think the LOB space was wasted after the deletes. But this article had an interesting disclaimer highlighted in a box:
“Note: be aware that the unused space after deletion can be reused in the
LOB segment for further insert after retention time is passed.”
As far as I can tell, Oracle’s disclaimer is correct, at least in our situation. Unused LOB space left after a deletion is reused.
Here is a description of the LOB scripts in my GitHub repository:
spacetest.sql – This was my first script. I used various versions of this script to test different metrics that I could find about LOBs. This was helpful because I started very simple and tried to understand what each source of information was telling me. I really struggled to understand what AVG_ROW_LEN from USER_TABLES was telling me until I realized that I was hitting this bug or one like it:
Bug 14651892 AVG_ROW_LEN computed incorrectly on LOB when AUTO_SAMPLE_SIZE is used
It also took me a long time to understand why DBMS_SPACE.SPACE_USAGE only reported full or unformatted blocks for LOB segments. LOBs are all or nothing for a block. Each LOB that is not stored inline for a row fully occupies one or more blocks. With all these observations I throw in the caveat that this is the best I can tell based on my observations on my system.
The database with the CLOB uses this character set: WE8MSWIN1252.
This Oracle document says that character sets like this have a single byte per character in a CLOB:
CLOBs and NCLOBs character set storage in Oracle Release 8i, 9i, 10g and higher (Doc ID 257772.1)
“CLOBs when using fixed width character set NLS_CHARACTERSET are stored in the NLS_CHARACTERSET character set on disk. Examples are WE8MSWIN1252 …”
This matters because dbms_lob.getlength returns the size of a CLOB in characters. In some character sets like AL32UTF8 you have to multiply dbms_lob.getlength’s output by 2 to get the bytes for the CLOB.
Given all the information I got from playing with spacetest.sql I created the next two scripts to feed information into the final space script.
blobinlinecutoff.sql – Takes an integer from 1 to 8191 and creates a row in a table with a LOB of that many bytes in size and it outputs whether that LOB is an inline LOB or not. I used this to find the cutoff point between LOBs that fit in a row and ones that did not. I found the cutoff to be 3964 bytes in all the scenarios I tried. I use this cutoff to ignore inline LOBs when counting the total LOB space in the column’s LOB segment.
inoneblock.sql – Takes an integer from 1 to 8191 and creates a row in a table with a LOB of that many bytes in size and it outputs whether that LOB fits fully in one block. Many LOBs are larger than one block which is only 8K for me so I wanted to calculate exactly how many blocks each LOB would take. I got 8132 in my tests which means 60 bytes per block are unusable for LOB storage. So, I would need to divide the LOB size in bytes by 8132 and round the resulting number up to the next integer to get the number of blocks occupied by the LOB.
I plugged the results from blobinlinecutoff.sql and inoneblock.sql into my final script:
lobspace.sql – Outputs information about LOB space used to show how much is allocated but not used by current LOBs. This space could either be available for reuse by future inserts or it could be wasted due to some bug.
Notice that the top of the script has the input from the previous two:
inlinecutoff number := 3964; usableperblock number := 8132;
You also put the table owner, name, and LOB column name at the top as parameters.
The output looks like this on my BLOB column table:
-------------------------------------- Table owner = MYOWNER Table name = MYTABLE LOB column name = MYLOBCOLUMN -------------------------------------- Number of rows in table = 380553608 Number of rows with lob in table row = 338491004 Number of rows with lob in lob segment = 42062604 Total lob segment size = 1496535 megabytes Total size of full lob segment blocks = 1473933.135288238525390625 megabytes Total lob space used in lob segment = 1462133.555019378662109375 megabytes -------------------------------------- Percentage of full blocks used = 99% PL/SQL procedure successfully completed. Elapsed: 07:46:50.26
I found that this database is not deleting all the rows that it is supposed to, so it is fully using all the space freed by the deletes and is continually adding space.
But the output from the other database (the one with the CLOB) looked different:
-------------------------------------- Table owner = MYOWNER2 Table name = MYTABLE2 LOB column name = MYLOBCOLUMN2 -------------------------------------- Number of rows in table = 66360290 Number of rows with lob in table row = 54200592 Number of rows with lob in lob segment = 12159698 Total lob segment size = 913999 megabytes Total size of full lob segment blocks = 906413.57425689697265625 megabytes Total lob space used in lob segment = 373804.97021484375 megabytes -------------------------------------- Percentage of full blocks used = 41% PL/SQL procedure successfully completed. Elapsed: 00:45:22.57
Notice that the percentage of full blocks used in the first database was 99% but in this one it is 41%. This made me really wonder if deleted space was really being freed for use by inserts. But digging further I found that we only keep 7 days of history on this table and back in November we hit a peak of activity which expanded the LOB segment. Since then, we have stopped adding new space to the LOB segment’s tablespace. It appears that we have plenty of space free for this LOB segment to absorb a new batch of inserts because more than half of the space in the LOB segment is free for reuse.
Here are the settings for the BLOB:
LOB (MYLOBCOLUMN) STORE AS BASICFILE ( TABLESPACE MYTABLESPACE ENABLE STORAGE IN ROW CHUNK 8192 RETENTION CACHE LOGGING
Here are the settings for the CLOB:
LOB (MYLOBCOLUMN2) STORE AS BASICFILE ( TABLESPACE MYTABLESPACE2 ENABLE STORAGE IN ROW CHUNK 8192 RETENTION NOCACHE LOGGING
Some blog posts that I studied for this:
I wanted to put this out there to help others but also myself. I do not want to forget some of the things I learned in the process. Also, if anyone out there has any feedback on this including any errors that I made it would be great to hear it.
Great read Bobby. Lobs, Blobs & Clobs can be a huge pain. But, if you understand the underlying way they really work, they are manageable. Thanks for the detailed breakdown of yet another gotcha with these things.
Thanks for your comment. The LOB stuff is a mess but I feel like I learned a few things that are helpful.