Day: January 3, 2021

Btree vs Bitmap index (oracle)

In this article,we will look at the difference,advantages and disadvantages of using btree(default) and bitmap index

Btree index has a balanced tree like structure with root,branch and leaf nodes.It is the most commonly used index for performance improvement.The index itself store the rowid and column values in a table.If we want to unleash the full potential of btree index,then all the table rowid and column values which needs to be fetched should be stored in index blocks.There are row pointers which points to the table directly from the leaf blocks in index.Basic btree structure looks something like below figure

BTREE STRUCTURE

Like btree index,bitmap index also has similar structure but stores the column values in the form of binary values with combination of 0 and 1.We can say dummy variables created for the low distinct values making an intersection point.The structure of bitmap looks similar to below figure.

The figure shows that we try to select a shirt detail from shirt table where the shirt size is medium and i want shirt colour which should be both red and orange.

BITMAP STRUCTURE

CARDINALITY :

Btree – high cardinality (high distinct values)

SQL> select count(distinct order_id) as HIGH_CARDINALITY_COLUMN from sales;

HIGH_CARDINALITY_COLUMN
-----------------------
                1048576

SQL> select count(*) from sales;

  COUNT(*)
----------
   1048576

Bitmap – low cardinality (less number of distinct values)

SQL> select count(distinct order_priority) as LOW_CARDINALITY_COLUMN from sales;

LOW_CARDINALITY_COLUMN
----------------------
                     4
SQL> select count(*) from sales;

  COUNT(*)
----------
   1048576

SIZE:

Btree – Consume more space

SQL> select segment_name,bytes/1024/1024 as SIZE_MB from dba_segments where segment_name in ('BTREE_SALES_IT','BTREE_SALES_PRIORI');

SEGMENT_NAME                                SIZE_MB
---------------------------------------- ----------
BTREE_SALES_IT                                   25 >---- 25MB
BTREE_SALES_PRIORI                               16 >---- 16MB

Bitmap – Consume less space compared to Btree provided there should be less distinct values and if high distinct values are present,then consume more space

SQL> column segment_name format a40
SQL> select segment_name,bytes/1024/1024 as SIZE_MB from dba_segments where segment_name in ('BTM_SALES_IT','BTM_SALES_PRIORI');

SEGMENT_NAME                                SIZE_MB
---------------------------------------- ----------
BTM_SALES_PRIORI                              .8125  <----- 0.8MB
BTM_SALES_IT                                      2  <----- 2MB

INDEX USAGE ON NULL VALUES :

Btree – Null values with indexed columns are not stored in Btree

Oracle ignores the index and goes for full table scan when there is a btree indexed null column

SQL> alter table sales add NULL_COLUMN varchar2(10);

Table altered.

SQL> select count(*),count(NULL_COLUMN) from sales;

  COUNT(*) COUNT(NULL_COLUMN)
---------- ------------------
   1048576                  0

SQL> select /*+index(sales btree)*/ count(*) from sales where null_column='';

Elapsed: 00:00:00.00

Execution Plan
----------------------------------------------------------
Plan hash value: 8150843

-----------------------------------------------------------------------------
| Id  | Operation           | Name  | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |       |     1 |     7 |     0   (0)|          |
|   1 |  SORT AGGREGATE     |       |     1 |     7 |            |          |
|*  2 |   FILTER            |       |       |       |            |          |
|   3 |    TABLE ACCESS FULL| SALES |  1048K|  7168K|  6482   (6)| 00:00:01 |
-----------------------------------------------------------------------------

Bitmap – Null values with indexed columns are stored in Bitmap

Oracle use bitmap index on null columns as well it doesnot matter

SQL> select /*+index(sales bitmap)*/ count(*) from sales where null_column='';

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 633761674

-----------------------------------------------------------------------------------------
| Id  | Operation                      | Name   | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT               |        |     1 |     7 |     0   (0)|          |
|   1 |  SORT AGGREGATE                |        |     1 |     7 |            |          |
|*  2 |   FILTER                       |        |       |       |            |          |
|   3 |    BITMAP CONVERSION COUNT     |        |  1048K|  7168K|    23   (0)| 00:00:01 |
|   4 |     BITMAP INDEX FAST FULL SCAN| BITMAP |       |       |            |          |
-----------------------------------------------------------------------------------------

