Skip to Main Content

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Leonardo H.

Asked: October 28, 2006 - 6:32 pm UTC

Last updated: October 03, 2019 - 1:42 am UTC

Version: 8.1.7

Viewed 10K+ times! This question is

You Asked

Hi Tom, please explain the differences between global partitioned index and local partitioned index.

Thanks!

and Tom said...

<quote src=expert one on one Oracle>
Partitioning Indexes
Indexes, like tables, may be partitioned. There are two possible methods to partition indexes. You may either:

&#61553; Equipartition the index with the table – Also known as a local index. For every table partition, there will be an index partition that indexes just that table partition. All of the entries in a given index partition point to a single table partition and all of the rows in a single table partition are represented in a single index partition.
&#61553; Partition the index by range – Also known as a global index. Here the index is partitioned by range, and a single index partition may point to any (and all) table partitions.

In the locally partitioned index, the index entries in a given partition, point into exactly one table partition. The globally partitioned index diagram however, shows that the index entries in a global index may point into any or all of the table partitions. Also, note that the number of index partitions may in fact be different than the number of table partitions.

</quote>

Rating

  (29 ratings)

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

Comments

How about a..

A reader, October 29, 2006 - 10:26 am UTC

.. for example. Like an example where I would like to use a local index vs where I would like to do a global index.

Tom Kyte
October 29, 2006 - 12:35 pm UTC

Well, there has been a lot written about this stuff in books (all of the ones I've done for example)....


The partitioning scheme is pretty much driven by your ACCESS patterns.

Let's say you HASH PARTITION a table by EMPNO into 64 partitions.
but you frequently query "where name = :x" or "where name like :x"

Now, if you used a local index, then the "where name = :x" query would have to range scan 64 local index partitions, since the name "SCOTT" could appear in 0 to 64 of the partitions.

But, if you did a GLOBAL index on name, say range partitioning it so that A-K was in partition 1 and L-Z were in partition 2 - then you would have to range scan ONE partition only.



So, think about how the data is organized, think about how you ACCESS the data - and the design will start to fall out from there.

excelent discription on clobal and local index on the partition table

Piyush Sachan, November 14, 2006 - 6:03 am UTC

This was the execent example where and niot the global and loacl index top be used this was very much help to me as i was palnning to drop all global index and create them localy.

comparision between global and local index

Jyotiranjan Nayak, November 14, 2006 - 6:27 am UTC

hi all this was the perfect example where i could see the difference between the two. one thing more is there any specific rule where we can get to know on what columns to create global or local index

Tom Kyte
November 14, 2006 - 7:22 am UTC

the rule, the golden rule:

learn how things work, understand what they do, realize what your goal is, use the things you know how they work and understand what they do to realize that goal.

Meaning - understand them, then you will know when they apply.

compressing an index partition

martina, September 28, 2007 - 10:10 am UTC

Hi Tom,

most of our big tables are partitioned, their indices are equipartitioned. Is there a way to compress a partitioned index? from the documentation i get the impression it is not possible:

"key_compression

Specify COMPRESS to enable key compression, which eliminates repeated occurrence of key column values. Use integer to specify the prefix length (number of prefix columns to compress).

For unique indexes, the range of valid prefix length values is from 1 to the number of key columns minus 1. The default prefix length is the number of key columns minus 1.

For nonunique indexes, the range of valid prefix length values is from 1 to the number of key columns. The default prefix length is number of key columns.

Oracle Database compresses only nonpartitioned indexes that are nonunique or unique indexes of at least two columns."

but also:
"key_compression This clause is relevant for composite-partitioned indexes. Use this clause to change the compression attribute for the partition and every subpartition in that partition. Oracle Database marks each index subpartition in the partition UNUSABLE and you must then rebuild these subpartitions. Key compression must already have been specified for the table before you can specify key compression for a partition. You can specify this clause only at the partition level. You cannot change the compression attribute for an individual subpartition.

You can use this clause for noncomposite index partitions. However, it is more efficient to use the rebuild_clause for noncomposite partitions, which lets you rebuild and set the compression attribute in one step."
http://download-uk.oracle.com/docs/cd/B19306_01/server.102/b14200/statements_1008.htm

which sounds contradictory to me. anyway, i did not succeed in compressing index partitions.

would you please tell me how to, or if it's not why it is not possible?

thank you,
martina




Tom Kyte
September 28, 2007 - 6:02 pm UTC

ops$tkyte%ORA10GR2> CREATE TABLE t
ops$tkyte%ORA10GR2> (
ops$tkyte%ORA10GR2>   dt  date,
ops$tkyte%ORA10GR2>   x   int,
ops$tkyte%ORA10GR2>   y   varchar2(30)
ops$tkyte%ORA10GR2> )
ops$tkyte%ORA10GR2> PARTITION BY RANGE (dt)
ops$tkyte%ORA10GR2> (
ops$tkyte%ORA10GR2>   PARTITION part1 VALUES LESS THAN (to_date('13-mar-2003','dd-mon-yyyy')) ,
ops$tkyte%ORA10GR2>   PARTITION part2 VALUES LESS THAN (to_date('14-mar-2003','dd-mon-yyyy')) ,
ops$tkyte%ORA10GR2>   PARTITION junk VALUES LESS THAN (MAXVALUE)
ops$tkyte%ORA10GR2> )
ops$tkyte%ORA10GR2> /
ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> insert into t select to_date( '12-mar-2003', 'dd-mon-yyyy')+mod(rownum,3), mod(rownum,5), rpad('*',30,'*') from all_objects;
ops$tkyte%ORA10GR2> */
ops$tkyte%ORA10GR2> drop index t_idx;

Index dropped.

ops$tkyte%ORA10GR2> create index t_idx on t(dt,x,y) local;

Index created.

ops$tkyte%ORA10GR2> select partition_name, bytes from user_segments where segment_name = 'T_IDX';

PARTITION_NAME                      BYTES
------------------------------ ----------
PART1                             2097152
PART2                             2097152
JUNK                              2097152

ops$tkyte%ORA10GR2> drop index t_idx;

Index dropped.

ops$tkyte%ORA10GR2> create index t_idx on t(dt,x,y) compress 3 local;

Index created.

ops$tkyte%ORA10GR2> select partition_name, bytes from user_segments where segment_name = 'T_IDX';

PARTITION_NAME                      BYTES
------------------------------ ----------
PART1                              262144
PART2                              262144
JUNK                               262144



similar results in 9i - what did you try ?

One more example

lyxx, September 29, 2007 - 5:56 pm UTC


compressed partitions

martina, September 30, 2007 - 4:02 am UTC

Thank You!

I tried to compress existing index partitions:

INFO/INFO_PROD> alter index t_idx rebuild partition part1 compress;
alter index t_idx rebuild partition part1 compress
*
ERROR at line 1:
ORA-28659: COMPRESS must be specified at object level first


INFO/INFO_PROD> alter index t_idx modify default attributes compress
  2  /
alter index t_idx modify default attributes compress
                                            *
ERROR at line 1:
ORA-02243: invalid ALTER INDEX or ALTER MATERIALIZED VIEW option


INFO/INFO_PROD> alter index t_idx compress
  2  /
alter index t_idx compress
                  *
ERROR at line 1:
ORA-02243: invalid ALTER INDEX or ALTER MATERIALIZED VIEW option


then i read some documentation and got confused. what i want is to compress new index partitions and age out uncompressed ones. I'm afraid i cannot recreate all the indices. Most of our Databases are 9.2.0.7

regards, martina
Tom Kyte
October 03, 2007 - 2:11 pm UTC

well, index compression is very different from table compression.

In index compression - you need to rebuild the index to be compressed, it works by factoring out leading edge values that repeat.

I consider that a bug

martina, October 04, 2007 - 10:25 am UTC

Hi Tom,

i understand that, but i don't understand why i cannot do following:

alter index t_idx modify default attributes compress;
This would affect only new partitions and after that i should also be able to say:
alter index t_idx rebuild partition part1 compress;

Which is somehow similar to:
create index t_idx2 on t2(dt,x,y) compress local
(partition part1 nocompress
,partition part2 nocompress
,partition junk nocompress
);

there is even a bug filed: 5101794 (Documentation bug)
"The text has been modified to include this information. Development is pursuign the possibility of allowing compression to be enabled for an index that was not created with compression enabled. If this code fix is implemented, the doc will need to be changed again. "

and i cannot think of a feasible workaround.

regards, martina

Oracle Guidelines for Partitioning Indexes

Krishan Jaglan, October 06, 2010 - 11:44 am UTC

Guidelines for Partitioning Indexes

When deciding how to partition indexes on a table, consider the mix of applications that need to access the table. There is a trade-off between performance on the one hand and availability and manageability on the other. Here are some of the guidelines you should consider:

For OLTP applications:

Global indexes and local prefixed indexes provide better performance than local nonprefixed indexes because they minimize the number of index partition probes.

Local indexes support more availability when there are partition or subpartition maintenance operations on the table. Local nonprefixed indexes are very useful for historical databases.

For DSS applications, local nonprefixed indexes can improve performance because many index partitions can be scanned in parallel by range queries on the index key.

For example, a query using the predicate "acctno between 40 and 45" on the table checks of Figure 4-4 causes parallel scans of all the partitions of the nonprefixed index ix3. On the other hand, a query using the predicate deptno BETWEEN 40 AND 45 on the table deptno of Figure 4-5 cannot be parallelized because it accesses a single partition of the prefixed index ix1.

For historical tables, indexes should be local if possible. This limits the impact of regularly scheduled drop partition operations.

Unique indexes on columns other than the partitioning columns must be global because unique local nonprefixed indexes whose key does not contain the partitioning key are not supported.

http://download.oracle.com/docs/cd/B28359_01/server.111/b32024/part_avail.htm#CIHJCJJF
Tom Kyte
October 07, 2010 - 1:49 am UTC

and your point was what exactly?

Local non prefixed

A reader, March 01, 2011 - 3:06 am UTC

Does all local (prefixed or non prefixed) has one-to-one mapping with table partitions as stated in your book? In oracle doc and metalink note it states that each row of a table will have entry in each and every partition in case of non prefixed indexes.

doc reference - Partitioned Indexes: Global, Local, Prefixed and Non-Prefixed [ID 69374.1]

This is what is states :

CREATE TABLE dept
(deptno NUMBER NOT NULL,
dname VARCHAR2(10) NOT NULL,
loc VARCHAR2(14))
PARTITION BY RANGE (deptno)
(PARTITION part1 VALUES LESS THAN (30),
PARTITION part2 VALUES LESS THAN (MAXVALUE));

Then a local prefixed index would be created as follows:

CREATE INDEX deptloc1_idx ON dept(deptno) LOCAL;

though we could be much more specific about partition names and tablespaces if
we chose.

Local non-prefixed indexes will not have the table's partitioning key as their
leftmost column. For example:

CREATE INDEX deptloc2_idx ON dept(loc) LOCAL;

Each partition of a non-prefixed local index will of course potentially contain the full
range of possible key values, as shown in the diagram below:

| |
------- -------
| | | |
A.. Z.. A.. Z.. (for a VARCHAR2 column)

This may look inefficient, but remember that we can search all the index
partitions in parallel.

So what is correct?

Jugal

Tom Kyte
March 01, 2011 - 9:22 am UTC

if you have a LOCAL index - then by definition, by the very definition of what it means to be a LOCAL index - there is a one to one relationship between index partitions and table partitions. Any given index partition in a LOCAL index points to a SINGLE table partition.



I think you have misread entirely what the document states. What they are saying is that for any given LOCAL index partition - the range of values for LOC (in this case) could be the entire set of key values - anything from a location starting with A through to locations starting with Z's. They are NOT saying (not anywhere in there) that a given local index partition can point to any row in any partition of the table, they are simply saying "loc=ABC could be in partition 1, partition 2, ... partition N in the set of the local index partitions".


And why would it be grossly inefficient??????

If you query:

where deptno = ? and loc = ?

q) how many "non-prefixed" index partitions would we have to scan???
a) one


