Showing posts with label index. Show all posts
Showing posts with label index. Show all posts

Saturday, October 1, 2011

Online index rebuild cleanup

 If session performing online index rebuild will be killed by mistake Oracle end up with some inconsistency inside dictionary. If you try to re-run killed command to create index once more database will complain that index already exist. Although if you try to drop index database will complain that index is in rebuild state and could not be dropped at that time.
To fix that issue you need to use package dbms_repair as showed in example:

  1. Building test table
    SQL> create table test tablespace users as select rownum id, 'xxxxxxxxxxxxxxxxxxxxxxxx' col1 from dba_source, dba_source where rownum < 10000000;
    Table created.
    
  2. New index build has been started with online clause and session has been killed
    SQL> create index test_index on test (id) tablespace users online;
    create index test_index on test (id) tablespace users online
                                                               *
    ERROR at line 1:
    ORA-03113: end-of-file on communication channel
    Process ID: 4356
    Session ID: 191 Serial number: 13
    
  3. Sanity check - index exist even if build has been never completed.
    SQL> select index_name from dba_indexes where table_name = 'TEST';
    INDEX_NAME
    ------------------------------
    TEST_INDEX
    
  4. Now let's try to drop it
    SQL> drop index test_index;
    drop index test_index
               *
    ERROR at line 1:
    ORA-08104: this index object 66960 is being online built or rebuilt
    
  5. So maybe we can create it again ?
    SQL> create index test_index on test (id) tablespace users online;
    create index test_index on test (id) tablespace users online
                 *
    ERROR at line 1:
    ORA-00955: name is already used by an existing object
    
  6. Let's clean it up and allow Oracle to drop index - ONLINE_INDEX_CLEAN need a object number which can be taken from drop index error message or using the following query (at least it worked for me).
    SQL> select min(object_id) from dba_objects where object_name = 'TEST_INDEX';
    
    MIN(OBJECT_ID)
    --------------
            66960
    
    SQL> declare
      2  ret boolean;
      3
      4  begin
      5      ret:=dbms_repair.ONLINE_INDEX_CLEAN(66960);
      6  end;
      7  /
    
    PL/SQL procedure successfully completed.
    
    and now let's check if index is still there
    SQL> select index_name from dba_indexes where table_name = 'TEST';
    no rows selected
    
Hope it help to solve some of your problems.
regards,
Marcin

Friday, June 10, 2011

Rolling partition and global index - do we need to rebuild ?

Some time ago (arrghhh - three month ago) I blogged about partition dropping when table has a global index - see. There was a example based on my tests. Now it is time to show some real figures. Both examples are sharing same scenario - about 2 years without touching indexes, 7 partitions are drop once per week. Partition drop is taking up to 6 - 7 hours. This time coalesce was not a option due to free up space requirement but it should help in future to avoid similar situations.
Example number 1
Table has around 562.000.000 rows and one partition has around 13.000.000 rows. Primary key index based on one number column growth to 140 GB and has been rebuilt to 13.9 GB. It is around 10 times less.
Example number 2 - bigger scale
Table has around 2.130.000.000 rows and one partition has around 45.000.000 rows.Primary key index based on one number column growth to 360 GB and has been rebuilt to 40 GB. It is 9 times less.
I put both examples here to show you that running coalesce for global indexes after partition drop can save you a lot of space and if done regularly prevent you from more resource consuming index rebuild.To be honest this is one of a few situations when I have seen any improvement (space wise this time) after index rebuild. But keep in mind that this is a special situation and for most of other cases you don't need to rebuild you indexes - if you think you need read Niall or Richard blog first.
regards,
Marcin

Monday, March 14, 2011

Rolling partition and global index coalesce

Resolving one of performance issues I figure out that primary key index is much bigger that it should be. Most of database activity is inserting data into partitioning table using some date field as a partitioning key. Rows can be inserted to every partition not only to most recent one. Daily job is dropping one oldest partition per day. Unfortunately primary key index is not partitioned and is maintained during partition drop as a global index. Correlation of these two things is a root cause of index size explosion.
Short test case below:
  • script test.sql has been used to check index size, number of rows in table and partition drop
  • in second session simple script inserting data into random partition was running continuously