SYNTAX :

Btree:

create index index_name on table_name(column_name);

SQL> create index btree_SALES_PRIORI on sales(ORDER_PRIORITY);

Index created.

Elapsed: 00:00:03.23 <------- Observe time taken to create btree index on less distinct column
SQL> create index btree_SALES_IT on sales(ITEM_TYPE);

Index created.

Elapsed: 00:00:05.75 <------- Highly time consuming when btree index created on less distinct columns!! 😦

Bitmap:

create bitmap index index_name on table_name(column_name) nologging;

SQL> create bitmap index btm_sales_priori on sales(order_priority) nologging;

Index created.

Elapsed: 00:00:00.71 <---- less time taken to create bitmap in less distinct column 🙂
SQL> create bitmap index btm_sales_it on sales(item_type) nologging;

Index created.

Elapsed: 00:00:00.49 <--- less time taken to create bitmap in less distinct column 🙂

PURPOSE :

Btree – used for OLTP specifically for heavy DML transactions like INSERT,UPDATE and DELETE

Bitmap – used for OLAP specifically for STAR SCHEMAS with fact and dimension tables with frequent data retrieval like select queries with AND and OR operator in the where clause

DRAWBACKS:

Btree – Foreign key columns should be indexed which is referenced to a primary key column if not it cause locking issues

You will encounter below wait event with Row Exclusive Table Lock on parent table when there is an unindexed foreign key on a child table ,if the user forget to commit the data in case of btree index

  Event waited on                             Times   Max. Wait  Total Waited
  ----------------------------------------   Waited  ----------  ------------
  enq: TM - contention                            1        6.75          6.75

Bitmap – DML operations in bitmap indexed columns cause locking issues

So it is better to make bitmap index invisible or unusable when there is a DML load

creating bitmap index on high cardinality columns will

  • impact performance negatively
  • increase index size
  • index rebuild operation is time consuming

SUPPORTED EDITION:

Btree – default index supported in all editions

Bitmap – Supported in enterprise edition

EXECUTION PLAN:

Btree index on high distinct column: <—- See the time taken to create index ,number of physical IO,cost and size when btree created on high distinct column

SQL> create index btree_sales_id on sales(order_id);

Index created.

Elapsed: 00:00:02.98 <----
SQL> select segment_name,bytes/1024/1024 from dba_segments where segment_name='BTREE_SALES_ID';

SEGMENT_NAME                             BYTES/1024/1024
---------------------------------------- ---------------
BTREE_SALES_ID                                        19 <---

SQL> set timing on
SQL> set autot traceonly
SQL> select /*+index(btree_sales_id)*/ country,units_sold,unit_price,unit_cost,total_profit from sales where order_id between 123 and 1234;

1112 rows selected.

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 1484275115

--------------------------------------------------------------------------------
----------------------

| Id  | Operation                           | Name           | Rows  | Bytes | C
ost (%CPU)| Time     |

--------------------------------------------------------------------------------
----------------------

|   0 | SELECT STATEMENT                    |                |  1113 | 48972 |
  36   (3)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| SALES          |  1113 | 48972 |
  36   (3)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN                  | BTREE_SALES_ID |  1113 |       |
   5   (0)| 00:00:01 |

--------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          1  recursive calls
          0  db block gets
        179  consistent gets
         24  physical reads <----
          0  redo size
      46477  bytes sent via SQL*Net to client
       1309  bytes received via SQL*Net from client
         77  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
       1112  rows processed


Bitmap on high distinct column:<—- See the time taken to create,number of physical IO,cost and size when bitmap created on high distinct column

SQL> create bitmap index btm_sales_id on sales(order_id) nologging;

Index created.

Elapsed: 00:00:09.89 <------
SQL> select segment_name,bytes/1024/1024 from dba_segments where segment_name='BTM_SALES_ID';

SEGMENT_NAME                             BYTES/1024/1024
---------------------------------------- ---------------
BTM_SALES_ID                                          30 <-----


SQL> select /*+btm_sales_id*/ country,units_sold,unit_price,unit_cost,total_profit from sales where order_id between 123 and 1234;