we still partition eliminate. It is true that an index on (loc,deptno) or (deptno,loc) would be "better" for that predicate (avoid going to the table to look up deptno values) - but we'd only hit one index partition. And we don't really care if the index is prefixed or not (loc,deptno) would do dandy.


The whole thing about non-prefixed indexes is a big waste of time to discuss. they are just as good as prefixed indexes. We can partition eliminate as long as you put the table partition key into the predicate (which would be a requirement for prefixed indexes as well)



So, document referenced does not contradict anything I wrote and you don't really need to lose sleep over non-prefixed indexes. As I wrote in Expert Oracle Database Architecture:

<quote>
Local Indexes


Oracle makes a distinction between the following two types of local indexes:

o Local prefixed indexes: These are indexes whereby the partition keys are on the leading edge of the index definition. For example, if a table is range partitioned on a column named LOAD_DATE, a local prefixed index on that table would have LOAD_DATE as the first column in its column list.

o Local nonprefixed indexes: These indexes do not have the partition key on the leading edge of their column list. The index may or may not contain the partition key columns.

Both types of indexes are able to take advantage of partition elimination, both can support uniqueness (as long as the non-prefixed index includes the partition key), and so on. The fact is that a query that uses a local prefixed index will always allow for index partition elimination, whereas a query that uses a local non-prefixed index might not. This is why local non-prefixed indexes are said to be “slower” by some people—they do not enforce partition elimination (but they do support it).

There is nothing inherently better about a local prefixed index as opposed to a local nonprefixed index when that index is used as the initial path to the table in a query. What I mean by that is that if the query can start with “scan an index” as the first step, there isn’t much difference between a prefixed and a nonprefixed index.
</quote>



Local non prefixed index

jugal kishore gupta, March 01, 2011 - 3:15 am UTC

Also, Oracle 11g doc give this example for local non proefixed index:

This illustrates a table called CHECKS that has been range partitioned on CHKDATE. A local non-prefixed index on ACCTNO has been created. This index contains the following values:

ACCTNO31 and ACCTNO82 in CHKDATE 1/97

ACCTNO54 and ACCTNO82 in CHKDATE 2/97

ACCTNO15 and ACCTNO35 in CHKDATE 12/97


We see here that same account82 is spanning 2 index partitions.

