Canalblog
Editer l'article Suivre ce blog Administration + Créer mon blog
Publicité
Blog d'un DBA sur le SGBD Oracle et SQL
31 janvier 2020

Toujours lire les data blocs depuis le disque dur sans vider le Buffer Cache - Always read data blocks from the hard drive

      

Introduction
C'est article fait suite à "Comment vider des données en Shared Pool ?" : http://dbaoraclesql.canalblog.com/archives/2020/01/20/37957327.html
sauf que cette fois on va s'intéresser au Database Buffer Cache.

Parfois on veut que Oracle lise systématiquement les blocs d'une table non pas depuis le Database Buffer Cache (s'ils sont présents) mais depuis le disque dur.

Quel est l'intérêt? Tester les débits en lecture du disque dur par exemple! Cela arrive quand on a tuné un SELECT dans la base, qu'on estime ne plus rien pouvoir faire au niveau Oracle et que donc il faut maintenant tuner le hardware, notamment les disques durs avec l'utilitaire Linux Hdparm.

Le problème est que si les blocs sont lus depuis le disque dur lors du premier SELECT, ils sont placés en SGA et les prochains SELECTs les liront dans le Buffer Cache. Donc le tuning des disques ne pourra pas être testé...

Comment faire? 


 

Points d'attention
Ne jamais vider le Database Buffer Cache en production sous peine de fortement ralentir les traitements.
 


 
Base de tests
Une base Oracle 18c.


 
Exemples

============================================================================================
Méthode 1 : flusher le Database Buffer Cache 

============================================================================================
Une solution brutale est de vider TOUT le Database Buffer Cache avec la commande FLUSH. Problème, cela va ralentir les SELECTs à venir de tous les users puisque Oracle va devoir relire les données depuis le disque. 

On utilise Autotrace pour voir les lectures physiques et logiques.
     SQL> set autotrace on

Ma base vient de démarrer : le SELECT lit les données sur disque dur.
     SQL> select * from zztest;
     Statistics
     ----------------------------------------------------------
     142 recursive calls
     4 db block gets
     1856 consistent gets
     381 physical reads
     0 redo size
     2958855 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     8 sorts (memory)
     0 sorts (disk)
     20000 rows processed

On relance le SELECT : il n'y a que des lectures en SGA.

     SQL> select * from zztest;
     20000 rows selected.

     SQL> select * from zztest;
     Plan hash value: 3582063246
     ----------------------------------------------------------------------------
     | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
     ----------------------------------------------------------------------------
     | 0 | SELECT STATEMENT | | 20000 | 2441K| 101 (0)| 00:00:01 |
     | 1 | TABLE ACCESS FULL| ZZTEST | 20000 | 2441K| 101 (0)| 00:00:01 |
     ----------------------------------------------------------------------------

     Statistics
     ----------------------------------------------------------
     235 recursive calls
     4 db block gets
     2048 consistent gets
     0 physical reads
     0 redo size
     2958855 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     14 sorts (memory)
     0 sorts (disk)
     20000 rows processed

Si on flush le buffer cache, on revoit les lectures sur le disque dur avec "381 physical reads". Problème, cela concerne TOUTES les tables, donc on ralentit tous les SELECTs et tous les utilisateurs. Le résultat est OK mais ce n'est pas ce que je veux!
     SQL> alter system flush buffer_cache;
     System altered.

     SQL> select * from zztest;
     Statistics
     ----------------------------------------------------------
     142 recursive calls
     4 db block gets
     1856 consistent gets
     381 physical reads
     0 redo size
     2958855 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     8 sorts (memory)
     0 sorts (disk)
     20000 rows processed
     

============================================================================================
Méthode 2 : le Direct Path Read
============================================================================================
Quand je tune un ordre, mon objectif est de faire de la micro-chirurgie, mes tests doivent impacter au strict minimum voir pas du tout les autres users. C'est pourquoi le flush du Buffer Cache n'est pas une bonne solution.

Donc comment lire TOUJOURS les données depuis le disque dur sans flusher le Buffer Cache? 

La solution est de provoquer un Direct Path Read!
Que dit la doc Oracle? Qu'il s'agit de la lecture physique de blocs et que ceux-ci sont mis en cache non pas dans la SGA mais dans la PGA. "A direct read is a physical I/O from a data file that bypasses the buffer cache and reads the data block directly into process-private memory".

A noter qu'il n'existe pas de hint pour forcer un "direct path read", que ce hint soit documenté ou non.
     SQL> select name from v$sql_hint where UPPER(name) like '%DIRECT%' or UPPER(name) like '%PATH%' or UPPER(name) like '%READ%' order by 1;
    name 
    ------
    NO_XDB_FASTPATH_INSERT
    VECTOR_READ
    VECTOR_READ_TRACE
    XDB_FASTPATH_INSERT

