Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Pauline.

Asked: January 26, 2001 - 3:53 pm UTC

Last updated: July 28, 2009 - 8:46 pm UTC

Version: oracle 8.1.5

Viewed 1000+ times

You Asked

Hi Tom,
For finding and eliminating migrated or chained rows, I have a
question.
To identify the migrated or chained rows, I need to run
$ORACLE_HOME/rdbms/admin/utlchain.sql
and then do

SQL> analyze table table_name list chained rows;
SQL> select * from chained_rows;

Here I may guess some talbe has migrated or chained rows,and put
the table name in analyze command. But assume I have lot of tables but don't know which table has migrated or chained rows,
how should I do the analyze?

Thanks.

Pauline



and Tom said...

If you have lots of tables -- and you do not know which may or may not contain chained rows, you'll need to analyze them all to discover which do and how many they have.

In order to discover which have chained rows, an analyze will be needed -- or, a SELECT COUNT() can be used if you don't want to analyze. A SELECT COUNT can be used to tell you if you have lots of chained/migrated rows in a table. We can do this by watching the statistic 'table fetch continued row'. You might even combine this with SAMPLE to only look at some percentage of the table.

Consider this small example. It will use a SELECT to determine the amount of chaining.


ops$tkyte@ORA8I.WORLD> create table t1
( a char(2000) default '*',
2 b char(2000) default '*',
3 c char(2000) default '*',
4 d char(2000) default '*',
5 e char(2000) default '*' );

Table created.

That is a table where if a,b,c,d,e have values -- the row will be chained as the rowsize is 10,000 bytes which is bigger then my 8k blocksize

ops$tkyte@ORA8I.WORLD> create table t2 ( a varchar2(4000),
2 b varchar2(4000),
3 c varchar2(4000) );

Table created.

this is a table that could be chained, if we put 4000 bytes into each of a,b,c

ops$tkyte@ORA8I.WORLD> insert into t1 values ( 'a', 'b', 'c', 'd', 'e' );
ops$tkyte@ORA8I.WORLD> insert into t1 values ( 'a', 'b', 'c', 'd', 'e' );
ops$tkyte@ORA8I.WORLD> insert into t1 values ( 'a', 'b', 'c', 'd', 'e' );
ops$tkyte@ORA8I.WORLD> insert into t1 values ( 'a', 'b', 'c', 'd', 'e' );
ops$tkyte@ORA8I.WORLD> insert into t1 values ( 'a', 'b', 'c', 'd', 'e' );

ops$tkyte@ORA8I.WORLD> insert into t2 values ( 'a', 'b', 'c' );
ops$tkyte@ORA8I.WORLD> insert into t2 values ( 'a', 'b', 'c' );
ops$tkyte@ORA8I.WORLD> insert into t2 values ( 'a', 'b', 'c' );
ops$tkyte@ORA8I.WORLD> insert into t2 values ( 'a', 'b', 'c' );
ops$tkyte@ORA8I.WORLD> insert into t2 values ( 'a', 'b', 'c' );

So, we put 5 rows into each... Now we are ready to test. We'll start by using COLUMN VALUE NEW_VAL V to let us save the starting "table fetch continued row" value in our session

ops$tkyte@ORA8I.WORLD> column value new_val V

ops$tkyte@ORA8I.WORLD> select b.value
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /

VALUE
----------
50

Now, to show how to do the count correctly, I'll start by doing it wrong! I'll select the count of the first column A:

ops$tkyte@ORA8I.WORLD> select count( a ) from t1;

COUNT(A)
----------
5

ops$tkyte@ORA8I.WORLD> select b.value-&V
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /
old 1: select b.value-&V
new 1: select b.value- 50

B.VALUE-50
----------
0

According to that, we did NO 'table fetch continued row'. That makes sense because A would be expected to be on the first block of the row. We never fetched the rest of the row -- we didn't need to (this also shows why you would want to put your "stable" columns that are frequently fetched FIRST in the create table -- put the columns that will cause chaining LAST. If you fetch the stable columns like dates, ids and such freqently and don't fetch the "big" columns that cause chaining as often -- chaining won't affect you as much)

So, lets see how to measure this correctly now:


ops$tkyte@ORA8I.WORLD> select b.value
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /

VALUE
----------
50

ops$tkyte@ORA8I.WORLD> select count( e ) from t1;

COUNT(E)
----------
5

ops$tkyte@ORA8I.WORLD> select b.value-&V
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /
old 1: select b.value-&V
new 1: select b.value- 50

B.VALUE-50
----------
5

There we go -- this shows that we did 5 table fetch continued rows in order to count the E column. This is what we were expecting. What you need to do is count the LAST column in the table -- if the row is chained, we will need to fetch the continued row to get that column.

Now lets try our unchained table:


ops$tkyte@ORA8I.WORLD> select b.value
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /

VALUE
----------
55

ops$tkyte@ORA8I.WORLD> select count( c ) from t2;

COUNT(C)
----------
5

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select b.value-&V
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /
old 1: select b.value-&V
new 1: select b.value- 55

B.VALUE-55
----------
0

Ok, it has no chaining yet, as expected. We'll update to force some and check again:

ops$tkyte@ORA8I.WORLD> update t2 set a = rpad('*',4000,'*'),
2 b = rpad('*',4000,'*'),
3 c = rpad('*',4000,'*')
4 /

5 rows updated.

ops$tkyte@ORA8I.WORLD> select b.value
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /

VALUE
----------
55

ops$tkyte@ORA8I.WORLD> select count( c ) from t2;

COUNT(C)
----------
5

ops$tkyte@ORA8I.WORLD>
ops$tkyte@ORA8I.WORLD> select b.value-&V
2 from v$statname a, v$mystat b
3 where a.statistic# = b.statistic#
4 and a.name = 'table fetch continued row'
5 /
old 1: select b.value-&V
new 1: select b.value- 55

B.VALUE-55
----------
5


That shows that the update cause the 5 rows we had to chain (they became 12,000 bytes in length, they have to be chained).



Now, this also points out something interesting -- if your row size is close to your blocksize OR you have a long/long raw, you will have chained rows and you will not be able to get rid of them by definition. Don't even try, it won't be possible!

Rating

  (4 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Should we reconsider the design?

Drew, May 23, 2002 - 2:43 pm UTC

Tom,
What is exactly effects do chained rows has on performance? I know it's not good but in what way? We have a table that has 80 columns, among them are more than 10 VARCHAR2(1000). That certainly will cause chained rows no matter what because our db_block_size is 8K. Sould we consider changing the table structure or it won't hurt? We don't have much data now. With time going on and more data are in the table, will it cause any performance problem?
Thank you very much in advance.

Tom Kyte
May 23, 2002 - 3:38 pm UTC

it won't cause chaining "no mater what" -- only when you have more then 8000 bytes of data in a row will it cause chaining (since they are varchar2's, they VARY in length).

Do not change the data structure -- you would be introducing a join or something and that is even worse.

Chaining just means we have to read more then one block in order to piece the row together (when you want columns that are on the chained block). that is better then breaking the table into two or something. it is unavoidable when the row exceeds the block size

A reader, July 25, 2009 - 9:54 pm UTC

1-)
Sir,why do you get initially 50.
You havent performed any query !!!! 


ops$tkyte@ORA8I.WORLD> column value new_val V

ops$tkyte@ORA8I.WORLD> select b.value
  2  from v$statname a, v$mystat b
  3  where a.statistic# = b.statistic#
  4    and a.name = 'table fetch continued row'
  5  /

     VALUE
----------
        50



2-)
Suppose I have chained rows, Is there a way to get rid of these chained rows?
I guess reorganization doesnt work for chaining issue.

Tom Kyte
July 26, 2009 - 7:58 am UTC

1) how do you know I didn't? Obviously - I did. I did not log out and log in right then and there, I was using an existing session.

2) if you have chained rows, not migrated, they are chained because the row does not fit on a single block OR because the row has more than 255 columns. The only way to get rid of rows too big for a block is to use a block that is big enough to hold them - but probably you would just live with what you have (you would NOT use multiple block sizes to 'fix' these rows, you would have to recreate the entire database). If the number of columns exceeds 255, they will be chained and there is nothing you can do about it.

Scofield

A reader, July 25, 2009 - 10:10 pm UTC

Respected Sir,
Regarding the row chaining issue;
Instead of full scan, if we use an index to go the last column,
Do I still suffer from row chaning?

Tom Kyte
July 26, 2009 - 8:02 am UTC

if you mean:

our query plan shows we use the index and not the table to retrieve a column (eg: there is no table access by index rowid, just the index <whatever> scan), will we still do a table fetch continued row?

No, you will not, you will only get that when you attempt to retrieve the row from the table.


row size < block size

Jatin, July 28, 2009 - 8:56 am UTC

Hi Tom

In case I have a 4k block size and all my freelist blocks are 30 % (1.2 K) full; what will be the behaviour of inserting a new row with 3k block size ? Will it select a new block ahead of the HWM or will it lead to row chaining.. (which by definition is not possible for rows having size < block size)

Thanks for your replies.

Rgds
Jatin
Tom Kyte
July 28, 2009 - 8:46 pm UTC

If you do not have a block with at least 3k free on it, it would new a block and put the row there. In all probability - it would also do that since by default you have 10% free set and you would need MORE than 3k free on the block to insert that row.


we will not chain unless

a) row is too big for an empty block
b) row has more than 255 columns

so, assuming your row has less than 255 columns - and we know it can fit on a single block, it will not chain