Oracle 12c: top n query e default

Questo post nasce da un lungo giro, come mi è capitato più volte in passato; ieri approfittando di un momento di calma ho dato una occhiata ai miei “feed”, strumento che ormai pare obsoleto e rimpiazzato per i più (ma non da me) da twitter che però non ritengo possa rimpiazzare come funzionalità l’accopiata blog+feed, direi anzi che twitter va verso la direzione di creare confusione e far perdere informazioni preziose… ma sto divagando. Ieri quindi ho dato una letta a qualche articolo sui blog che seguo ormai da anni e che parlano di database Oracle, in particolare leggevo gli ultimi post di Jonathan Lewis, che rappresenta ancora una preziosissima risorsa. Finito di leggere un post l’occhio mi è caduto sul blogroll che in testa riporta questo interessante link; si tratta di una pagina in cui a sua volta sono raggruppati un sacco di link a fonti di informazione sul nuovo Oracle 12c. Scorrendo i link mi sono soffermato su questo, non tanto per l’argomento quanto per l’autore che mi era un nome noto. Arrivato al post di Connor Mcdonald l’argomento mi ha incuriosito e sono andato alla sua fonte, un articolo di Tom Kyte. Come si vede un giro abbastanza lungo (almeno per il livello medio di molti utilizzatori di internet); comunque sull’articolo di Tom Kyte ho deciso di approfondire. Ho così rispolverato la macchina virtuale con Oracle 12c, mi ci è voluto un attimo per riorientarmi e trovare il container, creare un utente (e prima una tablespace dedicata), poi sono riuscito a ripetere gli esempi mostrati, prima sui Default sui valori delle colonne nelle tabelle, dove Oracle ha sicuramente colmato una lacuna, permettendo di definire come valore di default per una colonna il numero generato da una sequence, creata esplicitamente e separatamente o implicitamente e legata alla tabella con la specifica “generated as [default] identity“. Un po’ più bizzarra la funzionalità “default on NULL” che però evidentemente per molti può risultare utile. Non ho testato la miglioria sull’aggiunta di colonne con valori di default, ma sembra anche essa una grande cosa (ho passato brutti momenti in passato con l’aggiunta di colonne con valore di default a tabelle molto grandi).

Infine l’argomento “top n query“: anche questo mi è  parso interessante, finché non ho notato una differenza fra le mie prove e quanto mostrato da Tom. Ho ripetuto le prove anche su una precedente installazione di Oracle 12c in cui ho installato Enterprise Edition (mentre in ambiente virtuale ho installato una standard edition per fare prove sulla “Multitenant Architecture“) e il risultato è diverso da quello mostrato di Tom, a livello di piano di esecuzione, ecco i miei risultati:

CRISTIAN@10.110.3.52/salespdb > create table t2 as select * from all_objects;

Table created.

CRISTIAN@10.110.3.52/salespdb > create index idx_t2 on t2(owner,object_name);

Index created.

CRISTIAN@10.110.3.52/salespdb > select owner,object_name,object_id from t2
 2 order by owner,object_name fetch first 5 rows only;

OWNER OBJECT_NAME OBJECT_ID
----------------------------- ------------------------------ ----------
APEX_040200 APEX 87057
APEX_040200 APEXWS 86726
APEX_040200 APEX_ADMIN 88996
APEX_040200 APEX_APPLICATIONS 87697
APEX_040200 APEX_APPLICATION_ALL_AUTH 87765

Elapsed: 00:00:00.07

Execution Plan
----------------------------------------------------------
Plan hash value: 3975347511

-----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes |TempSpc| Cost (%CPU)| Time |
-----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 76285 | 21M| | 1101 (1)| 00:00:01 |
|* 1 | VIEW | | 76285 | 21M| | 1101 (1)| 00:00:01 |
|* 2 | WINDOW SORT PUSHED RANK| | 76285 | 2756K| 3600K| 1101 (1)| 00:00:01 |
| 3 | TABLE ACCESS FULL | T2 | 76285 | 2756K| | 362 (1)| 00:00:01 |
-----------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=5)
 2 - filter(ROW_NUMBER() OVER ( ORDER BY "OWNER","OBJECT_NAME")<=5)


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

La cosa che mi “perprime” è che con la classica tecnica del ROWNUM ho questo:


CRISTIAN@10.110.3.52/salespdb > select * from ( select owner,object_name,object_id from t2
 2 order by owner,object_name) where rownum<=5;

OWNER OBJECT_NAME OBJECT_ID
----------------------------- ------------------------------ ----------
APEX_040200 APEX 87057
APEX_040200 APEXWS 86726
APEX_040200 APEX_ADMIN 88996
APEX_040200 APEX_APPLICATIONS 87697
APEX_040200 APEX_APPLICATION_ALL_AUTH 87765

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 2015243804

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 725 | 8 (0)| 00:00:01 |
|* 1 | COUNT STOPKEY | | | | | |
| 2 | VIEW | | 5 | 725 | 8 (0)| 00:00:01 |
| 3 | TABLE ACCESS BY INDEX ROWID| T2 | 76285 | 2756K| 8 (0)| 00:00:01 |
| 4 | INDEX FULL SCAN | IDX_T2 | 5 | | 3 (0)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - filter(ROWNUM<=5)


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