Please clarify as this is very confusing for us given the fact that we have complete confidence in your books where this aspect is stated deifferently - your book says that there is one-to-one correspondence between table partions and local partitions.
Tom Kyte
March 01, 2011 - 12:08 pm UTC

read above, I fail to see ANY inconsistencies here at all.

There is a 1:1 relationship between a LOCAL INDEX PARTITION and a TABLE PARTITION.

It should be obvious that if you have a table EMP (for example) partitioned by HASH on EMPNO (for example) and a LOCAL INDEX on last_name (for example) that the last_name = 'KYTE' could be in zero, one or ALL of the local index partitions.

But a given local index partition points to exactly and only ONE table partition.



I think you must be mis-interpreting something here - there are quite simply no inconsistencies that I can see between the documentation and what I've written, and what I've written and the documentation. We say the same exact thing.



Please explain how your posted example where shows that there is not a 1:1 correspondence between table partitions and local index partitions? It doesn't, it just doesn't.

Local and Global

Jugal K Gupta, March 01, 2011 - 11:28 pm UTC

Thanktyou Tom - no confusions now on local indexes thing that I posted above.
Sir, can you suggest one thing. We have a transaction table which is 21GB and growing. We want to partition the table on trasaction_date column monthly. But I have 8 indexes on this table - some are composite involving 2 to 4 columns and others are on individual columns. Our queries do not use transaction date in most of the queries. So can I assume that local indexes will not be efficient. The other option is to create global indexes but these index columns cannot be range partitioned as they are not date columns. Can you suggest should I use normal global non-partitioned index or hash partion global index. Whih one will give better performance?
1. Local index - predicates do not use table partition key.
2. Global non-partitioned index -predicates do not use table partition key.
3. Global hash partition index - predicates do not use table partition key.

Many thanks in advance

Jugal
Tom Kyte
March 02, 2011 - 7:31 am UTC

Our queries do not use transaction date
in most of the queries. So can I assume that local indexes will not be
efficient.



You can assume that from a data retrieval perspective - you will have to scan N indexes (where N = number of partitions) if you use local indexes and do not reference the partition key.

So, from a data retrieval perspective, there will be a negative impact. From a data administration perspective (truncate/drop old partitions) there would be a positive impact.

Probably, you can assume an overall "negative impact day to day", yes.


The other option is to create global indexes but these index columns
cannot be range partitioned as they are not date columns.


Range partitioning works on numbers, dates, strings.
List partitioning - ditto
Hash partitioning - the same.


Not sure what you mean there....


why do you believe you cannot range partition them again?

Index partition

Jugal K Gupta, March 02, 2011 - 10:31 pm UTC

Many Thanks sir for your lucid clarification.

1. Most of the indexes I cannot range partition because I cannot define the ranges. E.g accountid is a column requiring indexing but this is generated randomly through customer specified setup and application logic. Other columns for indexing are also similar. I can think of global indexes but only hash partitioned option will work in most of these cases. Will hash partion indexes have a positive impact on performance when the table is range partitoned on postingdate column? Because of perfromance implications, I cannot go for local indexes and I understand that global indexes will go for rebuild everytime I do some maintenance like dropping and adding partitions.

Or should I go for global non partitioned index? Will this help in improving perfromance after I have partioned the table only when compared to non-partitioned table and index setup that I currently have.

Just wanted to consider all options before deciding on usingt partioning, if at all it is beneficial from perfromance and maintenance aspect.

Many thanks for all your inputs - it has provided me with much more clarity now.

Regards
Jugal
Tom Kyte
March 03, 2011 - 7:41 am UTC

1) Will hash partion indexes
have a positive impact on performance when the table is range partitoned on
postingdate column?


Everyone repeat along with me:

it might go faster
it might go slower
it might stay the same

"it depends" - it always depends.


If (the index is large enough or contentious enough that having multiple partitions is a good idea) AND (you only use equals on the index key so we can have partition elimination)
then
hash partitioning the index might make sense
else
it probably doesn't
end if



If you use "accountid > ?", then we'd have to probe every single hash partition, that would not be good.

if you use "accountid = ?", then we'd be able to partition prune and that would be good.

If the index isn't huge in the first place - you gain nothing administratively from partitioning.

If the index isn't a source of contention (buffer busy waits on a hot right hand side index like you might see in a table that is heavily inserted into and uses a sequence or date/timestamp to populate the values), then you gain nothing there either.

So, it depends.


Will this help in improving
perfromance after I have partioned the table only when compared to
non-partitioned table and index setup that I currently have.


same as above, it depends. It probably would have a marginal negative impact as you have added an extra step to the lookup by partitioning the table. When you access the rows via a global index, you are using a global rowid which is four parts instead of just three like a normal rowid is. It takes a little (very little) more cpu to process it.


Will this help in improving
perfromance after I have partioned the table only when compared to
non-partitioned table and index setup that I currently have.


One word for you:

benchmark


benchmark the implementations you are considering. Make sure to do it with real data volumes and real simulated user loads.

Default Tablespace for Local Index

Rajeshwaran, Jeyabal, May 20, 2011 - 8:07 pm UTC

scott@ORA10GR2> create table t(
  2     x number,
  3     y number,
  4     z date)
  5  partition  by list(x)
  6  (
  7         partition p1 values (1) ,
  8         partition p2 values (2) ,
  9         partition p3 values (3) ,
 10         partition p4 values (4) ,
 11         partition p_max values(default)
 12  );

Table created.

Elapsed: 00:00:00.21
scott@ORA10GR2>
scott@ORA10GR2> create index t_ind on t(x) local tablespace TS_xxx_02_D;

Index created.

Elapsed: 00:00:00.17
scott@ORA10GR2>
scott@ORA10GR2> select partition_name,tablespace_name
  2  from user_ind_partitions
  3  where index_name ='T_IND'
  4  /

PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
P1                             TS_xxx_02_D
P2                             TS_xxx_02_D
P3                             TS_xxx_02_D
P4                             TS_xxx_02_D
P_MAX                          TS_xxx_02_D

Elapsed: 00:00:00.23
scott@ORA10GR2>
scott@ORA10GR2> begin
  2    for x in (select * from user_ind_partitions where  index_name ='T_IND')
  3    loop
  4      execute immediate ' alter index t_ind rebuild partition '||x.partition_name||' tablespace ts_xxx_audit nologging ';
  5    end loop;
  6  end;
  7  /

PL/SQL procedure successfully completed.

Elapsed: 00:00:00.60
scott@ORA10GR2>
scott@ORA10GR2> select partition_name,tablespace_name
  2  from user_ind_partitions
  3  where index_name ='T_IND'
  4  /

PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
P1                             ts_xxx_audit
P2                             ts_xxx_audit
P3                             ts_xxx_audit
P4                             ts_xxx_audit
P_MAX                          ts_xxx_audit

Elapsed: 00:00:00.15
scott@ORA10GR2>
scott@ORA10GR2> select dbms_metadata.get_ddl('INDEX','T_IND') from dual;

