Neden ve Ne zaman Bitmap Index

@ 09 Mart 2011 tarihinde yazdı. Yazıya yorum yazın.

Merhabalar,

Bu yazımda bir örnekle bitmap indeksi nasıl kullandığımızı göstermek istiyorum. Öncelikle hatırlatmam gerekirse bitmap indeks bir çeşit rowid indekslemesidir ancak verinin saklanması b-tree indekslere göre çok daha komplikedir. Bu komplikasyonun arkasında ise oldukça yüksek performans üretebilen bir indeksleme yapısı bulunmaktadır. Bitmap indeksleme çok fazla sayıda satıra sahip tablolardaki düşük çeşitlilik (cardinality) sahibi sütunlar üzerinde yapılmalıdır. Unutmamanız gereken bir başka konu ise bitmap indeksleme sizin için gerçek bir şeytana dönüşebilir! Bitmap indekslemenin olduğu tablo çok fazla güncelleniyor ve sürekli DML operasyonuna maruz kalıyorsa eğer bir daha düşünün çünkü bir bitmap indeksi güncellemenin maliyeti oldukça yüksektir ve kimi zaman yığın bir yüklemeden önce kullanılamaz hale getirilir ve yükleme bittikten sonra yeniden oluşturulurlar. Bunun sebebi ise bitmap indekslerin sahip olduğu bit’lerin sıkıştırılmış bir formatta bulundurulması. Yani bitmap indeks aslında bir sıkıştırılmış indekstir ve doğal olarak yerden de tasarruf edebilirsiniz. Bitmap indeksler yapısı gereği rowid içermek yerine bir bit tutarlar. Az sonraki örnekte bitmap indeksin sahip olduğu bit’leri nasıl rowid’ye değiştirdiğini göstereceğim. Bu durumun akabinde, değiştirilen rowid’ler de bir anahtar değere sahip olacaktırlar (verinin kendisi).

Bitmap indekslerin diğer indekslerden farklı olduğu bir başka durum ise NULL değer yani değersiz değeri indeksleyebilmelidir. Bu durum bir takım sorguların müthiş hızlı çalışmasını sağlayabilir (örneğin tek ve yegane null değeri kabul ederek çalışan count(*) gibi). Ayrıca bitmap indekslerin kaplayacağı disk boyutu da standart bir b-tree indeksten daha az olacaktır!

Aşağıdaki örneğimizde bir bitmap indeksin işleri nasıl kolaylaştırdığını göstereceğim;

SQL> set line 1000
SQL> set pagesize 1000
SQL> set timing on
SQL> set autotrace on
SQL> drop table ogan_deneme cascade constraints purge;
drop table ogan_deneme cascade constraints purge
*
ERROR at line 1:
ORA-00942: table or view does not exist

Elapsed: 00:00:00.02
SQL> create table ogan_deneme
2  as
3  select * from all_objects;

Table created.

Elapsed: 00:00:14.05
SQL> select count(*) from ogan_deneme;

COUNT(*)
----------
466586

Elapsed: 00:00:02.10

Execution Plan
----------------------------------------------------------
Plan hash value: 423114900

--------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Cost (%CPU)| Time     |
--------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |     1 |   336   (8)| 00:00:02 |
|   1 |  SORT AGGREGATE    |             |     1 |            |          |
|   2 |   TABLE ACCESS FULL| OGAN_DENEME |   466K|   336   (8)| 00:00:02 |
--------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

–> Gördüğünüz gibi basit bir okumanın maliyeti bize ciddi bir yük getirdi. Bütün tablo tarandı ve 3858 veri bloğundan fiziksel olarak bilgi istendi. Çok kötü!

SQL> desc ogan_deneme;
Name                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              Null?    Type
-------------------------------
OWNER NOT NULL VARCHAR2(30)
OBJECT_NAME NOT NULL VARCHAR2(30)
SUBOBJECT_NAME VARCHAR2(30)
OBJECT_ID NOT NULL NUMBER
DATA_OBJECT_ID NUMBER
OBJECT_TYPE VARCHAR2(19)
CREATED NOT NULL DATE
LAST_DDL_TIME NOT NULL DATE
TIMESTAMP VARCHAR2(19)
STATUS VARCHAR2(7)
TEMPORARY VARCHAR2(1)
GENERATED VARCHAR2(1)
SECONDARY VARCHAR2(1)

SQL> select count(distinct object_type) from ogan_deneme;

COUNT(DISTINCTOBJECT_TYPE)
--------------------------
25

Elapsed: 00:00:00.30

Execution Plan
----------------------------------------------------------
Plan hash value: 1705519545

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |     1 |    11 |   348  (11)| 00:00:02 |
|   1 |  SORT GROUP BY     |             |     1 |    11 |            |          |
|   2 |   TABLE ACCESS FULL| OGAN_DENEME |   466K|  5008K|   348  (11)| 00:00:02 |
----------------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

–> Az önce yaptığımız sorgu cache içerisinde barındırıldı ve fiziksel okuma aşamasından mantıksal okuma aşamasına geçiş yaptığımız için “physical reads” sıfır olarak gerçekleştir.

SQL> create bitmap index ogan_bitmap
2  on ogan_deneme (object_type);

Index created.

Elapsed: 00:00:00.36

SQL> select count(*) from ogan_deneme;

COUNT(*)
----------
466586

Elapsed: 00:00:01.66

Execution Plan
----------------------------------------------------------
Plan hash value: 1145454183