1112 rows selected.

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
Plan hash value: 1651313649

--------------------------------------------------------------------------------
--------------------

| Id  | Operation                           | Name         | Rows  | Bytes | Cos
t (%CPU)| Time     |

--------------------------------------------------------------------------------
--------------------

|   0 | SELECT STATEMENT                    |              |  1113 | 48972 |   2
54   (1)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| SALES        |  1113 | 48972 |   2
54   (1)| 00:00:01 |

|   2 |   BITMAP CONVERSION TO ROWIDS       |              |       |       |
        |          |

|*  3 |    BITMAP INDEX RANGE SCAN          | BTM_SALES_ID |       |       |
        |          |

--------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
        239  recursive calls
          0  db block gets
        559  consistent gets
         87  physical reads <----
          0  redo size
      46278  bytes sent via SQL*Net to client
       1300  bytes received via SQL*Net from client
         76  SQL*Net roundtrips to/from client
         52  sorts (memory)
          0  sorts (disk)
       1112  rows processed

Btree:

Use btree index most of the time 99% unless there are specific requirement to use other indexes

Below is an example of highly distinct column

SQL> select count(order_id) from sales group by order_id having count(order_id) = 1 fetch next 10 rows only;

COUNT(ORDER_ID)
---------------
              1
              1
              1
              1
              1
              1
              1
              1
              1
              1

10 rows selected.

Bitmap:

Use bitmap index when a column has 1% distinct values from overall column

SQL> select count(order_priority) as ORDER_PRIORITY from sales group by order_priority having count(*) > 1;

ORDER_PRIORITY
--------------
        261964
        262329
        262160
        262123

Btree index on a low distinct column:<—- See number of physical IO and cost when btree created on low distinct column

SQL> select /*+index(sales btree_SALES_PRIORI) index(sales btree_SALES_IT)*/ country,ITEM_TYPE,SALES_CHANNEL,ORDER_PRIORITY from sales where item_type='Vegetables' and order_priority='L';

21769 rows selected.

Elapsed: 00:00:00.56

Execution Plan
----------------------------------------------------------
Plan hash value: 329813273

----------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                    | 21795 |   659K| 12458   (2)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID BATCHED| SALES              | 21795 |   659K| 12458   (2)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | BTREE_SALES_PRIORI |   262K|       |   519   (8)| 00:00:01 |
------------------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
          4  recursive calls
          0  db block gets
      15143  consistent gets
      12305  physical reads <-----
          0  redo size
     705620  bytes sent via SQL*Net to client
      16504  bytes received via SQL*Net from client
       1453  SQL*Net roundtrips to/from client
          0  sorts (memory)
          0  sorts (disk)
      21769  rows processed

Bitmap index on a low distinct column:<—- See effect of number of physical IO and cost when bitmap created on low distinct column

Creating bitmap indexes in multiple columns would enhance the data retrieval with lightning performance!

SQL> select country,ITEM_TYPE,SALES_CHANNEL,ORDER_PRIORITY from sales where item_type='Vegetables' and order_priority='L';

21769 rows selected.

Elapsed: 00:00:00.34

Execution Plan
----------------------------------------------------------
Plan hash value: 948039403

--------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name             | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                  | 21845 |   661K|  2743   (1)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| SALES            | 21845 |   661K|  2743   (1)| 00:00:01 |
|   2 |   BITMAP CONVERSION TO ROWIDS       |                  |       |       |            |          |
|   3 |    BITMAP AND                       |                  |       |       |            |          |
|*  4 |     BITMAP INDEX SINGLE VALUE       | BTM_SALES_IT     |       |       |            |          |
|*  5 |     BITMAP INDEX SINGLE VALUE       | BTM_SALES_PRIORI |       |       |            |          |
------------------------------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
         35  recursive calls
          5  db block gets
      12123  consistent gets
      10022  physical reads <-----
        956  redo size
     705400  bytes sent via SQL*Net to client
      16439  bytes received via SQL*Net from client
       1453  SQL*Net roundtrips to/from client
          2  sorts (memory)
          0  sorts (disk)
      21769  rows processed

From this post, it has been practically observed that both btree and bitmap index are very efficient in their own way and solve the purpose! we also learnt the basic differences between both indexes and where to effectively use them