Le hint PARALLEL seul ne suffit pas
On peut utiliser le hint PARALLEL (avec un seul R et deux L) car celui-ci force ce qu'on appelle le "direct path read".

Le plan d'éxcution a bien changé mais aucune lecture physique... Internet aurait-il menti? Ce serait bien la première fois :-)
     SQL> select /*+ PARALLEL */ * from zztest;
     Plan hash value: 190125610
     --------------------------------------------------------------------------------------------------------------
     | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
     --------------------------------------------------------------------------------------------------------------
     | 0 | SELECT STATEMENT | | 20000 | 2441K| 56 (0)| 00:00:01 | | | |
     | 1 | PX COORDINATOR | | | | | | | | |
     | 2 | PX SEND QC (RANDOM)| :TQ10000 | 20000 | 2441K| 56 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
     | 3 | PX BLOCK ITERATOR | | 20000 | 2441K| 56 (0)| 00:00:01 | Q1,00 | PCWC | |
     | 4 | TABLE ACCESS FULL| ZZTEST | 20000 | 2441K| 56 (0)| 00:00:01 | Q1,00 | PCWP | |
     --------------------------------------------------------------------------------------------------------------

     Note
     -----
     - automatic DOP: Computed Degree of Parallelism is 2

     Statistics
     ----------------------------------------------------------
     8 recursive calls
     113 db block gets
     680 consistent gets
     0 physical reads
     0 redo size
     1312850 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     0 sorts (memory)
     0 sorts (disk)
     20000 rows processed

Transformer ma table en grosse table avec le paramètre _small_table_threshold
Visiblement le hint PARALLEL n'est pas suffisant...

En cherchant mieux sur le net j'ai trouvé cela : 
"The hidden parameter "_small_table_threshold" defines the number of blocks to consider a table as being "small". If size of the table or segment > 5 * _small_table_threshold or 10% of buffer cache, then the table/segment is considered Large table and always uses Direct Path Read."

En clair, il faut que je trompe Oracle pour lui faire croire que ma table est une grosse table en disant que celle-ci dépasse de 5 fois la valeur du paramètre _small_table_threshold (c'est plus simple que de travailler sur les 10% du Buffer cache).

Attention, seul SYS peut requêter les structures X$ et récupérer la valeur de ce paramètre.
     SQL> select ksppstvl
     from x$ksppi x, x$ksppcv y
     where (x.indx = y.indx)
     and ksppinm='_small_table_threshold';
     from x$ksppi x, x$ksppcv y
     *
     ERROR at line 2:
     ORA-00942: table or view does not exist

     SQL> connect SYS as sysdba
     Enter password:
     Connected.

     SQL> select ksppstvl     
     from x$ksppi x, x$ksppcv y
     where (x.indx = y.indx)
     and ksppinm='_small_table_threshold';
     KSPPSTVL
     -----------
     1171

La taille en blocs de ma table est près de 400 comme vu ci-dessus... On va donc mettre le paramètre à 50 blocs ==> 5*50 = 250 pour transformer ma petite table en GROSSE table.

Attention, c'est un paramètre caché donc il faut contacter le support avant de modifier celui-ci en production. MAIS comme cette modification est là pour du tuning sur une base de recette, vous ne devriez pas le modifier en prod.

On se reconnecte avec le user de tests.
     [oracle@vbgeneric ~]$ sqlplus HR
     Enter password:

     SQL> alter session set "_small_table_threshold" = 50;
     Session altered.

     SQL> show parameter _small_table_threshold
     NAME TYPE VALUE
     ------------------------------------ ----------- ------------------------------
     _small_table_threshold integer 50

Héhé, ça marche :-)
     SQL> select /*+ PARALLEL */ * from zztest;
     Plan hash value: 190125610
     --------------------------------------------------------------------------------------------------------------
     | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | TQ |IN-OUT| PQ Distrib |
     --------------------------------------------------------------------------------------------------------------
     | 0 | SELECT STATEMENT | | 20000 | 2441K| 56 (0)| 00:00:01 | | | |
     | 1 | PX COORDINATOR | | | | | | | | |
     | 2 | PX SEND QC (RANDOM)| :TQ10000 | 20000 | 2441K| 56 (0)| 00:00:01 | Q1,00 | P->S | QC (RAND) |
     | 3 | PX BLOCK ITERATOR | | 20000 | 2441K| 56 (0)| 00:00:01 | Q1,00 | PCWC | |
     | 4 | TABLE ACCESS FULL| ZZTEST | 20000 | 2441K| 56 (0)| 00:00:01 | Q1,00 | PCWP | |
     --------------------------------------------------------------------------------------------------------------

     Note
     -----
     - automatic DOP: Computed Degree of Parallelism is 2

     Statistics
     ----------------------------------------------------------
     171 recursive calls
     61 db block gets
     680 consistent gets
     356 physical reads
     0 redo size
     1313035 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     6 sorts (memory)
     0 sorts (disk)
     20000 rows processed

