Banco de dados Oracle – Dicas sobre índices

05 jun

Banco de dados Oracle – Dicas sobre índices

Design sem nome (2)

Introdução

Para alguns predicados quais índices são indicados?

  • “IS NULL”, criei o índice mas não está sendo utilizado?!
  • “WHERE ID=500 AND COD=3” ou “WHERE COD=3”. Qual ordem das colunas na criação do índice?
  • “STATUS=’ATIVO’”. Sendo status com baixa cardinalidade.

Dicas 1 - “IS NULL”, criei o índice mas não está sendo utilizado?!

  • Podemos observar que a coluna "NULO" é indexada (linha 5 do documento), mesmo com a seletividade bem baixa de 0,05%, foi realizado FTS.
  • Índices B-Tree não indexa valores nulos.
  • Criando o índice "IDX2_TESTE01_ISNULL" composto com uma constante, podemos contornar isso.

 

-- Preparação do ambiente
SQL> CREATE TABLE TESTE01 AS SELECT ' ' AS NULO,'DATAUNIQUE' NAME FROM DUAL CONNECT BY LEVEL <= 1000000;
SQL> UPDATE TESTE01 SET NULO=null;
SQL> UPDATE TESTE01 SET NULO='A' WHERE ID >= 500;
SQL> CREATE INDEX IDX1_TESTE01_ISNULL ON TESTE01(NULO);
SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>NULL, TABNAME=>'TESTE01', ESTIMATE_PERCENT=> NULL, METHOD_OPT=> 'FOR ALL COLUMNS SIZE 1');


SQL>SELECT INDEX_NAME, BLEVEL, LEAF_BLOCKS, CLUSTERING_FACTOR FROM USER_INDEXES WHERE INDEX_NAME LIKE '%TESTE01%';

INDEX_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
---------------------------------------- ---------- ----------- -----------------
IDX1_TESTE01_ISNULL 2 1811 3582





SQL>SELECT * FROM TESTE01 WHERE NULO IS NULL;

501 linhas selecionadas.


Plano de Execução
----------------------------------------------------------
Plan hash value: 3707836917

-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 501 | 10521 | 1036 (7)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| TESTE01 | 501 | 10521 | 1036 (7)| 00:00:01 |
-----------------------------------------------------------------------------

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

1 - filter("NULO" IS NULL)


Estatísticas
----------------------------------------------------------
19 recursive calls
0 db block gets
3645 consistent gets
0 physical reads
0 redo size
12959 bytes sent via SQL*Net to client
971 bytes received via SQL*Net from client
35 SQL*Net roundtrips to/from client
4 sorts (memory)
0 sorts (disk)
501 rows processed
----------------------------------------------------------

SQL> CREATE INDEX IDX2_TESTE01_ISNULL ON TESTE01(NULO,1);


SQL>SELECT INDEX_NAME, BLEVEL, LEAF_BLOCKS,CLUSTERING_FACTOR FROM USER_INDEXES WHERE INDEX_NAME LIKE '%TESTE01%';

INDEX_NAME BLEVEL LEAF_BLOCKS CLUSTERING_FACTOR
---------------------------------------- ---------- ----------- -----------------
IDX1_TESTE01_ISNULL 2 1811 3582
IDX2_TESTE01_ISNULL 2 2092 3585


SQL>SELECT * FROM TESTE01 WHERE NULO IS NULL;

501 linhas selecionadas.


Plano de Execução
----------------------------------------------------------
Plan hash value: 525699655

