There are so many things that can go wrong when you start using tables with more than 255 columns. Here’s one I discovered partly because I was thinking about a client requirement, partly because I had a vague memory of a change in behaviour in 12c and Stefan Koehler pointed me to a blog note by Sayan Malakshinov when I asked the Oak Table if anyone remembered seeing the relevant note.
Enough of the roundabout route, I’m going to start with a bit of code to create a table, stick a row in it, then update that row:
rem rem Script: wide_table_4.sql rem Author: Jonathan Lewis rem Dated: May 2017 rem rem Last tested rem 12.2.0.1 rem 12.1.0.2 rem 11.2.0,4 rem set pagesize 0 set feedback off spool temp.sql prompt create table t1( select 'col' || to_char(rownum,'fm0000') || ' varchar2(10),' from all_objects where rownum <= 320 ; prompt col0321 varchar2(10) prompt ) prompt / spool off @temp set pagesize 40 set feedback on insert into t1 (col0010, col0280) values ('0010','0280'); commit; update t1 set col0320 ='0320'; commit; column file_no new_value m_file_no column block_no new_value m_block_no select dbms_rowid.rowid_relative_fno(rowid) file_no, dbms_rowid.rowid_block_number(rowid) block_no, dbms_rowid.rowid_row_number(rowid) row_no from t1 ; alter system flush buffer_cache; alter system dump datafile &m_file_no block &m_block_no;
So I’ve written one of those horrible scripts that writes a script and then runs it – something you really shouldn’t do in a production system. The script creates a table with 320 columns and inserts a row that populates columns 10 and 280. That gets me two row pieces, one consisting of the 255 columns from columns 26 to 280 that goes in as row piece 0, the other consisting of the first 25 columns that goes in as row piece 1; the remaining 40 columns are not populated so Oracle “forgets” about them (“trailing nulls take no space”). The script then updates the row by setting column 320 to a non-null value.
For convenience I’ve then generated the file and block number (and row number, just to show its head piece went in as row 1 rather than row 0) of the row and done a symbolic block dump. The question is: what am I going to see in that block dump ?
Answers (part 1)
Here’s an extract from the block dump from 11.2.0.4 (12.1.0.2 is similar) – though I’ve cut out a lot of lines reporting the NULL columns:
ntab=1 nrow=2 frre=-1 fsbo=0x16 fseo=0x1e54 avsp=0x1e3e tosp=0x1f13 0xe:pti[0] nrow=2 offs=0 0x12:pri[0] offs=0x1e7a 0x14:pri[1] offs=0x1e54 block_row_dump: tab 0, row 0, @0x1e7a tl: 49 fb: -------- lb: 0x2 cc: 40 nrid: 0x014000a7.0 col 0: *NULL* col 1: *NULL* col 2: *NULL* ... col 37: *NULL* col 38: *NULL* col 39: *NULL* tab 0, row 1, @0x1e54 tl: 38 fb: --H-F--- lb: 0x2 cc: 25 nrid: 0x014000a3.0 col 0: *NULL* col 1: *NULL* col 2: *NULL* ... col 22: *NULL* col 23: *NULL* col 24: *NULL* end_of_block_dump
The block holds two row pieces and the piece stored as “row 1” is the starting row piece (the H in the flag byte (fb) tells us this). This row piece consists of 25 columns. The next rowpiece (identified by nrid:) is row zero in block 0x014000a3 – that’s block 163 of file 5 – which is the same block as the first row piece. When we look at row zero we see that it holds 40 columns, all null; it’s pointing to a third row piece at row zero in block 0x014000a7 (file 5, block 167) and, as corroborative evidence, we can also see that the flag byte has no bits set and that tells us that this is just a boring “somewhere in the middle” bit of a row. So it looks like we have to follow the pointer to find the last 255 columns of the table. So let’s take a look at the dump of file 5 block 167:
fsbo=0x14 fseo=0x1e76 avsp=0x1e62 tosp=0x1e62 0xe:pti[0] nrow=1 offs=0 0x12:pri[0] offs=0x1e76 block_row_dump: tab 0, row 0, @0x1e76 tl: 266 fb: -----L-- lb: 0x1 cc: 255 col 0: *NULL* col 1: *NULL* col 2: *NULL* ... col 251: *NULL* col 252: *NULL* col 253: *NULL* col 254: [ 4] 30 33 32 30 end_of_block_dump
Take note of the L in the flag byte – that tells us that we’re looking at the last row piece of a multi-piece row. It’s the last 255 columns we were looking for.
The mechanics have worked as follows
- On the simple insert Oracle split the used 280 columns into (25, 255)
- On the update we grew the used column count from 280 to 320, adding 40 columns. Oracle extended the 255 column row piece to 29, then split that rowpiece (40, 255) leaving 40 columns in the original block and migrating 255 column to a new block.
So a row that could have been stored as two pieces in one block is now three pieces spread over two blocks – and there’s worse to come.
Go back to the original block dump and check the used space. A good first approximation would be to check the “tl:” (total length) value for each row – this gives you: 49 + 38 bytes; add on a couple of hundred bytes for the general block overhead and stuff like the interested transaction list (ITL) and you find you’ve used less than 300 bytes in the block. But I’ve got a little procedure (I published this version of it some time ago) to check for free and used space – and this is what it said about the (ASSM) segment that holds this table:
Unformatted : 44 / 360,448 Freespace 1 ( 0 - 25% free) : 0 / 0 Freespace 2 ( 25 - 50% free) : 0 / 0 Freespace 3 ( 50 - 75% free) : 0 / 0 Freespace 4 ( 75 - 100% free) : 15 / 122,880 Full : 1 / 8,192
Take particular note of the “Full” block at the end of the report – that’s the block where we’ve used up rather less than 300 bytes. In fact if you look again at the first block dump you’ll see the avsp (available space) and tosp (total space) figures of 0x1e3e and 0x1f13 bytes (7,742 and 7,955 bytes). There’s loads of space in the block but the block has been marked in the bitmap space management map as full. That’s really bad news.
On the plus side 12.2 behaves differently, as noted by Sayan in his blog note. We still get the third row piece but it’s in the same block as the first two and the block doesn’t get marked as full in the bitmap.
And there’s still more to come – but it will have to wait a little longer.
[…] find there are always more waiting to be found. It’s been some time since I last wrote about tables with more than 255 columns, and I said then that there was more to come. In the last article I described how adding a few […]
Pingback by 255 Columns | Oracle Scratchpad — February 28, 2018 @ 12:28 pm GMT Feb 28,2018 |
[…] 255 columns (May 2017) – strange extremes in space usage with wide tables […]
Pingback by 255 column catalogue | Oracle Scratchpad — January 25, 2022 @ 12:17 pm GMT Jan 25,2022 |