Skip to Main Content
  • Questions
  • How does a global index behave on partitioned table

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Ambuj.

Asked: October 13, 2018 - 9:29 pm UTC

Last updated: October 15, 2018 - 10:16 am UTC

Version: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 - 64bit Production

Viewed 1000+ times

You Asked

Suppose i have below table which has 1 billion record and partitioned based on Budget_Flag

CREATE TABLE xx_tab1
(budget_flag  varchar(1), 
period_name VARCHAR2(10),
begin_dr   number,
begin_cr  number, 
creation_date    DATE)
PARTITION BY LIST(budget_flag)
(
PARTITION budget_flag_A VALUES('A'),
PARTITION budget_flag_B VALUES ('B'),
PARTITION budget_flag_Dflt VALUES(DEFAULT)
);


and i have Global index on period_name column as below

create index index_period_name on xx_tab1(period_name);


and then i run the query as below

select (begin_cr-begin_dr) bal
from xx_tab1
where budget_flag = 'A'
and period_name = 'JAN-18'


is this query is going to look into only partitioned "budget_flag_A" because i have predicate "budget_flag" in my query or is this going to scan whole table because i have have predicate "Period_Name" in query which is not partitioned.

what kind of index, local or global index will be useful in this condition with regard to performance and maintainability?

Also if i have another Query which scan beyond the partition, and i have same index as mentioned above on "Period_Name"

select (begin_cr-begin_dr) bal
from xx_tab1
where period_name = 'JAN-18'


Is my above query going to perform same as non-partitioned table?
is partitioning is going to affect the performance?

and Chris said...

Provided you include the partition key in the query, the global index can restrict access to only the partitions of interest.

You can see this by looking at the Pstart & Pstop columns in an execution plan. For example:

CREATE TABLE xx_tab1
(budget_flag  varchar(1), 
period_name VARCHAR2(10),
begin_dr   number,
begin_cr  number, 
creation_date    DATE)
PARTITION BY LIST(budget_flag)
(
PARTITION budget_flag_A VALUES('A'),
PARTITION budget_flag_B VALUES ('B'),
PARTITION budget_flag_Dflt VALUES(DEFAULT)
);

insert into xx_tab1 
  with rws as (
    select level x from dual
    connect by level < 200
  )
  select case mod ( x, 2 )
           when 0 then 'A'
           else 'B'
         end,
         'JAN-' || lpad ( floor ( x / 2 ), 2, '0' ) per,
         x, x, sysdate
  from   rws;
commit;

create index index_period_name on xx_tab1(period_name);

set serveroutput off
select /*+ gather_plan_statistics */(begin_cr-begin_dr) bal
from   xx_tab1
where  budget_flag = 'A'
and    period_name = 'JAN-18';

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS LAST +PARTITION'));

select /*+ gather_plan_statistics */(begin_cr-begin_dr) bal
from   xx_tab1
where  budget_flag = 'B'
and    period_name = 'JAN-18';

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ROWSTATS LAST +PARTITION'));

PLAN_TABLE_OUTPUT                                                                                                     
SQL_ID  d18y8wmpmcypp, child number 0                                                                                 
-------------------------------------                                                                                 
select /*+ gather_plan_statistics */(begin_cr-begin_dr) bal from                                                      
xx_tab1 where  budget_flag = 'B' and    period_name = 'JAN-18'                                                        
                                                                                                                      
Plan hash value: 3318394780                                                                                           
                                                                                                                      
-------------------------------------------------------------------------------------------------------------------   
| Id  | Operation                                  | Name              | Starts | E-Rows | Pstart| Pstop | A-Rows |   
-------------------------------------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT                           |                   |      1 |        |       |       |      1 |   
|*  1 |  TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| XX_TAB1           |      1 |      1 |     2 |     2 |      1 |   
|*  2 |   INDEX RANGE SCAN                         | INDEX_PERIOD_NAME |      1 |      2 |       |       |      2 |   
-------------------------------------------------------------------------------------------------------------------   
                                                                                                                      
Predicate Information (identified by operation id):                                                                   
---------------------------------------------------                                                                   
                                                                                                                      
   1 - filter("BUDGET_FLAG"='B')                                                                                      
   2 - access("PERIOD_NAME"='JAN-18') 


Notice how the start/stop columns both show 1? And the filter operation filter("BUDGET_FLAG"='A')? This means the query only accessed this partition. The database is able to do the filtering using the index.

But if you query without the partition key, the database doesn't know which partitions the values could be in. So you access them all:

select /*+ gather_plan_statistics */(begin_cr-begin_dr) bal
from   xx_tab1
where  period_name = 'JAN-18';

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST +PARTITION'));