-----------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 501 | 10521 | 6 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID BATCHED| TESTE01 | 501 | 10521 | 6 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX2_TESTE01_ISNULL | 501 | | 4 (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------

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

2 - access("NULO" IS NULL)


Estatísticas
----------------------------------------------------------
2 recursive calls
0 db block gets
81 consistent gets
0 physical reads
0 redo size
12959 bytes sent via SQL*Net to client 
971 bytes received via SQL*Net from client
35 SQL*Net roundtrips to/from client
1 sorts (memory)
0 sorts (disk)
501 rows processed

Dicas 2 - “WHERE ID=500 AND COD=3” ou “WHERE COD=3”. Qual ordem das colunas na criação do índice?

  • Não importando a ordem das colunas podemos observar o mesmo planos e o mesmo número de consistente gets.
  • Utilizando apenas o COD no WHERE, no índice com a coluna mais distinta primeiro, ocorreu FTS, mas IDX1_TESTE01_COD_ID foi usado, como é possível se ambos índices tem a coluna COD?

-- trace dump do índice
SELECT object_id FROM user_objectsWHERE object_name = IDX1_TESTE01_ID_COD ';

ALTER SESSION SET EVENTS 'immediate trace name treedump level object_id';

----- begin tree dump
branch: 0x417ca9 4291753 (0: nrow: 5, level: 2) -- root block
branch: 0x418caf 4295855 (-1: nrow: 556, level: 1)
leaf: 0x417caa 4291754 (-1: row:425.425 avs:824)
----- fim

SQL> select dbms_utility.data_block_address_file(4291753), dbms_utility.data_block_address_block(4291753) from dual;

-- dump do nó raiz
SQL> alter system dump datafile 1 block 97456;

“v$diag_info” -> para informações da localização do dump.

  • Temos agora o dump do nó raiz de ambos os índices. Notem que o índice IDX1_TESTE01_ID_COD não indexa a coluna COD, já que a coluna ID é suficiente.
  -- preparação do ambiente de testes  					
 CREATE TABLE TESTE02 AS SELECT mod(rownum,500000) id, mod(rownum,10000) cod, 'DATAUNIQUE' name FROM dual CONNECT BY LEVEL <= 1000000;  					
   					
 -- Para os predicados citados, é comum o pensamento de colocar ID na primeira posição do indice	 Mas			   	
 -- Indices possiveis	  				
   					
 SQL> CREATE INDEX IDX1_TESTE01_ID_COD ON TESTE01(ID,COD);  					
 SQL> CREATE INDEX IDX1_TESTE01_COD_ID ON TESTE01(COD,ID) INVISIBLE;  					
   					
 -- Coletando estatísticas  					
 EXEC DBMS_STATS	GATHER_TABLE_STATS(OWNNAME=>NULL, TABNAME=>'TESTE01', ESTIMATE_PERCENT=> NULL, METHOD_OPT=> 'FOR ALL COLUMNS SIZE 1');   				
   					
 -- distribuição   					
 SQL>SELECT TABLE_NAME, COLUMN_NAME, NUM_DISTINCT FROM DBA_TAB_COLS WHERE OWNER = 'SYS' AND TABLE_NAME LIKE 'TESTE01' AND COLUMN_NAME IN ('COD','ID');  					
   					
 TABLE_NAME                     COLUMN_NAME                    NUM_DISTINCT  					
 ------------------------------ ------------------------------ ------------  					
 TESTE01                        ID                                   500000   (Mais valores distintos)					
 TESTE01                        COD                                    1000  					
    					
   					
					
 					
 SQL>SELECT * FROM TESTE01 WHERE ID=500 AND COD=3;  					
   					
 não há linhas selecionadas  					
   					
 Plano de Execução  					
 ----------------------------------------------------------  					
 Plan hash value: 2374796228  					
   					
 -----------------------------------------------------------------------------------------------------------  					
 | Id  | Operation                           | Name                | Rows  | Bytes | Cost (%CPU)| Time     |  					
 -----------------------------------------------------------------------------------------------------------  					
 |   0 | SELECT STATEMENT                    |                     |     2 |    40 |     5   (0)| 00:00:01 |  					
 |   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TESTE01             |     2 |    40 |     5   (0)| 00:00:01 |  					
 |*  2 |   INDEX RANGE SCAN                  | IDX1_TESTE01_ID_COD |     2 |       |     3   (0)| 00:00:01 |  					
 -----------------------------------------------------------------------------------------------------------  					
   					
 Predicate Information (identified by operation id):  					
 ---------------------------------------------------  					
   					
    2 - access("ID"=500 AND "COD"=3)  					
   					
   					
 Estatísticas  					
 ----------------------------------------------------------  					
           0  recursive calls  					
           0  db block gets  					
           3  consistent gets  					
           0  physical reads  					
           0  redo size  					
         477  bytes sent via SQL*Net to client  					
         597  bytes received via SQL*Net from client  					
           1  SQL*Net roundtrips to/from client  					
           0  sorts (memory)  					
           0  sorts (disk)  					
           0  rows processed  					
   					
 ----------------------------------------------------------  					
    					
 					
 SQL>SELECT * FROM TESTE01 WHERE COD=3;  					
   					
 100 linhas selecionadas	  				
   					
   					
 Plano de Execuão  					
 ----------------------------------------------------------  					
 Plan hash value: 3707836917  					
   					
 -----------------------------------------------------------------------------  					
 | Id  | Operation         | Name    | Rows  | Bytes | Cost (%CPU)| Time     |  					
 -----------------------------------------------------------------------------  					
 |   0 | SELECT STATEMENT  |         |   100 |  2000 |  1003   (7)| 00:00:01 |  					
 |*  1 |  TABLE ACCESS FULL| TESTE01 |   100 |  2000 |  1003   (7)| 00:00:01 |  					
 -----------------------------------------------------------------------------  					
   					
 Predicate Information (identified by operation id):  					
 ---------------------------------------------------  					
   					
    1 - filter("COD"=3)  					
   					
   					
 Estatísticas  					
 ----------------------------------------------------------  					
           0  recursive calls  					
           0  db block gets  					
        3469  consistent gets  					
           0  physical reads  					
           0  redo size  					
        2883  bytes sent via SQL*Net to client  					
         674  bytes received via SQL*Net from client  					
           8  SQL*Net roundtrips to/from client  					
           0  sorts (memory)  					
           0  sorts (disk)  					
         100  rows processed  					
   					
 ----------------------------------------------------------  					
        SQL> ALTER INDEX IDX1_TESTE01_ID_COD INVISIBLE;  					
 SQL> ALTER INDEX IDX1_TESTE01_COD_ID VISIBLE;  					
   					
 SQL>SELECT * FROM TESTE01 WHERE ID=500 AND COD=3;  					
   					
 não há linhas selecionadas  					
   					
   					
 Plano de Execução  					
 ----------------------------------------------------------  					
 Plan hash value: 3469596195  					
   					
 -----------------------------------------------------------------------------------------------------------  					
 | Id  | Operation                           | Name                | Rows  | Bytes | Cost (%CPU)| Time     |  					
 -----------------------------------------------------------------------------------------------------------  					
 |   0 | SELECT STATEMENT                    |                     |     2 |    40 |     5   (0)| 00:00:01 |  					
 |   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TESTE01             |     2 |    40 |     5   (0)| 00:00:01 |  					
 |*  2 |   INDEX RANGE SCAN                  | IDX1_TESTE01_COD_ID |     2 |       |     3   (0)| 00:00:01 |  					
 -----------------------------------------------------------------------------------------------------------  					
   					
 Predicate Information (identified by operation id):  					
 ---------------------------------------------------  					
   					
    2 - access("COD"=3 AND "ID"=500)  					
   					
   					
 Estatísticas  					
 ----------------------------------------------------------  					
 0  recursive calls  					
           0  db block gets  					
           3  consistent gets  					
           0  physical reads  					
           0  redo size  					
         477  bytes sent via SQL*Net to client  					
         597  bytes received via SQL*Net from client  					
           1  SQL*Net roundtrips to/from client  					
           0  sorts (memory)  					
           0  sorts (disk)  					
           0  rows processed  					
   					
 ----------------------------------------------------------  					
 SQL>SELECT * FROM TESTE01 WHERE COD=3;  					
   					
 100 linhas selecionadas	  				
   					
   					
 Plano de Execução  					
 ----------------------------------------------------------  					
 Plan hash value: 3469596195  					
   					
 -----------------------------------------------------------------------------------------------------------  					
 | Id  | Operation                           | Name                | Rows  | Bytes | Cost (%CPU)| Time     |  					
 -----------------------------------------------------------------------------------------------------------  					
 |   0 | SELECT STATEMENT                    |                     |   100 |  2000 |   103   (0)| 00:00:01 |  					
 |   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TESTE01             |   100 |  2000 |   103   (0)| 00:00:01 |  					
 |*  2 |   INDEX RANGE SCAN                  | IDX1_TESTE01_COD_ID |   100 |       |     3   (0)| 00:00:01 |  					
 -----------------------------------------------------------------------------------------------------------  					
   					
 Predicate Information (identified by operation id):  					
 ---------------------------------------------------  					
   					
    2 - access("COD"=3)  					
   					
   					
 Estatísticas  					
 ----------------------------------------------------------  					
           0  recursive calls  					
           0  db block gets  					
         111  consistent gets  					
           0  physical reads  					
           0  redo size  					
        2638  bytes sent via SQL*Net to client  					
         674  bytes received via SQL*Net from client  					
           8  SQL*Net roundtrips to/from client  					
           0  sorts (memory)  					
           0  sorts (disk)  					
         100  rows processed  					
 ----------------------------------------------------------  					
   					
 					
 					
Parte dump nó raiz do indice IDX1_TESTE01_ID_COD					
------------------------------------------------------------------
   Branch block dump  					
   =================  					
   header address 1903575108=0x71764044  					
   kdxcolev 2  					
   KDXCOLEV Flags = - - -  					
   kdxcolok 0  					
   kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y  					
   kdxconco 3  					
   kdxcosdc 0  					
 kdxconro 3  					
 kdxcofbo 34=0x22  					
 kdxcofeo 8026=0x1f5a  					
 kdxcoavs 7992  					
 kdxbrlmc 4298659=0x4197a3  					
 kdxbrsno 0  					
 kdxbrbksz 8056  					
 kdxbr2urrc 0  					
 row#0[8046] dba: 4299330=0x419a42  					
col 1; TERM                       ----------------------
   row#1[8036] dba: 4300001=0x419ce1  					
   col 0; len 4; (4):  c3 34 05 5a  					
   col 1; TERM  					
   row#2[8026] dba: 4300620=0x419f4c  					
   col 0; len 4; (4):  c3 4d 33 5a  					
   col 1; TERM  					
   ----- end of branch block dump -----  					
     					
   ------------------------------------------------------------------  					
   					
 Parte dump nó raiz do indice IDX1_TESTE01_COD_ID  					
 ------------------------------------------------------------------  					
 Branch block dump  					
 =================  					
 header address 1886658628=0x70742044  					
 kdxcolev 2  					
 KDXCOLEV Flags = - - -  					
 kdxcolok 0  					
 kdxcoopc 0x80: opcode=0: iot flags=--- is converted=Y  					
 kdxconco 3  					
 kdxcosdc 0  					
 kdxconro 4  					
 kdxcofbo 36=0x24  					
 kdxcofeo 8004=0x1f44  					
 kdxcoavs 7968  					
 kdxbrlmc 4295855=0x418caf  					
 kdxbrsno 0  					
 kdxbrbksz 8056  					
 kdxbr2urrc 0  					
 row#0[8043] dba: 4296396=0x418ecc  					
 col 0; len 2; (2):  c1 03  					
 col 1; len 4; (4):  c3 10 4c 3f  					
 col 2; TERM  					
 row#1[8030] dba: 4296937=0x4190e9  					
 col 0; len 2; (2):  c1 05  					
 col 1; len 4; (4):  c3 14 2d 2d  					
 col 2; TERM  					
 row#2[8017] dba: 4297478=0x419306  					
 col 0; len 2; (2):  c1 07  					
 col 1; len 4; (4):  c3 18 0e 25  					
 col 2; TERM  					
 row#3[8004] dba: 4297938=0x4194d2  					
 col 0; len 2; (2):  c1 09  					
 col 1; len 4; (4):  c3 1b 53 1d  					
 col 2; TERM

Dicas 3 - “STATUS=’ATIVO’”. Sendo status com baixa cardinalidade.

CREATE TABLE TBSTATUS(ID NUMBER, status varchar2(10));  												
  												
INSERT INTO TBSTATUS SELECT rownum,'INATIVO' FROM DUAL CONNECT BY LEVEL <= 1000000;  												
  												
UPDATE TBSTATUS SET status='ATIVO' WHERE ID > 999950;  												
  												
EXEC DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=>NULL, TABNAME=>'TBSTATUS', ESTIMATE_PERCENT=> NULL, METHOD_OPT=> 'FOR COLUMNS STATUS SIZE 2');   												
  												