DBMS_METADATA.GET_DDL('INDEX','T_IND')
--------------------------------------------------------------------------------

  CREATE INDEX "IRADS"."T_IND" ON "IRADS"."T" ("X")
  PCTFREE 10 INITRANS 2 MAXTRANS 255
  STORAGE(
  BUFFER_POOL DEFAULT)
  TABLESPACE "TS_xxx_02_D"  LOCAL
 (PARTITION "P1"
  PCTFREE 10 INITRANS 2 MAXTRANS 255  NOLOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "ts_xxx_audit" ,
 PARTITION "P2"
  PCTFREE 10 INITRANS 2 MAXTRANS 255  NOLOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "ts_xxx_audit" ,
 PARTITION "P3"
  PCTFREE 10 INITRANS 2 MAXTRANS 255  NOLOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "ts_xxx_audit" ,
 PARTITION "P4"
  PCTFREE 10 INITRANS 2 MAXTRANS 255  NOLOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "ts_xxx_audit" ,
 PARTITION "P_MAX"
  PCTFREE 10 INITRANS 2 MAXTRANS 255  NOLOGGING
  STORAGE(INITIAL 65536 NEXT 1048576 MINEXTENTS 1 MAXEXTENTS 2147483645
  PCTINCREASE 0 FREELISTS 1 FREELIST GROUPS 1 BUFFER_POOL DEFAULT)
  TABLESPACE "ts_xxx_audit" )



Elapsed: 00:00:01.42
scott@ORA10GR2>
scott@ORA10GR2> alter table t split partition p_max values(5) into (partition p_5,partition p_max);

Table altered.

Elapsed: 00:00:00.42
scott@ORA10GR2>
scott@ORA10GR2> select partition_name,tablespace_name
  2  from user_ind_partitions
  3  where index_name ='T_IND'
  4  /

PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
P1                             ts_xxx_audit
P2                             ts_xxx_audit
P3                             ts_xxx_audit
P4                             ts_xxx_audit
P_5                            TS_xxx_02_D
P_MAX                          TS_xxx_02_D

6 rows selected.

Elapsed: 00:00:00.23
scott@ORA10GR2>
scott@ORA10GR2>


Tom:

Is it possible to change the default tablespace (TS_xxx_02_D) for Local index? If possible Can you please show me how?

Default Tablespace for Local Index

Rajeshwaran, Jeyabal, May 24, 2011 - 10:26 am UTC


Tom:

Sorry for the lenghty script above. Is that possible to change the default tablespace Tom, other than dropping and recreating the index?
Tom Kyte
May 24, 2011 - 12:55 pm UTC

http://docs.oracle.com/docs/cd/E11882_01/server.112/e17118/statements_1010.htm#i2129868

ops$tkyte%ORA11GR2> CREATE TABLE t
  2  (
  3    dt  date,
  4    x   int,
  5    y   varchar2(30)
  6  )
  7  PARTITION BY RANGE (x)
  8  (
  9    PARTITION part1 VALUES LESS THAN (1),
 10    PARTITION part2 VALUES LESS THAN (2)
 11  )
 12  /

Table created.

ops$tkyte%ORA11GR2> 
ops$tkyte%ORA11GR2> create index t_idx on t(y) local tablespace users;

Index created.

ops$tkyte%ORA11GR2> select partition_name, tablespace_name from user_ind_partitions where index_name = 'T_IDX';

PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
PART1                          USERS
PART2                          USERS

ops$tkyte%ORA11GR2> alter index t_idx modify default attributes tablespace p1;

Index altered.

ops$tkyte%ORA11GR2> alter table t add partition part3 values less than (3);

Table altered.

ops$tkyte%ORA11GR2> select partition_name, tablespace_name from user_ind_partitions where index_name = 'T_IDX';

PARTITION_NAME                 TABLESPACE_NAME
------------------------------ ------------------------------
PART1                          USERS
PART2                          USERS
PART3                          P1

sonu, June 20, 2011 - 2:24 pm UTC

is there any way to find out table has local or global index by using data dictionary views? i do not find anything in dba_index which tells that this table has global or local or global partition index.
Tom Kyte
June 20, 2011 - 3:15 pm UTC

ops$tkyte%ORA10GR2> CREATE TABLE t
  2  (
  3    dt  date,
  4    x   int,
  5    y   varchar2(30)
  6  )
  7  PARTITION BY RANGE (dt)
  8  (
  9    PARTITION part1 VALUES LESS THAN (to_date('13-mar-2003','dd-mon-yyyy')) ,
 10    PARTITION part2 VALUES LESS THAN (to_date('14-mar-2003','dd-mon-yyyy')) ,
 11    PARTITION junk VALUES LESS THAN (MAXVALUE)
 12  )
 13  /

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> create index t_idx1 on t(x) local;

Index created.

ops$tkyte%ORA10GR2> create index t_idx2 on t(y) global partition by range(y)
  2  ( partition x values less than ('x'),
  3    partition y values less than ( maxvalue )
  4  );

Index created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select index_name, locality from user_part_indexes where index_name like 'T_IDX_';

INDEX_NAME                     LOCALI
------------------------------ ------
T_IDX1                         LOCAL
T_IDX2                         GLOBAL


sonu, June 20, 2011 - 7:55 pm UTC

Thanks Tom, 

I created one index and specify GLOBAL in the syntax what kind of this index? by specifying GLOBAL that doesn't mean index is global partition index. correct?


  1  CREATE TABLE t
  2      (
  3        dt  date,
  4        x   int,
  5        y   varchar2(30)
  6      )
  7      PARTITION BY RANGE (dt)
  8      (
  9        PARTITION part1 VALUES LESS THAN (to_date('13-mar-2003','dd-mon-yyyy')) ,
 10       PARTITION part2 VALUES LESS THAN (to_date('14-mar-2003','dd-mon-yyyy')) ,
 11       PARTITION junk VALUES LESS THAN (MAXVALUE)
 12*    )
17:51:31  13  /

Table created.

Elapsed: 00:00:00.00
17:51:35 SQL> create index t_idx_dt on t(dt) global;

Index created.

Elapsed: 00:00:00.00
17:52:48 SQL> select index_name, locality from user_part_indexes where index_name like 'T_IDX%';

no rows selected



Tom Kyte
June 21, 2011 - 7:39 am UTC

A non-partitioned index on a partitioned table is technically always a global index (by definition, since it is NOT local). But since there is no partitioning associated with the index - it is not a partitioned index.

Due to the lack of a partitioning scheme, it is not a partitioned index - but it is technically "global" since it'll have global rowids in it that can point to any of the table partitions.

sonu, June 21, 2011 - 5:07 pm UTC

Thanks Tom!
I was looking such kind of clerfication from doc or some book. but i think you are more than oracle documentaion and books. thanks again

local vs global

Reader, July 19, 2011 - 4:13 pm UTC

local_index on (<partition_key_colum>,column2)
or
global_nonpartitioned_index on (<partition_key_colum>,column2)

In general, which one is recomended for better performance when created on a range partitioned table?
Thanks
Tom Kyte
July 19, 2011 - 4:16 pm UTC

neither, both, local, global

"it depends", I can come up with examples for each case if I wanted.

We'd need more data here - use case.


In general, you many times do not need the partition key column in the local index - sometimes you want it, but not always.


You'd need to give us a much more specific use case - with details about the range of values in the partition_key_column (many times - it is constant in a partition! that affects the answer) and the query in general.



