Evaluarea operatorilor relaţionali

Sursa de date pentru operatori poate fi:

Unele valori posibile pentru coloana operation din tabelul PLAN_TABLE sunt descrise în continuare (se vor explica principiile de evaluare a unor operatori). O descriere completă a acestor valori este în documentaţia Oracle OPERATION and OPTIONS Values Produced by EXPLAIN PLAN, sau într-un document local.

Pe lângă tipul de operaţie care se execută (coloana operation), la unele dintre operaţii este importantă şi coloana options (care este o completare a operaţiei).

DELETE STATEMENT, INSERT STATEMENT, SELECT STATEMENT, UPDATE STATEMENT

Această operaţie este prima înregistrare extrasă în Plan_Table (este ultima operaţie care se executată la evaluarea planului generat) şi corespunde la instrucţiunea SQL pentru care se generează planul (instrucţiune delete, insert, select, update).

Consultarea întregului tabel

De exemplu, pentru instrucţiunea:

select * from studenti;

se obţine planul:

------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |  3073 |   270K|    13   (0)| 00:00:01 |
|   1 |  TABLE ACCESS FULL| STUDENTI |  3073 |   270K|    13   (0)| 00:00:01 |
------------------------------------------------------------------------------

Pentru instrucţiunea:

select * from studenti where nume<'POP';
se obţine planul:
------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |    37 |  3330 |    13   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| STUDENTI |    37 |  3330 |    13   (0)| 00:00:01 |
------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - filter("NUME"<'POP')

Operaţia TABLE ACCESS cu optiunea FULL va consulta întregul tabel, în ordinea în care se memorează înregistrările. Acest tip de consultare se recomandă dacă se folosesc mai mult de 5% din înregistrările memorate în tabel, sau dacă nu există definite indexuri pentru coloanele folosite la filtrare.

Consultarea unui tabel cu utilizarea unui index

Operaţia TABLE ACCESS cu optiunea BY INDEX ROWID va consulta un tabel şi se va folosi un index. Pentru acest tip de operaţie se va folosi o nouă operaţie, cu numele INDEX (imediat următoarea în structura arborescentă), care precizează modul de interogare a indexului.

Considerăm instrucţiunea:

select * from discipline where cod='MII0001';

Tabelul discipline are un index construit pentru cheia primară cod. Planul de execuţie este:

-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |     1 |   112 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| DISCIPLINE  |     1 |   112 |     2   (0)| 00:00:01 |
|*  2 |   INDEX UNIQUE SCAN         | SYS_C005507 |     1 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("COD"='MII0001')

Se observă o consultare a unui index, iar valoarea se foloseşte pentru un acces rapid la o înregistrare identificată în index.

Pentru instrucţiunea:

select * from discipline where cod<'MII0001';

se obţine un plan asemănător (se schimbă INDEX UNIQUE SCAN cu INDEX RANGE SCAN), chiar dacă ar fi necesară parcurgerea întregului tabel:

-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |     4 |   448 |     3   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| DISCIPLINE  |     4 |   448 |     3   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | SYS_C005507 |     4 |       |     2   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

In cazul în care numărul de înregistrări furnizate de interogare este mare, aceeaşi formă de instructiune select, dar cu altă condiţie, poate să furnizeze alt plan de execuţie (poate să folosească TABLE ACCESS FULL, deci indexul nu este utilizat). Exemplu pentru instrucţiunea:

select * from discipline where cod<'PP0001';

se obţine planul:

--------------------------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |   664 | 74368 |     8   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| DISCIPLINE |   664 | 74368 |     8   (0)| 00:00:01 |
--------------------------------------------------------------------------------

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

   1 - filter("COD"<'PP0001')

Consultarea cu Index Range Scan se foloseşte când în clauza where apar condiţii cu operatorii relaţionali: <, <, <>, >=, <=, between. Tot această metodă se foloseşte dacă indexul nu este unic (pot exista mai multe înregistrări pentru o valoare a expresiei de index), iar datele vor fi furnizate în ordinea crescătoare a coloanelor din index.
Consultarea cu operatorul Index Full Scan presupune parcurgerea întregului index. Acest operator se poate folosi numai cu optimizatorul bazat pe cost, care stabileşte dacă o scanare completă a indexului este o soluţie mai bună ca un full table scan împreună cu o operaţie de sort. De exemplu, pentru instrucţiunea:

select * from sectii order by cod;

se obţine planul:

-------------------------------------------------------------------------------------------
| Id  | Operation                   | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |             |    30 |  1350 |     2   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID| SECTII      |    30 |  1350 |     2   (0)| 00:00:01 |
|   2 |   INDEX FULL SCAN           | SYS_C005502 |    30 |       |     1   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

Aici s-a folosit o consultare fără filtru pentru index, dar se cere ca înregistrările să fie ordonate după cheia de index.

Consultarea cu Index Full Scan sau Index Fast Full Scan este foarte utilă când indexul conţine toate informaţiile cerute.

Exemplu:

select cod from sectii;

cu planul:

--------------------------------------------------------------------------------
| Id  | Operation        | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT |             |    30 |   120 |     1   (0)| 00:00:01 |
|   1 |  INDEX FULL SCAN | SYS_C005502 |    30 |   120 |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------

sau instrucţiunea:

select sectia,nrmatricol from studenti;

cu planul:

------------------------------------------------------------------------------------
| Id  | Operation            | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |             |  3186 | 41418 |     5   (0)| 00:00:01 |
|   1 |  INDEX FAST FULL SCAN| SYS_C005503 |  3186 | 41418 |     5   (0)| 00:00:01 |
------------------------------------------------------------------------------------

Operatorii Filter şi Access

Aşa după cum se vede şi în unul din exemplele precedente, la parcurgerea unui tabel se poate folosi operatorul (predicatul) filter. Acest lucru se observă şi în exemplul următor:

select * from discipline where substr(cod,1,2)='MI';

pentru care planul este:

--------------------------------------------------------------------------------
| Id  | Operation         | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |            |    14 |  1568 |     8   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| DISCIPLINE |    14 |  1568 |     8   (0)| 00:00:01 |
--------------------------------------------------------------------------------

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

   1 - filter(SUBSTR("COD",1,2)='MI')

Acest operator apare pe coloana ACCESS_PREDICATES din tabelul Plan_Table. In această coloană se precizează un eventual operator necesar pentru localizarea înregistrărilor.

Cu toate că tabelul folosit ca sursă de date are un index pentru "cod", acest index nu se foloseşte dacă la filtrare coloana de index se utilizează într-o expresie.
Pentru instrucţiunea:

select * from discipline where cod in (select distinct disciplina from rezultate where nota=10);

în planul generat se foloseşte operatorul filter şi operatorul access:

-----------------------------------------------------------------------------------
| Id  | Operation            | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT     |            |    62 |  7812 |    12   (9)| 00:00:01 |
|*  1 |  HASH JOIN RIGHT SEMI|            |    62 |  7812 |    12   (9)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL  | REZULTATE  |   173 |  2422 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL  | DISCIPLINE |  1430 |   156K|     8   (0)| 00:00:01 |
-----------------------------------------------------------------------------------

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

   1 - access("COD"="DISCIPLINA")
   2 - filter("NOTA"=10)

Acest plan este asemănător (nu este identic) cu planul pentru instrucţiunea:
select distinct d.* from rezultate inner join discipline d on cod=disciplina where nota=10;

---------------------------------------------------------------------------------
| Id  | Operation          | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |            |   173 | 27161 |    12   (9)| 00:00:01 |
|*  1 |  HASH JOIN         |            |   173 | 27161 |    12   (9)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| REZULTATE  |   173 |  7785 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| DISCIPLINE |  1430 |   156K|     8   (0)| 00:00:01 |
---------------------------------------------------------------------------------

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

   1 - access("DISCIPLINA"="COD")
   2 - filter("REZULTATE"."NOTA"=10)

Hash join

La evaluarea operatorului de join prin acest algoritm se procedează astfel:

Observaţii:
1. Eficienţa este mare dacă una din relaţii se poate memora în memoria internă.
2. Rezultate bune se obţin dacă dimensiunea unui bloc nu este mare

Pentru instrucţiunea:

select nume,prenume,denumire from studenti, sectii where studenti.sectia=sectii.cod;

sau:

select nume,prenume, denumire from studenti inner join sectii on studenti.sectia=sectii.cod;

sau:

select nume,prenume, denumire from sectii inner join studenti on studenti.sectia=sectii.cod;

se poate obţine planul:

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |  3186 |   395K|    14   (8)| 00:00:01 |
|*  1 |  HASH JOIN         |          |  3186 |   395K|    14   (8)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| SECTII   |    30 |  1500 |     2   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| STUDENTI |  3186 |   239K|    11   (0)| 00:00:01 |
-------------------------------------------------------------------------------

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

   1 - access("STUDENTI"."SECTIA"="SECTII"."COD")

Pentru instrucţiunea:

select nume,prenume,grupa 
  from studenti s inner join traiectorie t on s.sectia=t.sectia and s.nrmatricol=t.nrmatricol 
  where anuniv=2014;

se obţine planul:

Plan hash value: 3911260023

----------------------------------------------------------------------------------
| Id  | Operation          | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |             | 86784 |    11M|    22  (14)| 00:00:01 |
|*  1 |  HASH JOIN         |             | 86784 |    11M|    22  (14)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| TRAIECTORIE |  2094 |   102K|     8   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| STUDENTI    |  3186 |   276K|    11   (0)| 00:00:01 |
----------------------------------------------------------------------------------

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

   1 - access("S"."SECTIA"="T"."SECTIA" AND
              "S"."NRMATRICOL"="T"."NRMATRICOL")
   2 - filter("T"."ANUNIV"=2014)

Operaţia HASH JOIN poate apare cu diverse opţiuni, unele dintre ele vor fi descrise, pe exemple, în continuare.

Nested loops

Această metodă de evaluare a opertorului de join dintre două tabele este util dacă tabelele nu au dimensiune mare sau au un index util la operaţia de join.

Optimizatorul va alege ca sursă tabelul cu numărul minim de înregistrări.
Pentru instrucţiunea:

select * from dual a,dual b;

se obţine planul:

---------------------------------------------------------------------------
| Id  | Operation          | Name | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |      |     1 |     4 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS      |      |     1 |     4 |     4   (0)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| DUAL |     1 |     2 |     2   (0)| 00:00:01 |
---------------------------------------------------------------------------

Pentru următoarele două instrucţiuni:

select * from rezultate,discipline
where rezultate.disciplina=discipline.cod and anuniv=2014 and nota=10;

select * from (select * from rezultate where anuniv=2014 and nota=10) r,discipline
where r.disciplina=discipline.cod;

se obţine acelaşi plan:

--------------------------------------------------------------------------------------------
| Id  | Operation                    | Name        | Rows  | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT             |             |     1 |   232 |     4   (0)| 00:00:01 |
|   1 |  NESTED LOOPS                |             |     1 |   232 |     4   (0)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL          | REZULTATE   |     1 |   118 |     3   (0)| 00:00:01 |
|   3 |   TABLE ACCESS BY INDEX ROWID| DISCIPLINE  |     1 |   114 |     1   (0)| 00:00:01 |
|*  4 |    INDEX UNIQUE SCAN         | SYS_C005507 |     1 |       |     0   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

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

   2 - filter("ANUNIV"=2014 AND "NOTA"=10)
   4 - access("REZULTATE"."DISCIPLINA"="DISCIPLINE"."COD")

Dacă la ultima instrucţiune se schimbă constanta din filtru, atunci numărul de înregistrări consultate şi furnizate este diferit, deci e posibil să se schimbe şi planul de execuţie:

select * from (select * from rezultate where anuniv=2010 and nota=10) r,discipline
where r.disciplina=discipline.cod;

atunci planul generat este altul:

---------------------------------------------------------------------------------
| Id  | Operation          | Name       | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |            |   788 |   120K|    32   (4)| 00:00:01 |
|*  1 |  HASH JOIN         |            |   788 |   120K|    32   (4)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| REZULTATE  |   788 | 34672 |    22   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL| DISCIPLINE |  1430 |   156K|     9   (0)| 00:00:01 |
---------------------------------------------------------------------------------
 
Predicate Information (identified by operation id):
---------------------------------------------------
 
   1 - access("REZULTATE"."DISCIPLINA"="DISCIPLINE"."COD")
   2 - filter("NOTA"=10 AND "ANUNIV"=2010)

Merge join şi Sort join

Merge join este o metodă de evaluarea a operatorului de join între tabele prin parcurgerea următoarelor etape:

Deoarece operaţiile de sortare sunt costisitoare pentru surselor de date cu dimensiuni mari, acest tip de join nu este eficient în astfel de situaţii.

Exemplu:

select /*+USE_MERGE(s,t)*/ nume,prenume,grupa 
from studenti s inner join traiectorie t on s.sectia=t.sectia and s.nrmatricol=t.nrmatricol 
where anuniv=2014;

cu planul:

-------------------------------------------------------------------------------------------
| Id  | Operation           | Name        | Rows  | Bytes |TempSpc| Cost (%CPU)| Time     |
-------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |             |  2094 |   208K|       |    80   (4)| 00:00:01 |
|   1 |  MERGE JOIN         |             |  2094 |   208K|       |    80   (4)| 00:00:01 |
|   2 |   SORT JOIN         |             |  2094 | 60726 |       |     9  (12)| 00:00:01 |
|*  3 |    TABLE ACCESS FULL| TRAIECTORIE |  2094 | 60726 |       |     8   (0)| 00:00:01 |
|*  4 |   SORT JOIN         |             |  3186 |   227K|   568K|    71   (3)| 00:00:01 |
|   5 |    TABLE ACCESS FULL| STUDENTI    |  3186 |   227K|       |    11   (0)| 00:00:01 |
-------------------------------------------------------------------------------------------

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

   3 - filter("T"."ANUNIV"=2014)
   4 - access("S"."SECTIA"="T"."SECTIA")
       filter("S"."NRMATRICOL"="T"."NRMATRICOL" AND "S"."SECTIA"="T"."SECTIA")

Sort

Operatorul de sortare se foloseşte cu mai multe opţiuni (completări), dintre care amintim: Exemple:
select nume,prenume from studenti where sectia=43 order by 1,2;

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |    75 |  4875 |    14   (8)| 00:00:01 |
|   1 |  SORT ORDER BY     |          |    75 |  4875 |    14   (8)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| STUDENTI |    75 |  4875 |    13   (0)| 00:00:01 |
-------------------------------------------------------------------------------

select distinct nume,prenume from studenti where sectia=43 order by 1,2;

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |    75 |  4875 |    15  (14)| 00:00:01 |
|   1 |  SORT UNIQUE       |          |    75 |  4875 |    14   (8)| 00:00:01 |
|*  2 |   TABLE ACCESS FULL| STUDENTI |    75 |  4875 |    13   (0)| 00:00:01 |
-------------------------------------------------------------------------------

select nume,count(*) from studenti group by nume order by 1;

-------------------------------------------------------------------------------
| Id  | Operation          | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
-------------------------------------------------------------------------------
|   0 | SELECT STATEMENT   |          |  1762 | 54622 |    14   (8)| 00:00:01 |
|   1 |  SORT GROUP BY     |          |  1762 | 54622 |    14   (8)| 00:00:01 |
|   2 |   TABLE ACCESS FULL| STUDENTI |  3073 | 95263 |    13   (0)| 00:00:01 |
-------------------------------------------------------------------------------

select count(*) from studenti;

-----------------------------------------------------------------------------
| Id  | Operation             | Name        | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------
|   0 | SELECT STATEMENT      |             |     1 |     5   (0)| 00:00:01 |
|   1 |  SORT AGGREGATE       |             |     1 |            |          |
|   2 |   INDEX FAST FULL SCAN| SYS_C009499 |  3073 |     5   (0)| 00:00:01 |
-----------------------------------------------------------------------------