-------------------------------------------------------------------------------------
| Id  | Operation                     | Name        | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |     1 |    11   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE               |             |     1 |            |          |
|   2 |   BITMAP CONVERSION COUNT     |             |   466K|    11   (0)| 00:00:01 |
|   3 |    BITMAP INDEX FAST FULL SCAN| OGAN_BITMAP |       |            |          |
-------------------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

–> “recursive calls”, “consistent gets” ve “physical reads” tutarlarının nasıl bir anda değiştiğini görebilirsiniz. Aynı sorguyu yeniden çalıştırdığımda ise performansın ne derece arttığı gözler önünde olacaktır.

SQL> /

COUNT(*)
----------
466586

Elapsed: 00:00:00.01

Execution Plan
----------------------------------------------------------
Plan hash value: 1145454183

-------------------------------------------------------------------------------------
| Id  | Operation                     | Name        | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |     1 |    11   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE               |             |     1 |            |          |
|   2 |   BITMAP CONVERSION COUNT     |             |   466K|    11   (0)| 00:00:01 |
|   3 |    BITMAP INDEX FAST FULL SCAN| OGAN_BITMAP |       |            |          |
-------------------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

–> 1 salisede sonuç elde ettik, hiç “recursive call”, “physical reads” olmadan okumamızı tamamladık. CBO’nun hesapladığı maliyet birimi (cost) ise 348’den 11’e indi. Gerçekleştirdiğimiz operasyon ise “Bitmap conversion count” yani toplam verinin saydırılması için kullandığımız bir değiştirme methodu. Bunun nasıl değiştirildiğine bakmadan önce yine bir performans karşılaştırması yapalım;

SQL> select count(distinct object_id) from ogan_deneme;

COUNT(DISTINCTOBJECT_ID)
------------------------
466586

Elapsed: 00:00:08.45

Execution Plan
----------------------------------------------------------
Plan hash value: 1705519545

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             |     1 |    13 |   343  (10)| 00:00:02 |
|   1 |  SORT GROUP BY     |             |     1 |    13 |            |          |
|   2 |   TABLE ACCESS FULL| OGAN_DENEME |   466K|  5919K|   343  (10)| 00:00:02 |
----------------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

–> indekslenmeyen alan üzerinde sayım talebinde bulunduğumuz zaman tablo yine felaket..

SQL>  select count(distinct object_type) from ogan_deneme;

COUNT(DISTINCTOBJECT_TYPE)
--------------------------
25

Elapsed: 00:00:00.16

Execution Plan
----------------------------------------------------------
Plan hash value: 1281218884

---------------------------------------------------------------------------------------------
| Id  | Operation                     | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |             |     1 |    11 |    11   (0)| 00:00:01 |
|   1 |  SORT GROUP BY                |             |     1 |    11 |            |          |
|   2 |   BITMAP CONVERSION TO ROWIDS |             |   466K|  5008K|    11   (0)| 00:00:01 |
|   3 |    BITMAP INDEX FAST FULL SCAN| OGAN_BITMAP |       |       |            |          |
---------------------------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

–> Bitmap indeksimizin bulunduğu durumda ise sonuç gerçekten çok iyi. Buradaki aşamanın adı ise “Bitmap conversion to rowids”. İşte bitmap indeks bit’lerinin rowid’lerine dönüştürülerek, esas sorgulamak istediğimiz veriye ulaştığımız aşama. Bir de btree indeksin nasıl davrandığına bakalım;

SQL> create table ogan_btree
2  as
3  select * from all_objects;

Table created.

SQL> select count(*) from ogan_btree;

COUNT(*)
----------
466588

Elapsed: 00:00:03.40

Execution Plan
----------------------------------------------------------
Plan hash value: 1605169051

-------------------------------------------------------------------------
| Id  | Operation          | Name       | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |            |     1 |   337   (9)| 00:00:02 |
|   1 |  SORT AGGREGATE    |            |     1 |            |          |
|   2 |   TABLE ACCESS FULL| OGAN_BTREE |   488K|   337   (9)| 00:00:02 |
-------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

SQL> create index ogan_btree on ogan_btree(object_type) compute statistics;

Index created.

Elapsed: 00:00:03.13

SQL> select count(*) from ogan_btree;

COUNT(*)
----------
466588

Elapsed: 00:00:00.14

Execution Plan
----------------------------------------------------------
Plan hash value: 1605169051

-------------------------------------------------------------------------
| Id  | Operation          | Name       | Rows  | Cost (%CPU)| Time     |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |            |     1 |   337   (9)| 00:00:02 |
|   1 |  SORT AGGREGATE    |            |     1 |            |          |
|   2 |   TABLE ACCESS FULL| OGAN_BTREE |   488K|   337   (9)| 00:00:02 |
-------------------------------------------------------------------------

Note
-----
- dynamic sampling used for this statement

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

–> CBO aynı sütun için tanımladığımız btree indeksi kullanmak bile istemedi!

Yukarıda örneğini gösterdiğim maliyetler yalnızca 466 bin civarında kaydı olan bir tablo için geçerliydi ancak bunun kat be katı daha fazla büyüklükte olan tablolardaki performansı siz düşünün. Daha komplike örneklerde de karmaşık WHERE koşullarındaki davranışları gösterilebilir ancak benim verdiğim sade örnekle arasında çok fazla fark yok zira sonuç yine “bitmap conversion to rowids” !

İyi çalışmalar.

Ogan