But - "in general", if the partition key is on the leading edge of the index and needs to be there - then a local index makes the most sense.

local vs global

Reader, July 20, 2011 - 11:54 am UTC

Regarding "In general, you many times do not need the partition key column in the local index - sometimes you want it, but not always." -- can you please clarify?

if partition key column is not in the local index, oracle may not be able to do partition pruning? is this a fair statement? If so, where such index would be prefered? Data warehouse environment?
Thanks
Tom Kyte
July 22, 2011 - 1:02 pm UTC

If the partition key is referenced in the where clause in a manner that allows partition elimination - oracle will ALWAYS be able to do partition pruning - regardless of whether the partition key exists in the local index or not.

suppose you have a table T with columns a,b,c

The table is partitioned by A.

You have a local index on (b)


if you query

select * from t where a = ? and b between ? and ?

then the database will absolutely be able to partition eliminate to one single local index partition AND THEN range scan on B.


Oracle will be able to partition eliminate in ALL OF THE SAME cases for a locally partitioned index regardless of whether it is locally prefixed or not.


Here is an excerpt from one of my books on this topic that explains it in some detail:


<quote src=Effective Oracle by Design>
Local Indexes
Oracle makes a distinction between the following two types of local indexes:
 * Local prefixed indexes: These are indexes whereby the partition keys are on the leading edge of the index definition. For example, if a table is range partitioned on a column named LOAD_DATE, a local prefixed index on that table would have LOAD_DATE as the first column in its column list.
 * Local nonprefixed indexes: These indexes do not have the partition key on the leading edge of their column list. The index may or may not contain the partition key columns.
Both types of indexes are able to take advantage of partition elimination, both can support uniqueness (as long as the non-prefixed index includes the partition key), and so on. The fact is that a query that uses a local prefixed index will always allow for index partition elimination, whereas a query that uses a local non-prefixed index might not. This is why local non-prefixed indexes are said to be “slower” by some people—they do not enforce partition elimination (but they do support it). 
There is nothing inherently better about a local prefixed index as opposed to a local nonprefixed index when that index is used as the initial path to the table in a query. What I mean by that is that if the query can start with “scan an index” as the first step, there isn’t much difference between a prefixed and a nonprefixed index. 
Partition Elimination Behavior
For the query that starts with an index access, whether or not it can eliminate partitions from consideration all really depends on the predicate in your query. A small example will help demonstrate this. The following code creates a table, PARTITIONED_TABLE, that is range partitioned on a numeric column A such that values less than two will be in partition PART_1 and values less than three will be in partition PART_2:
ops$tkyte@ORA11GR2> CREATE TABLE partitioned_table
  2  ( a int,
  3    b int,
  4    data char(20)
  5  )
  6  PARTITION BY RANGE (a)
  7  (
  8  PARTITION part_1 VALUES LESS THAN(2) tablespace p1,
  9  PARTITION part_2 VALUES LESS THAN(3) tablespace p2
 10  )
 11  /
Table created.
We then create both a local prefixed index, LOCAL_PREFIXED, and a local nonprefixed index, LOCAL_NONPREFIXED. Note that the nonprefixed index does not have A on the leading edge of its definition, which is what makes it a nonprefixed index:
ops$tkyte@ORA11GR2> create index local_prefixed on partitioned_table (a,b) local;
Index created.

ops$tkyte@ORA11GR2> create index local_nonprefixed on partitioned_table (b) local;
Index created.
Next, we’ll insert some data into one partition and gather statistics:
ops$tkyte@ORA11GR2> insert into partitioned_table
  2  select mod(rownum-1,2)+1, rownum, 'x'
  3    from all_objects;
72771 rows created.

ops$tkyte@ORA11GR2> begin
  2     dbms_stats.gather_table_stats
  3     ( user,
  4      'PARTITIONED_TABLE',
  5       cascade=>TRUE );
  6  end;
  7  /
PL/SQL procedure successfully completed.
We take offline tablespace P2, which contains the PART_2 partition for both the tables and indexes:
ops$tkyte@ORA11GR2> alter tablespace p2 offline;
Tablespace altered.
Taking tablespace P2 offline will prevent Oracle from accessing those specific index partitions. It will be as if we had suffered “media failure,” causing them to become unavailable. Now we’ll query the table to see what index partitions are needed by different queries. This first query is written to permit the use of the local prefixed index:
ops$tkyte@ORA11GR2> select * from partitioned_table where a = 1 and b = 1;
         A          B DATA
---------- ---------- --------------------
         1          1 x
That query succeeded, and we can see why by reviewing the explain plan. We’ll use the built-in package DBMS_XPLAN to see what partitions this query accesses. The PSTART (partition start) and PSTOP (partition stop) columns in the output show us exactly what partitions this query needs to have online and available in order to succeed:
ops$tkyte@ORA11GR2> delete from plan_table;
4 rows deleted.

ops$tkyte@ORA11GR2> explain plan for
  2  select * from partitioned_table where a = 1 and b = 1;
Explained.

ops$tkyte@ORA11GR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------
| Operation                          | Name              | Rows  | Pstart| Pstop |
----------------------------------------------------------------------------------
| SELECT STATEMENT                   |                   |     1 |       |       |
|  PARTITION RANGE SINGLE            |                   |     1 |     1 |     1 |
|   TABLE ACCESS BY LOCAL INDEX ROWID| PARTITIONED_TABLE |     1 |     1 |     1 |
|    INDEX RANGE SCAN                | LOCAL_PREFIXED    |     1 |     1 |     1 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("A"=1 AND "B"=1)
Note The DBMS_XPLAN output has been edited to remove information that was not relevant, in order to permit the examples to fit on the printed page.
So, the query that uses LOCAL_PREFIXED succeeds. The optimizer was able to exclude PART_2 of LOCAL_PREFIXED from consideration because we specified A=1 in the query, and we can see that clearly in the plan PSTART and PSTOP are both equal to 1. Partition elimination kicked in for us. The second query fails, however:
ops$tkyte@ORA11GR2> select * from partitioned_table where b = 1;
ERROR:
ORA-00376: file 13 cannot be read at this time
ORA-01110: data file 13: '/home/ORA11GR2/.../o1_mf_p2_1dzn8jwp_.dbf'
no rows selected
And using the same technique, we can see why:
ops$tkyte@ORA11GR2> delete from plan_table;
4 rows deleted.

ops$tkyte@ORA11GR2> explain plan for
  2  select * from partitioned_table where b = 1;
Explained.

ops$tkyte@ORA11GR2> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------
| Operation                          | Name              | Rows  | Pstart| Pstop |
----------------------------------------------------------------------------------
| SELECT STATEMENT                   |                   |     1 |       |       |
|  PARTITION RANGE ALL               |                   |     1 |     1 |     2 |
|   TABLE ACCESS BY LOCAL INDEX ROWID| PARTITIONED_TABLE |     1 |     1 |     2 |
|    INDEX RANGE SCAN                | LOCAL_NONPREFIXED |     1 |     1 |     2 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   3 - access("B"=1)
Here the optimizer was not able to remove PART_2 of LOCAL_NONPREFIXED from consideration—it needed to look in both the PART_1 and PART_2 partitions of the index to see if B=1 was in there. Herein lies a performance issue with local nonprefixed indexes: they do not make you use the partition key in the predicate as a prefixed index does. It is not that prefixed indexes are better; it’s just that in order to use them, you must use a query that allows for partition elimination.
If we drop the LOCAL_PREFIXED index and rerun the original successful query as follows:
ops$tkyte@ORA11GR2> drop index local_prefixed;
Index dropped.