Une deuxième fois? C'est encore OK :-) Youppppppeeeeee!
     SQL> select /*+ PARALLEL */ * from zztest;
     Statistics
     ----------------------------------------------------------
     6 recursive calls
     57 db block gets
     524 consistent gets
     353 physical reads
     0 redo size
     1312922 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     0 sorts (memory)
     0 sorts (disk)
     20000 rows processed

A noter que sans le hint PARALLEL, cela ne fonctionne pas : les données sont lues depuis la SGA. C'est donc bien le parallélisme qui provoque le Direct Path Read.
     SQL> select * from zztest;
     Plan hash value: 3582063246
     ----------------------------------------------------------------------------
     | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
     ----------------------------------------------------------------------------
     | 0 | SELECT STATEMENT | | 20000 | 2441K| 101 (0)| 00:00:01 |
     | 1 | TABLE ACCESS FULL| ZZTEST | 20000 | 2441K| 101 (0)| 00:00:01 |
     ----------------------------------------------------------------------------

     Statistics
     ----------------------------------------------------------
     57 recursive calls
     6 db block gets
     1808 consistent gets
     0 physical reads
     0 redo size
     2958855 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     3 sorts (memory)
     0 sorts (disk)
     20000 rows processed

Les autres sessions ne sont pas impactées
Dernier test : je me connecte comme un autre user pour voir si mes modifications n'impactent vraiment que ma session. 

Oui, dans ce cas, les données sont bien lues en SGA et pas sur disque dur.

     [oracle@vbgeneric ~]$ sqlplus HR2
     Enter password:

     SQL> set autotrace on

     SQL> select * from HR.zztest;
     Execution Plan
     ----------------------------------------------------------
     Plan hash value: 3582063246
     ----------------------------------------------------------------------------
     | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
     ----------------------------------------------------------------------------
     | 0 | SELECT STATEMENT | | 20000 | 2441K| 101 (0)| 00:00:01 |
     | 1 | TABLE ACCESS FULL| ZZTEST | 20000 | 2441K| 101 (0)| 00:00:01 |
     ----------------------------------------------------------------------------
     
     Statistics

     ----------------------------------------------------------
     215 recursive calls
     4 db block gets
     1955 consistent gets
     0 physical reads
     0 redo size
     2958855 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     6 sorts (memory)
     0 sorts (disk)
     20000 rows processed

Vider sous Linux le cache du disque dur
ATTENTION, il y a un piège au niveau Linux! Sur le net il est précisé qu'il faut vider le cache disque car, la table étant petite, le résultat tient dans ce cache...

Je lance une commande de flush du cache disque (en étant root car il faut certains droits pour cette commande).
     [oracle@vbgeneric ~]$ su - root
     Password:
     Last login: Fri Oct 4 10:40:29 EDT 2019 on pts/0

     [root@vbgeneric ~]# echo 3 > /proc/sys/vm/drop_caches

Pas de changement au niveau Oracle, j'ai les même stats mais avec l'Autotrace on n'a pas le temps d'exécution de la requête.
     SQL> select /*+ PARALLEL */ * from zztest;
     Statistics
     ----------------------------------------------------------
     6 recursive calls
     57 db block gets
     524 consistent gets
     353 physical reads
     0 redo size
     1311991 bytes sent via SQL*Net to client
     15270 bytes received via SQL*Net from client
     1335 SQL*Net roundtrips to/from client
     0 sorts (memory)
     0 sorts (disk)
     20000 rows processed



Conclusion

Il faut deux conditions pour forcer systématiquement les lectures de blocs depuis le disque dur plutôt que depuis la SGA :
- le hint PARALLEL
- le paramètre _small_table_threshold avec une valeur telle que ma table devienne une grosse table


 

Publicité
Publicité
Commentaires
Blog d'un DBA sur le SGBD Oracle et SQL
Publicité
Archives
Blog d'un DBA sur le SGBD Oracle et SQL
  • Blog d'un administrateur de bases de données Oracle sur le SGBD Oracle et sur les langages SQL et PL/SQL. Mon objectif est de vous faire découvrir des subtilités de ce logiciel, des astuces, voir même des surprises :-)
  • Accueil du blog
  • Créer un blog avec CanalBlog
Visiteurs
Depuis la création 340 769
Publicité