PLAN_TABLE_OUTPUT                                                                                                                            
SQL_ID  68x4sdkun5uhc, child number 0                                                                                                        
-------------------------------------                                                                                                        
select /*+ gather_plan_statistics */(begin_cr-begin_dr) bal from                                                                             
xx_tab1 where  period_name = 'JAN-18'                                                                                                        
                                                                                                                                             
Plan hash value: 2470579760                                                                                                                  
                                                                                                                                             
------------------------------------------------------------------------------------------------------------------------------------------   
| Id  | Operation                                  | Name              | Starts | E-Rows | Pstart| Pstop | A-Rows |   A-Time   | Buffers |   
------------------------------------------------------------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT                           |                   |      1 |        |       |       |      2 |00:00:00.01 |       3 |   
|   1 |  TABLE ACCESS BY GLOBAL INDEX ROWID BATCHED| XX_TAB1           |      1 |      2 | ROWID | ROWID |      2 |00:00:00.01 |       3 |   
|*  2 |   INDEX RANGE SCAN                         | INDEX_PERIOD_NAME |      1 |      2 |       |       |      2 |00:00:00.01 |       1 |   
------------------------------------------------------------------------------------------------------------------------------------------   
                                                                                                                                             
Predicate Information (identified by operation id):                                                                                          
---------------------------------------------------                                                                                          
                                                                                                                                             
   2 - access("PERIOD_NAME"='JAN-18')


Note that the Pstart/stop don't specify a specific partition. Just a rowid.

Richard Foote has started a series on global (non-partitioned) indexes on partitioned tables. Read this if you want to know more: https://richardfoote.wordpress.com/2018/10/04/hidden-efficiencies-of-non-partitioned-indexes-on-partitioned-tables-part-i-the-jean-genie/

Is my above query going to perform same as non-partitioned table?

No. Querying a partitioned table without the partition key is likely to be slower than the same query on a non-partitioned table. For example:

drop table xx_tab1 cascade constraints purge;
CREATE TABLE xx_tab1
(budget_flag  varchar(1), 
period_name VARCHAR2(10),
begin_dr   number,
begin_cr  number, 
creation_date    DATE);

insert into xx_tab1 
  with rws as (
    select level x from dual
    connect by level < 200
  )
  select case mod ( x, 2 )
           when 0 then 'A'
           else 'B'
         end,
         'JAN-' || lpad ( floor ( x / 2), 2, '0' ) per,
         x, x, sysdate
  from   rws;
commit;

create index index_period_name on xx_tab1(period_name);

select /*+ gather_plan_statistics */(begin_cr-begin_dr) bal
from   xx_tab1
where  period_name = 'JAN-18';

select * 
from   table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST +PARTITION'));

PLAN_TABLE_OUTPUT                                                                                                     
SQL_ID  68x4sdkun5uhc, child number 0                                                                                 
-------------------------------------                                                                                 
select /*+ gather_plan_statistics */(begin_cr-begin_dr) bal from                                                      
xx_tab1 where  period_name = 'JAN-18'                                                                                 
                                                                                                                      
Plan hash value: 3698929902                                                                                           
                                                                                                                      
-------------------------------------------------------------------------------------------------------------------   
| Id  | Operation                           | Name              | Starts | E-Rows | A-Rows |   A-Time   | Buffers |   
-------------------------------------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT                    |                   |      1 |        |      2 |00:00:00.01 |       2 |   
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| XX_TAB1           |      1 |      2 |      2 |00:00:00.01 |       2 |   
|*  2 |   INDEX RANGE SCAN                  | INDEX_PERIOD_NAME |      1 |      2 |      2 |00:00:00.01 |       1 |   
-------------------------------------------------------------------------------------------------------------------   
                                                                                                                      
Predicate Information (identified by operation id):                                                                   
---------------------------------------------------                                                                   
                                                                                                                      
   2 - access("PERIOD_NAME"='JAN-18')


Notice that the query does 2 gets instead of 3? In a non-partitioned table, all the database blocks can be in the same segment. Whereas with partitioning, rows in different partitions must be in different segments.

So it's possible to get all the data from a non-partitioned table with fewer I/O accesses.

is partitioning is going to affect the performance?

Yes. Could be faster, could be slower. It depends on your queries!

Partitioning is a big topic. Read Connor's guide on this to learn more:

https://asktom.oracle.com/partitioning-for-developers.htm

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

More to Explore

VLDB

If you are new to partitioning, check out Connor McDonald's introduction series here.

VLDB

Documentation set on VLDB and Partitioning.