ops$tkyte@ORA11GR2> select * from partitioned_table where a = 1 and b = 1;
         A          B DATA
---------- ---------- --------------------
         1          1 x
it succeeds, but as we’ll see, it used the same index that just a moment ago failed us. The plan shows that Oracle was able to employ partition elimination here—the predicate A=1 was enough information for the database to eliminate index partition PART_2 from consideration:
ops$tkyte@ORA11GR2> delete from plan_table;
4 rows deleted.

ops$tkyte@ORA11GR2> explain plan for
  2  select * from partitioned_table where a = 1 and b = 1;
Explained.

ops$tkyte@ORA11GR2> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------
| Operation                          | Name              | Rows  | Pstart| Pstop |
----------------------------------------------------------------------------------
| SELECT STATEMENT                   |                   |     1 |       |       |
|  PARTITION RANGE SINGLE            |                   |     1 |     1 |     1 |
|   TABLE ACCESS BY LOCAL INDEX ROWID| PARTITIONED_TABLE |     1 |     1 |     1 |
|    INDEX RANGE SCAN                | LOCAL_NONPREFIXED |     1 |     1 |     1 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter("A"=1)
   3 - access("B"=1)
Note the PSTART and PSTOP column values of 1 and 1.This proves that the optimizer is able to perform partition elimination even for nonprefixed local indexes.
If you frequently query the preceding table with the following queries:
select ... from partitioned_table where a = :a and b = :b;
select ... from partitioned_table where b = :b;
then you might consider using a local nonprefixed index on (b,a). That index would be useful for both of the preceding queries. The local prefixed index on (a,b) would be useful only for the first query.
The bottom line here is that you should not be afraid of nonprefixed indexes or consider them as major performance inhibitors. If you have many queries that could benefit from a nonprefixed index as outlined previously, then you should consider using one. The main concern is to ensure that your queries contain predicates that allow for index partition elimination whenever possible. The use of prefixed local indexes enforces that consideration. The use of nonprefixed indexes does not. Consider also how the index will be used. If it will be used as the first step in a query plan, there are not many differences between the two types of indexes. 


</quote>

Jess, January 18, 2012 - 6:51 am UTC

Hi Tom,

As you've mentioned multiple times above, "If the partition key is referenced in the where clause in a manner that allows partition elimination - oracle will ALWAYS be able to do partition pruning - regardless of whether the partition key exists in the local index or not."

In my case, I have table partitioned on column A to support the bulk of the queries. But now a large number of queries are coming in searching on column B (so column A isn't part of the query, causing full table scans).

The proposed solution was to create a new [sister] table that's partitioned on column B, so that relevant queries can go against that table. (This is all in the warehouse, so it'd just be another ETL added to the existing batch).

Before we make those kinds of changes, however, the DBAs suggested that we add a local index on column B to the original table to see if that helps, as it could scan multiple partitions in parallel.

We're still trying to get this implemented into the test environment to see if there is any gain, but I fail to understand why there would be.... Even though it's a local index, the data can still be in any/all partitions. Since the query excludes column A, why would there be any benefit to indexing B??

Thank you as always....


Tom Kyte
January 18, 2012 - 7:41 am UTC

how many rows in the table?

how many partitions?

is the data uniformly distributed over these partitions in general?

how many rows would be retrieved via this index in general?

why not a global index on B and the columns the query against B query? (a covering index).