SQL>SELECT COUNT(*),STATUS FROM TBSTATUS GROUP BY STATUS;  												
  												
  COUNT(*) STATUS  												
---------- ----------  												
    999950 INATIVO  												
        50 ATIVO  												
  												
  												
CREATE INDEX IDX_TBSTATUS_01 ON TBSTATUS(status);  												
  												
  												
SQL>SELECT * FROM TBSTATUS WHERE status = 'ATIVO';  												
  												
50 linhas selecionadas.  												
  												
  												
Plano de Execução  												
----------------------------------------------------------  												
Plan hash value: 3383094675  												
  												
-------------------------------------------------------------------------------------------------------  												
| Id  | Operation                           | Name            | Rows  | Bytes | Cost (%CPU)| Time     |  												
-------------------------------------------------------------------------------------------------------  												
|   0 | SELECT STATEMENT                    |                 |    50 |   650 |     4   (0)| 00:00:01 |  												
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TBSTATUS        |    50 |   650 |     4   (0)| 00:00:01 |  												
|*  2 |   INDEX RANGE SCAN                  | IDX_TBSTATUS_01 |    50 |       |     3   (0)| 00:00:01 |  												
-------------------------------------------------------------------------------------------------------  												
  												
Predicate Information (identified by operation id):  												
---------------------------------------------------  												
  												
   2 - access("STATUS"='ATIVO')  												
  												
  												