Nel mio caso non usa l’indice, mi è venuto lo scrupolo di provare a vedere le statistiche e il costo del piano usando un hint per forzare l’uso dell’indice:


CRISTIAN@10.110.3.52/salespdb > select /*+ INDEX(T2 IDX_T2) */owner,object_name,object_id from t2
 2 order by owner,object_name fetch first 5 rows only;

OWNER OBJECT_NAME OBJECT_ID
----------------------------- ------------------------------ ----------
APEX_040200 APEX 87057
APEX_040200 APEXWS 86726
APEX_040200 APEX_ADMIN 88996
APEX_040200 APEX_APPLICATIONS 87697
APEX_040200 APEX_APPLICATION_ALL_AUTH 87765

Elapsed: 00:00:00.02

Execution Plan
----------------------------------------------------------
Plan hash value: 2588503356

----------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 76285 | 21M| 62581 (1)| 00:00:03 |
|* 1 | VIEW | | 76285 | 21M| 62581 (1)| 00:00:03 |
|* 2 | WINDOW NOSORT STOPKEY | | 76285 | 2756K| 62581 (1)| 00:00:03 |
| 3 | TABLE ACCESS BY INDEX ROWID| T2 | 76285 | 2756K| 62581 (1)| 00:00:03 |
| 4 | INDEX FULL SCAN | IDX_T2 | 76285 | | 454 (1)| 00:00:01 |
----------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=5)
 2 - filter(ROW_NUMBER() OVER ( ORDER BY "OWNER","OBJECT_NAME")<=5)


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

Le statistiche sono migliorate, ma il costo indicato per il  piano di esecuzione è elevato. Come spiega bene Christian Antognini sul suo libro “Troubleshooting Oracle Performance” (e come la mia esperienza conferma) piani di esecuzione non ottimali molto spesso sono generati da previsioni sbagliate, infatti (ma si intravedeva gia prima):

</pre>
CRISTIAN@10.110.3.52/salespdb > select /*+ gather_plan_statistics */ owner,object_name,object_id from t2
 2 order by owner,object_name fetch first 5 rows only;

OWNER OBJECT_NAME OBJECT_ID
----------------------------- ------------------------------ ----------
APEX_040200 APEX 87057
APEX_040200 APEXWS 86726
APEX_040200 APEX_ADMIN 88996
APEX_040200 APEX_APPLICATIONS 87697
APEX_040200 APEX_APPLICATION_ALL_AUTH 87765

Elapsed: 00:00:00.07
CRISTIAN@10.110.3.52/salespdb > select plan_table_output from table(dbms_xplan.display_cursor(null, null, 'allstats last'));

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID g467gxjwprt0g, child number 0
-------------------------------------
select /*+ gather_plan_statistics */ owner,object_name,object_id from
t2 order by owner,object_name fetch first 5 rows only

Plan hash value: 3975347511

----------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | OMem | 1Mem | Used-Mem |
----------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 5 |00:00:00.04 | 1300 | | | |
|* 1 | VIEW | | 1 | 76285 | 5 |00:00:00.04 | 1300 | | | |
|* 2 | WINDOW SORT PUSHED RANK| | 1 | 76285 | 5 |00:00:00.04 | 1300 | 4096 | 4096 | 4096 (0)|
| 3 | TABLE ACCESS FULL | T2 | 1 | 76285 | 76285 |00:00:00.13 | 1300 | | | |
----------------------------------------------------------------------------------------------------------------------

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

1 - filter("from$_subquery$_002"."rowlimit_$$_rownumber"<=5)
 2 - filter(ROW_NUMBER() OVER ( ORDER BY "OWNER","OBJECT_NAME")<=5)
22 rows selected.

Elapsed: 00:00:00.06
<pre>

Cioè nel mio caso oracle non si aspetta 5 righe ma tutte le 76285 presenti nella tabella, nonostante il fatto che sia scritto nella query che il risultato conterrà 5 righe. Perché a me non funziona come dovrebbe quindi non è ancora chiaro, ho provato a ricalcolare statistiche a cambiare client (oltre che server) ma non ho visto miglioramenti, un tema da indagare.

P.S.

Attenzione che sulla funzionalità FETCH..OFFSET è gia stato rilevato un baco, se ne parla qui, io ho effettuato lo stesso test riportato da Connor Mcdonald nel primo commento con lo stesso risultato.

 

 

 

4 pensieri su “Oracle 12c: top n query e default

      1. Sì confermo i tuoi test….in effetti anche a me da lo stesso risultato.
        Ho verificato anche nella pagina con i test eseguiti da Tom ma i risultati non tornano comunque.Nella query mostrata (uguale alla nostra) il CBO utilizza l’indice, nel nostro caso no, e quindi sono diversi anche i piani.
        Anch’io ho aggiornato le statistiche più volte ma non sembra essere risolutivo. L’articolo tra l’altro è abbastanza sintetico, perchè non ci dice le impostazioni del DB, o almeno dei parametri del CBO, che sembrerebbero non standard altrimenti riusciremo a riprodurlo anche noi.
        Continuerò ad indagare intanto.
        Ciao
        Alberto

Lascia un commento