could there be an advantage to indexing b - sure there could be, but we have no information about anything here (other than the table exists, has two columns A and B and is partitioned on A - which isn't a lot of information)

Jess, January 18, 2012 - 8:16 am UTC

Hi Tom,

Didn't think some of those things would make a difference, as most DW things are usually large. But you're right.

The fact table has about 5 billion rows at the moment. It is partitioned daily on a date column (A). Every week you add anywhere between 50 and 75 million records added. The lifetime of this table is at least 3 years forward.

It has about 900 partitions at the moment. The data is 'evenly' distributed (that is, there are somewhere between 10 and 15 mil records a day) across week days (weekend partitions are mostly empty). It also exists in these volumes in the last 250 partitions or so. Everything earlier than that has less data.

At the moment, users pull the data by querying on a date range (column A) and filter by type. The filter usually eliminates about 60% of data. The date ranges, however, go from 1 day to 1 month (so from 4M to 80M), and we have to accommodate all. That said, 1 day to 1 week range are most common.

The users want to query on a different date, but in the same way (so between date B and B1, filtered by type).

Why not a global index? Presumably because they're a bit harder to maintain on tables this size? For a covering index, don't all the columns need to be part of it? That would be a lot of columns, as they're pulling almost everything from the fact (20-odd columns).

Thanks again...

Tom Kyte
January 18, 2012 - 9:50 am UTC

Why not a global index? Presumably because they're a bit harder to maintain on
tables this size?


how so.... you haven't said anything so far that would make them 'horrible' - I think overall they'd be a lot easier to manage than another entire set of tables that would have to be deleted from in order to drop an old set of data (since they are not going to be partitioned by A and you use that scheme I presume to purge old data)

For a covering index, don't all the columns need to be part
of it? That would be a lot of columns, as they're pulling almost everything
from the fact (20-odd columns).


Yes, but you were talking about putting them into another table anyway weren't you?



You didn't answer:

how many rows would be retrieved via this index in general?



additionally:

how big in size is this table (gb's)

does compression play into it (if not, why not, why not take the old partitions and alter table t move partition p after setting compress on the table?) - could that help significantly (all queries - not just the b query)

is there any implicit relation between A and B - for example, if A was a begin_date and B were an end_date - we know that A < B. If you said "where B between :x and :y" - we know that A < :y and you could add that to the query.

Jess, January 18, 2012 - 11:44 am UTC

Hi Tom,

Thanks much for the feedback and ideas.

You're right, I haven't said why they would be 'horrible', mostly because I don't think having one would be downright awful. Current steer from the DBAs is not wanting to maintaining global indexes, so to be avoided at all cost. While I'd be inclined to get into the performance environment and test either way, I am not sure I can win this. All I can do is voice an opinion. So yes, the key reason is lack of desire to maintain, whereas tables would maintain themselves. There are no provisions to drop any of the old partitions/data. These facts will have all the data for years to come (for audit purposes), so no purging in sight.

Re: covering index, I guess that's one way of looking at it. But I would've thought it would affect DML against the table (whereas a second table would be independed) plus compression (below)...

Thought row retrieval info was there. You get between 10-15 mil rows of audit information a day, 5 days a week. The two dates in question are action_date and audit_date. All actions will be audited within 3 days and sent through. This means that whether you're querying by action_date or by audit_date, on average, you'll get the same amount of rows coming back. Roughly speaking, of course. So if you want to see all actions for last Friday, you're pulling ~10M rows of assorted audit dates between then and now (1 action_date partition). If you want to see all clients that had their actions audited last Friday, you're pulling ~10M of *different* rows dating somewhere between last Friday and last Wednesday (multiple action_date partitions), give or take, as they do get submitted late sometimes (which is why we can't impose a limit on how far back to look in the query).

So if you don't limit on type of client or action, your index is bringing back 10M rows if you specified 1 day. Queries range from 1 day to 1 week mostly, so could be up to 50M rows. If limiting on type, you're only bringing back 30-40% of that.

The table size is 415g of row space at present (can't access _segments views in production to get file space). The table and partitions *are* defined with compression, but haven't compressed anything yet. Because the existing table is partitioned on action date, it is possible to receive audits for dates way in the past. At my last company, we made tablespaces read-only and had ETLs put stale records into error tables. Then we'd unlock tablespaces, process these records, and make them read-only again. The preferred option here is to leave everything that's partitioned on action date as is. Not my call. (This is partly why creating a new table for audit date looked reasonable to me. Because audit date is current, we *could* do something with old partitions).

There *is* a relationship between the two dates (audit >= action). I did try adding action <= audit to the query, and it does eliminate as you said. Can't believe I didn't think of that! It's not of massive help for recent audit, but can knock out half the table for older ones. This is a great tip (regardless of how this particular index thing turns out) that I am sure will serve me well, so thank you again.

Lastly, I just tried creating a global index (local first, then global) in a smaller environment (large one not available) so with a smaller dataset. The plans came out very similar (and oddly with a slight difference in the number of rows, though stats have been collected inbetween).

Are similar plans a side effect of a much smaller environment? If a global index is the way to go, does it make sense to have a partitioned or a non-partitioned one?

The plan for partitioned (in small env):

----------------------------------------------------------------------------------
Id|Operation |Name |Rows |Bytes|Cost(%C)|Time |Pstrt|Pstop
----------------------------------------------------------------------------------
|0|SELECT STATEMENT | |14762|2465K| 938 (1)|00:00:12| |
|1| PARTITION RANGE SINGLE | |14762|2465K| 938 (1)|00:00:12| 882| 882
|2| TBL ACC BY GLOBAL IND ROWID|AUD_TBL|14762|2465K| 938 (1)|00:00:12|ROWID|ROWID
|3| INDEX RANGE SCAN |GP_IDX |14762| | 53 (2)|00:00:01| 882| 882
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
3 - access
("AUDIT_DATE">=TO_DATE('2011-11-28 00:00:00','syyyy-mm-dd hh24:mi:ss') AND
"AUDIT_DATE"<TO_DATE('2011-11-29 00:00:00','syyyy-mm-dd hh24:mi:ss'))

The plan for non-partitioned (in small env):
---------------------------------------------------------------------------------
Id|Operation |Name |Rows |Bytes|Cost(%C)|Time |Pstrt|Pstop
---------------------------------------------------------------------------------
|0|SELECT STATEMENT | |12452|2079K| 788 (1)|00:00:10| |
|1| TBL ACC BY GLOBAL IND ROWID|AUD_TBL|12452|2079K| 788 (1)|00:00:10|ROWID|ROWID
|2| INDEX RANGE SCAN |GNP_IDX|12452| | 43 (3)|00:00:01| |
---------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
2 - access
("AUDIT_DATE">=TO_DATE('2011-11-28 00:00:00','syyyy-mm-dd hh24:mi:ss') AND
"AUDIT_DATE"<TO_DATE('2011-11-29 00:00:00','syyyy-mm-dd hh24:mi:ss'))


You really have been a life-saver on this one (and taught me something cool while at it)!


Tom Kyte
January 18, 2012 - 1:05 pm UTC

Current steer from the DBAs
is not wanting to maintaining global indexes, so to be avoided at all cost.


ask them about maintaining an entire copy of the table! And the purging they would have to do - manually. At least with global indexes, it would just be either

a) rebuild them after the purge
b) maintain them transparently during the purge

it would be a single command - drop the partition in the existing table, and then either rebuild the global index or use the update indexes clause in the drop.


whereas tables would maintain
themselves.


really, i see two tables as more to maintain myself ;)


you're pulling ~10M
rows of assorted audit dates between then and now (1 action_date partition).


that means using an index to access a table is out of the question. So the plans you got are non-starters. You'd want a covering index so as to not have to access the table *at all*.


Say you want to get 1,000,000 rows via an index. And those rows are scattered all over the place. You might have to do as many as 1,000,000 single block reads on that table. At 5ms (pretty good retrieval rate for such an IO) that would be almost 1.5 hours. Times that by ten. It won't work. We need a full scan - of an index, of a table.



You can replicate this data into another table - but I see challenges in maintain the data - both in currency (you'll have two different versions of the truth at various points in time) as well as purging issues for this second table....

Jess, January 19, 2012 - 4:14 am UTC

Hi Tom,

Thank you again for all the suggestions and explanations.

Btw, there was a typo in my post above: "I just tried creating a global index (local first, then global)" was meant to be "global index (partitioned first, then non-partitioned)". Sorry about the confusion.

Re: DBAs, maintenance is a sticky point. Will try again today... They are happy to chat, but always have the final say in what goes live into the database.

>> ~10M rows of assorted audit dates between then and now (1 action_date partition).
> that means using an index to access a table is out of the question.
> So the plans you got are non-starters. You'd want a covering index <math follows>

Sorry, just to confirm I understood... This is what's happening right now with the current partitions. You ask for X action dates, you get X partitions scanned with 4M to 10M rows returned (depending on additional filters). So to go by audit date now, asking for X audit dates will scan the entire table. While covering index would save having to read the data, wouldn't it have negative impact on the inserts to the table?

Also, how does deciding whether to use a covering index or a global index change with changes in data retrieval? Is it purely math (as you specified above--if it adds up to an acceptable number, go one way, if not, go the other way)? Or is there more to it than that? What if we were filtering out a lot more data and had the query bringing back only about 10,000 rows for a day, 50,000 rows for a week? What if it's 1,000 to 5,000?

Then to go by audit date across partitions, would you still be recommending a covering index or a global index? If the latter, what drives the decision between having a partitioned vs non-partitioned global index?

Thank you again, this has been wonderfully helpful and educational.

Tom Kyte
January 19, 2012 - 5:35 am UTC

... You ask for X action dates, you get X partitions
scanned with 4M to 10M rows returned (depending on additional filters). ...

right, but that is not using an index is it?


... wouldn't it have
negative impact on the inserts to the table? ...

of course, but won't having a second table to constantly, in real time, replicate to do the same???

... Also, how does deciding whether to use a covering index or a global index
change with changes in data retrieval ...

what is the difference? a covering index is an index that has the necessary columns so the table doesn't have to be accessed. a covering index could be

a) not partitioned
b) locally partitioned
c) globally partitioned

they are orthogonal concepts, one does not preclude the other.


... would you still be recommending a
covering index ...

I'd be recommending a prototype and a benchmark to evaluate the various approaches in order to be able to numerically (rather than "I have a hunch") recommend the 'best way'.





Jess, January 19, 2012 - 5:52 am UTC