Estatísticas  												
----------------------------------------------------------  												
          0  recursive calls  												
          0  db block gets  												
         12  consistent gets  												
          0  physical reads  												
          0  redo size  												
       1733  bytes sent via SQL*Net to client  												
        641  bytes received via SQL*Net from client  												
          5  SQL*Net roundtrips to/from client  												
          0  sorts (memory)  												
          0  sorts (disk)  												
         50  rows processed  												
----------------------------------------------------------  												
  												
CREATE INDEX IDX_TBSTATUS_02 ON TBSTATUS(DECODE(status, 'ATIVO', 'ATIVO', NULL));  												
  												
SQL>SELECT index_name, blevel, leaf_blocks, distinct_keys, num_rows FROM user_indexes WHERE table_name='TBSTATUS';  												
  												
INDEX_NAME                         BLEVEL LEAF_BLOCKS DISTINCT_KEYS   NUM_ROWS  												
------------------------------ ---------- ----------- ------------- ----------  												
IDX_TBSTATUS_01                         2        2653             2    1000000  												
IDX_TBSTATUS_02                         0           1             1         50  -- novamente B-tree não indexa null 												
  											  -- assim o índice ocupa menos espaço.	
  												
-- realizado esta mudança no SQL, Temos:  												
SQL>SELECT * FROM TBSTATUS WHERE DECODE(status,'ATIVO','ATIVO',null) = 'ATIVO';  												
  												