Table definition:
  CREATE TABLE LOGS
   (    "LOG_ID" NUMBER NOT NULL ENABLE,
        "DELDATE" DATE NOT NULL ENABLE,
        "FILL" VARCHAR2(100),
         CONSTRAINT "LOGS_PK" PRIMARY KEY ("LOG_ID")
   )
  TABLESPACE "USERS"
  PARTITION BY RANGE ("DELDATE")
 (PARTITION "PARTITION1"  VALUES LESS THAN (TO_DATE(' 2011-02-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION2"  VALUES LESS THAN (TO_DATE(' 2011-03-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION3"  VALUES LESS THAN (TO_DATE(' 2011-04-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION4"  VALUES LESS THAN (TO_DATE(' 2011-05-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION5"  VALUES LESS THAN (TO_DATE(' 2011-06-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION6"  VALUES LESS THAN (TO_DATE(' 2011-07-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION7"  VALUES LESS THAN (TO_DATE(' 2011-08-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION8"  VALUES LESS THAN (TO_DATE(' 2011-09-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION9"  VALUES LESS THAN (TO_DATE(' 2011-10-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION10"  VALUES LESS THAN (TO_DATE(' 2011-11-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN')),
  PARTITION "PARTITION11"  VALUES LESS THAN (TO_DATE(' 2011-12-28 00:00:00', 'SYYYY-MM-DD HH24:MI:SS', 'NLS_CALENDAR=GREGORIAN'))
 );
Source of test.sql script 
prompt number of rows in table
 select count(*) from pioro.logs;
 
 prompt LOGS_PK index size
 select sum(bytes)/1024/1024 from dba_extents where segment_name = 'LOGS_PK';

 prompt calculating stats
 exec dbms_stats.gather_table_stats('PIORO','LOGS',cascade=>true, estimate_percent=>null);
 
 prompt rows/block without update global indexes
 select blevel, leaf_blocks, distinct_keys, NUM_ROWS, NUM_ROWS/leaf_blocks "rows/block" from dba_indexes 
where owner='PIORO' and table_name='LOGS' and leaf_blocks <> 0;

 prompt number of rows in table partition
 select count(*) from pioro.logs partition(&partname);
 set timing on 

 alter table pioro.logs drop partition &partname update global indexes;
Inserts script running in loop looks like

insert into logs select seq_log_id.nextval, to_date('2011-'|| trunc(dbms_random.value(2,9)) ||'-15','yyyy-mm-dd'),
lpad('x',70,'x') from all_source a, all_source b where rownum <= 3000000;
commit;
Here is a output from test.sql for first partition drop
SQL> @test
number of rows in table COUNT(*)                                                                                                 
----------                                                                                                          
  12000000                                                                                                          

Elapsed: 00:00:13.74
LOGS_PK index size

SUM(BYTES)/1024/1024                                                                                                
--------------------                                                                
242           

Elapsed: 00:00:00.51
calculating stats

PL/SQL procedure successfully completed.

Elapsed: 00:01:11.28
rows/block without update global indexes

    BLEVEL LEAF_BLOCKS DISTINCT_KEYS   NUM_ROWS rows/block                                                          
---------- ----------- ------------- ---------- ----------                                                          
         2       30000      12000000   12000000        400         

Elapsed: 00:00:00.03
number of rows in table partition
Enter value for partname: partition1
old   1:  select count(*) from pioro.logs partition(&partname)
new   1:  select count(*) from pioro.logs partition(partition1)

  COUNT(*)                                                                                                          
----------                                                                                                          
   3001361                                                                              
Elapsed: 00:00:00.35
Enter value for partname: partition1
old   1:  alter table pioro.logs drop partition &partname update global indexes
new   1:  alter table pioro.logs drop partition partition1 update global indexes

Table altered.

Elapsed: 00:00:55.37
As you can see before first drop average number of rows in one index leaf for primary key was 400. ( 8 kB database block ) Output from next run looks like:
SQL> @test
number of rows in table

  COUNT(*)   
----------   
  11998639   

Elapsed: 00:00:09.59
LOGS_PK index size

SUM(BYTES)/1024/1024  
--------------------  
                 296  

Elapsed: 00:00:01.12
calculating stats

PL/SQL procedure successfully completed.

Elapsed: 00:01:13.16
rows/block without update global indexes

    BLEVEL LEAF_BLOCKS DISTINCT_KEYS   NUM_ROWS rows/block    
---------- ----------- ------------- ---------- ----------    
         2       37500      11998639   11998639 319.963707    

Elapsed: 00:00:00.05
number of rows in table partition
Enter value for partname: partition2
old   1:  select count(*) from pioro.logs partition(&partname)
new   1:  select count(*) from pioro.logs partition(partition2)

  COUNT(*)                  
----------                  
   3749280                  

Elapsed: 00:00:00.43
Enter value for partname: partition2
old   1:  alter table pioro.logs drop partition &partname update global indexes
new   1:  alter table pioro.logs drop partition partition2 update global indexes

Table altered.

Elapsed: 00:01:22.85
Numbers of rows in one leaf block drop from 400 to 320 rows per block. After a few next runs it looks like:
SQL> @test
number of rows in table

  COUNT(*)                                                                                                          
----------                                                                                                          
   9748583                                                                                                          

Elapsed: 00:00:17.67
LOGS_PK index size

SUM(BYTES)/1024/1024      
--------------------      
                 414      

Elapsed: 00:00:00.85
calculating stats

PL/SQL procedure successfully completed.

Elapsed: 00:01:11.78
rows/block without update global indexes

    BLEVEL LEAF_BLOCKS DISTINCT_KEYS   NUM_ROWS rows/block        
---------- ----------- ------------- ---------- ----------        
         2       52500       9748583    9748583 185.687295        

Elapsed: 00:00:00.01
Now is really bad - only 186 rows in one leaf block and size of index segment is now 414 MB. It will have huge impact on database performance as much more index blocks have to be read during index range scans. Solution is quite simple and can be done online without huge overhead. It is a index coalesce - this command will consolidate a free space in existing index and provide more free blocks for particular index segment. See example:
SQL> alter index pioro.LOGS_PK coalesce;

Index altered.

Elapsed: 00:03:57.12
SQL> prompt number of rows in table partition
number of rows in table partition
SQL> select count(*) from pioro.logs partition(&partname);
Enter value for partname: partition4
old   1: select count(*) from pioro.logs partition(&partname)
new   1: select count(*) from pioro.logs partition(partition4)

  COUNT(*)                    
----------                    
   5248055                    

Elapsed: 00:00:10.47
SQL> alter table pioro.logs drop partition &partname update global indexes;
Enter value for partname: partition4
old   1: alter table pioro.logs drop partition &partname update global indexes
new   1: alter table pioro.logs drop partition partition4 update global indexes

Table altered.

Elapsed: 00:02:09.65
SQL> @test
number of rows in table

  COUNT(*)                          
----------                          
   7500528                          

Elapsed: 00:00:15.78
LOGS_PK index size

SUM(BYTES)/1024/1024      
--------------------      
                 414      

Elapsed: 00:00:01.47
calculating stats

PL/SQL procedure successfully completed.

Elapsed: 00:00:46.30
rows/block without update global indexes

    BLEVEL LEAF_BLOCKS DISTINCT_KEYS   NUM_ROWS rows/block      
---------- ----------- ------------- ---------- ----------      
         2       26682       7500528    7500528 281.108163      

Elapsed: 00:00:00.08
After index coalesce we can see that Oracle is able to allocate space from free block and size of index remain stable and number of rows in one leaf block is now increasing.
This post has been created based on test data and test scenario but after sucesfull implementation in life systems I will blog about real numbers from production databases.

Thursday, August 13, 2009

Oracle indexes

Hello,

Jonathan Lewis just started a series of posts about indexes. This is one of these things which has a flag "must to read". Please find some examples:
- Index fragmentation
- Index explosion part 1
- Index explosion part 2


regards,
Marcin

Tuesday, April 15, 2008

Big/small database block and indexes

There is a lot of myths about big and small database blocks for indexes.
From 9i there is a possibility to create tablespaces with different block size. A lot of people
think that there a solution for speed up a indexes. It was my assumption too, but I decided to
make some research on that. You can find my results and some remarks below.

Test nr 1.

Table definition

CREATE TABLE TEST1
(
A VARCHAR2(18 CHAR),
B VARCHAR2(18 CHAR),
B1 VARCHAR2(15 CHAR),
B2 VARCHAR2(16 CHAR),
B3 VARCHAR2(8 CHAR),
C1 DATE,
C2 DATE,
DUR VARCHAR2(6 CHAR),
E VARCHAR2(3 CHAR),
I VARCHAR2(40 CHAR)
)


There are 89999997 rows in this table,
and column A has 914174 different values.

I've wrote a piece of PL/SQL script which was looking
for random A value and then fetch all rows with this value
in loop. Time of looking for value and fetching all rows for it was
measured by timestamp variable in PL/SQL and then reported
to standard output. This script was executed in 'dry run' mode
2 times after database shutdown and then it was run in measured mode.
This same scenario was performed 12 times - 6 times for 8 kB block and
6 times for 16 kB.

declare

i int;
c int;
v_begin timestamp(9);
v_end timestamp(9);
v_interval INTERVAL DAY TO SECOND;


begin
i:=0;

for w in 1..1000 loop
v_begin := SYSTIMESTAMP;
c:=0;
for r in (select A, B, DUR from test where A = (select lpad(trunc(dbms_random.value(0,1000000)),18,'0') from dual)) loop
i:=r.duration;
c:=c+1;
end loop;
v_end := SYSTIMESTAMP;
v_interval := v_end - v_begin;
if (c<>0) then
dbms_output.put_line(to_char(v_interval) || ' ' || c);
end if;
end loop;
end;
/


Here are results:




So as you can see response time is almost the same and none of block size
is a winner.

When I made some more depth investigation, I found out that main SQL statement
is using index range scan to find rowid of block in table. In raw trace files,
I have found, as expected "db file sequential read" so Oracle was using single block
read to access a index and also single block read was used to access the table.
If database block size if below max IO request size it will be always executed in one step.

Here is example result from tkprof:

8 kB database block



16 kB database block




As you can see both execution of SQL has this same execution plan. But in case of 8 kB block SQL had better response time, then in case of 16 kB. There are one difference in executing both scenario - in 16 kB block there was several "
db file scattered read" events which means that there was multi blocks operations. In raw trace file I have found that each multi blocks operations were only 2 or 3 blocks read.

Another side effects, related to block size change are changes in SQL cost.
CBO is calculating index cost using leaf block number and clustering factor.
In case of 16 kB index tablespace, where will be less leaf blocks but clustering factor could be bigger then in 8 kB index tablespace.

Here are results from my example:

8 kB - cost 106

Leaf Blocks - 411768
Custering Factor - 86883030

16 kB

Leaf Blocks - 211117
Custering Factor - 90356662


There are all my findings. I'm waiting for any comments and remarks.


Update - 11.05.2008

With 4 kB blocks performance is even worse. I will update all the pictures soon.


Thursday, August 16, 2007

Order by - sort versus index full scan

High water mark is a real problem for some tables in database.

In my case I have a table which is a part of interface between two systems and once a week this table is filled with a lot of rows (about 1 000 000). When rows are processed, interface service remove them from the table. But there is a other service which check how many rows is in table and run two statement:

  • select count(*) from interface_table
  • select col1, col2, col3 from interface table order by col1,col2
with out the index both query use full scan table access so due to high water mark this statement takes a lot of time.

First statement is quite easy to tune is enough to add
index but second one is more complicated. One can smile here and
tell "We need to add one hint. that's all." Of course, we can if we have a access to source code.
In this case we have not.

Only way to change table access in second case is use a "order by" clause and knowledge how this clause is executing by Oracle.In Oracle database are two possibilities to execute "orderby" clause. One is use "SORT", second one is use "INDEX FULL SCAN". Oracle can use second option if table has primary key and columns in primary key are in this same order as columns in "ORDER BY" clause. Next restriction is that columns can not be varchar2.
Please find below examples of problem:

Table definition:

CREATE TABLE marcin_test
(
ROWSTAMP number(16),
INTERNAME VARCHAR2(50 BYTE),
TRANSID NUMBER(16),
TRANSORDER NUMBER(16),
ERRORCOUNT INTEGER,
ERRORFILE VARCHAR2(50 BYTE),
EMAILSENT VARCHAR2(10 BYTE)
)

begin
for i in 11..50000 loop
insert into marcin_test values (i,'aaaa',i,i,1,'a','a');
end loop;
commit;
end;
/


Testing SQL statement:

select intername, transid, transorder, errorfile
from marcin_test
order by transid, transorder;

Results:

  • no primary key – table with 50000 rows
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=523 Card=50000 Bytes=1000000)
1 0 SORT (ORDER BY) (Cost=523 Card=50000 Bytes=1000000)
2 1 TABLE ACCESS (FULL) OF 'MARCIN_TEST' (Cost=95 Card=50000 Bytes=1000000)

  • no primary key – table with 100 rows – HWM was set by 50000 rows
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=108 Card=10 Bytes=160)
1 0 SORT (ORDER BY) (Cost=108 Card=10 Bytes=160)
2 1 TABLE ACCESS (FULL) OF 'MARCIN_TEST' (Cost=95 Card=10 Bytes=160)

  • primary key – table with 50000 rows
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=523 Card=50000 Bytes=1000000)
1 0 SORT (ORDER BY) (Cost=523 Card=50000 Bytes=1000000)
2 1 TABLE ACCESS (FULL) OF 'MARCIN_TEST' (Cost=95 Card=50000 Bytes=1000000)

  • primary key – table with 100 rows – HWM was set by 50000 rows
0 SELECT STATEMENT Optimizer=ALL_ROWS (Cost=3 Card=10 Bytes=160)
1 0 TABLE ACCESS (BY INDEX ROWID) OF 'MARCIN_TEST' (Cost=3 Card=10 Bytes=160)
2 1 INDEX (FULL SCAN) OF 'T1_PK_2' (UNIQUE) (Cost=2 Card=10)

This examples shows that if we have a “ORDER BY” clause and if we can add primary key to table, we can avoid full scan on table with very high HWM and reduce response time for this
query.