True that. I will post some results in the next few weeks from the performance environment once we get a chance to run through the assorted indexes there. Thank you again for the help.

Partitioning audit table.

praveen, September 10, 2012 - 1:34 pm UTC

Tom,
I understand how local indexes makes a management easier. However, I have partitioned table with a global indexes (Index is not partitioned). I want to convert it to local indexed partitioned table to make the management easier as this holds all the audit data of a perticular table.
Since it has multiple indexes, how can I make this table as local partitioned table?

More explanatory...
my table is partitioned by moddt and primary key composite key on columns key1, key2,key3 + there are other indexes. This primary index (and other indexes) is a global index ofcourse. If I would like to convert to local partitioned index table, what happens other indexes? Do I need to consider dropping other indexes?
Tom Kyte
September 14, 2012 - 2:54 pm UTC

I want to
convert it to local indexed partitioned table to make the management easier as
this holds all the audit data of a perticular table.


stop, think, someone did this on purpose - they made them global for the queries. If you make them local - you'll find your query performance to be dramatically worse (highly likely).

are you so sure.


and if you are - you drop and recreate them. they cannot be converted - every single byte of data has to be moved.


dvega, December 12, 2017 - 1:16 pm UTC

"Since global indexes may be partitioned by range only, you must use local indexes if you wish to have a hash or composite partitioned index. "

Well, since 10g release 1 global partition indexes can be also partitioned by hash.
Connor McDonald
December 13, 2017 - 1:52 am UTC

Thanks, we've updated the content.

Shirt question about partitioned index

A reader, October 01, 2019 - 1:22 pm UTC

Say my table T is partitioned by number C1

My where clause include always the partition key.


If my where clause include C1 C5 and C6 ,
What diff btw.

Create index on cols C1, C5, C6
Or simply on C5, C6 suffice?



Connor McDonald
October 02, 2019 - 3:19 am UTC

Depends a lot on the scenarios. For example

SQL> create table t (c1 int, c5 int, c6 int)
  2  partition by list (c1 )
  3  (
  4   partition p1 values(1),
  5   partition p2 values(2),
  6   partition p3 values(3)
  7  );

Table created.

SQL>
SQL> insert /*+ APPENND */ into t
  2  select c1,x,x
  3  from
  4  ( select rownum c1 from dual connect by level <= 3 ),
  5  ( select  rownum x from dual connect by level <= 1000000 );

3000000 rows created.

SQL>
SQL> commit;

Commit complete.

SQL> exec dbms_stats.gather_table_stats('','T')

PL/SQL procedure successfully completed.

SQL>
SQL> create index ix1 on t ( c5,c6);

Index created.

SQL>
SQL> set autotrace traceonly explain
SQL> select * from t
  2  where c1 = 2
  3  and   c5 = 12345
  4  and   c6 = 12345;

Execution Plan
----------------------------------------------------------
Plan hash value: 1145707456

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                           |      |     1 |    13 |     6   (0)| 00:00:01 |       |       |
|*  1 |  TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| T    |     1 |    13 |     6   (0)| 00:00:01 |     2 |     2 |
|*  2 |   INDEX RANGE SCAN                         | IX1  |     3 |       |     3   (0)| 00:00:01 |       |       |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("C1"=2)
   2 - access("C5"=12345 AND "C6"=12345)

SQL>
SQL> drop index ix1;

Index dropped.

SQL> create index ix1 on t ( c5,c6) local;

Index created.

SQL>
SQL> set autotrace traceonly explain
SQL> select * from t
  2  where c1 = 2
  3  and   c5 = 12345
  4  and   c6 = 12345;

Execution Plan
----------------------------------------------------------
Plan hash value: 3177884563

-------------------------------------------------------------------------------------------------------------------
| Id  | Operation                                  | Name | Rows  | Bytes | Cost (%CPU)| Time     | Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                           |      |     1 |    13 |     4   (0)| 00:00:01 |       |       |
|   1 |  PARTITION LIST SINGLE                     |      |     1 |    13 |     4   (0)| 00:00:01 |     2 |     2 |
|   2 |   TABLE ACCESS BY LOCAL INDEX ROWID BATCHED| T    |     1 |    13 |     4   (0)| 00:00:01 |     2 |     2 |
|*  3 |    INDEX RANGE SCAN                        | IX1  |     1 |       |     3   (0)| 00:00:01 |     2 |     2 |
-------------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   3 - access("C5"=12345 AND "C6"=12345)

SQL>
SQL>
SQL>


In the above case, if you use a *global* index, then any benefit of the C1 predicate is lost. If you change to a local index, then we did get the benefit by hitting only a single partition.

But...this was a LIST partition, so there was only 1 value for C1 in each partition. If there was lots of values, then indexing on c5,c6 may not be as good as a c1,c5,c6 index.

SQL> create table t (c1 int, c5 int, c6 int, z int)
  2  partition by range (c1 )
  3  (
  4   partition p1 values less than (1000000),
  5   partition p2 values less than (2000000),
  6   partition p3 values less than (3000000)
  7  );

Table created.

SQL>
SQL> insert /*+ APPENND */ into t
  2  select c1*800000+mod(rownum,50),mod(x,5000),mod(x,5000),0
  3  from
  4  ( select rownum c1 from dual connect by level <= 3 ),
  5  ( select  rownum x from dual connect by level <= 1000000 );

3000000 rows created.

SQL>
SQL> commit;

Commit complete.

SQL> exec dbms_stats.gather_table_stats('','T')

PL/SQL procedure successfully completed.

SQL>
SQL> create index ix1 on t ( c5,c6) local;

Index created.

SQL>
SQL> set autotrace traceonly stat
SQL> select count(z) from t
  2  where c1 = 800012
  3  and   c5 = 2345
  4  and   c6 = 2345;

1 row selected.


Statistics
----------------------------------------------------------
          5  recursive calls
         10  db block gets
        208  consistent gets
         10  physical reads
       2076  redo size
        549  bytes sent via SQL*Net to client
        624  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>
SQL> drop index ix1;

Index dropped.

SQL> create index ix1 on t ( c1,c5,c6) local;

Index created.

SQL>
SQL> set autotrace traceonly stat
SQL> select count(z) from t
  2  where c1 = 800012
  3  and   c5 = 2345
  4  and   c6 = 2345;

1 row selected.


Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
          3  consistent gets
          2  physical reads
          0  redo size
        549  bytes sent via SQL*Net to client
        624  bytes received via SQL*Net from client
          2  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
          1  rows processed

SQL>
SQL>


As you can see - depends on the data and your requirements.

Last precisely question

A reader, October 02, 2019 - 4:47 am UTC

Wow tkx so much for your explanation so clear.
In my case the index is always local not global.
And the partitioning is a range interval (1) on c1.
So making index on c5 c6 suffice right? What would you index for the same query ? Would you include c1?
Connor McDonald
October 03, 2019 - 1:42 am UTC

If it is "range interval(1)", ie, a single value of C1 per partition, then yes, (local) indexing just on C5/C6 should be sufficient.

Tkx a lot

A reader, October 03, 2019 - 4:50 am UTC

So optimizer is aware and may use the index in the best way. Tkx.

More to Explore

Administration

Need more information on Administration? Check out the Administrators guide for the Oracle Database