50 linhas selecionadas.  												
  												
  												
Plano de Execução  												
----------------------------------------------------------  												
Plan hash value: 3422389394  												
  												
-------------------------------------------------------------------------------------------------------  												
| Id  | Operation                           | Name            | Rows  | Bytes | Cost (%CPU)| Time     |  												
-------------------------------------------------------------------------------------------------------  												
|   0 | SELECT STATEMENT                    |                 |    50 |   700 |     2   (0)| 00:00:01 | -- Custo reduziu 50%												
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| TBSTATUS        |    50 |   700 |     2   (0)| 00:00:01 |  												
|*  2 |   INDEX RANGE SCAN                  | IDX_TBSTATUS_02 |    50 |       |     1   (0)| 00:00:01 |  												
-------------------------------------------------------------------------------------------------------  												
  												
Predicate Information (identified by operation id):  												
---------------------------------------------------  												
  												
   2 - access(DECODE("STATUS",'ATIVO','ATIVO',NULL)='ATIVO')  												
  												
  												
Estatísticas  												
----------------------------------------------------------  												
   	0  recursive calls  											
   	0  db block gets  											
   	10  consistent gets  -- diferença de 2 Consistent gets. 											
   	0  physical reads  											
   	0  redo size  											
   	1733  bytes sent via SQL*Net to client  											
641  bytes received via SQL*Net from client  												
5  SQL*Net roundtrips to/from client  												
0  sorts (memory)  												
0  sorts (disk)  												
50  rows processed

Referências:
https://docs.oracle.com/cd/E11882_01/server.112/e40540/indexiot.htm#CNCPT1170
https://docs.oracle.com/cd/E11882_01/server.112/e41084/functions049.htm#SQLRF00631
https://docs.oracle.com/database/121/REFRN/GUID-89772970-11A3-4508-A3D3-C7149B1F8642.htm#REFRN30502