Nascondere i parametri di connessione con Perl DBI

In un’applicazione Perl, usando il noto modulo DBI, la connessione a un database avviene tramite un’istruzione come questa:


my $dbh = DBI->connect("DBI:mysql:test",
"utente", "password") or die "messaggio di errore";

Il primo parametro è obbligatorio, ed è composto da “dbi”, l’identificativo del modulo, seguito dal driver del database, nel nostro caso “mysql” e il nome del database, in questo esempio “test”. Per un database che si rispetti, occorre anche specificare il nome dell’utente e la password.

Niente da dire se questo script sta al sicuro nella vostra directory, protetta da occhi indiscreti.

Ma cosa succede se dovete distribuirlo a qualcuno per dimostrare qualcosa? Bisogna ricordarsi di cancellare nome utente e password, per evitare il fastidio di doverli poi cambiare nel database se sbadatamente abbiamo distribuito utente e password con l’esempio.

Oppure, se abbiamo un’applicazione CGI, nonostante le protezioni di sistema, il nostro script potrebbe essere sempre alla mercè degli occhi indiscreti di altri utenti che hanno accesso concorrente alla stessa applicazione.

Fortunatamente, esiste una soluzione che nasconde l’accesso alle variabili di connessione, prendendole da un file di configurazione, che può essere tenuto in una directory accessibile solo all’utente che deve eseguire l’applicazione, ma invisibile agli altri.

Funziona così: nella directory dell’utente interessato si costruisce un file di nome “.my.cnf” che contenga queste righe:


[mysql]
user=nomeutente
password=ilmiosegreto

Il file va protetto con gli opportuni privilegi ( chmod 600 .my.cnf) in modo che non possa essere letto da altri utenti. Quindi, si modifica l’istruzione di connessione così:


my $dbh = DBI->connect("DBI:mysql:test"
. ";mysql_read_default_file=$ENV{HOME}/.my.cnf",
undef, undef) or die "messaggio di errore";

L’opzione mysql_read_default_file indica il file da cui prendere le opzioni di connessione. Notate che al posto di nome utente e password ci sono due valori nulli (undef). Il nome del file, in questo caso, viene preso dalla variabile d’ambiente “$HOME” (nei sistemi Unix), ma si può anche indicare il nome espressamente. Questo accorgimento fa sì che lo script possa essere eseguito da più utenti, ciascuno con il suo nome utente e password, senza modificare nemmeno una riga.

L’indice fantasma

Forse non tutti sanno che, per ogni query, MySQL utilizza un solo indice per ogni tabella. Ciò significa che in certi casi il motore risponde più lentamente di quanto ci aspettiamo.

Per esempio, supponiamo di voler elencare tutti gli impiegati che appartengano a un dipartimento specifico o che lavorino in una data località. La ricerca solo per dipartimento (clausola WHERE nel campo id_dip) usa un indice, ed esegue il suo compito efficacemente.

Lo stesso accade se vogliamo cercare per località (indice id_loc). Ma quando proviamo a combinare le due ricerche, MySQL ci annuncia che non riesce a utilizzare nessun indice.


mysql> explain SELECT * FROM persone WHERE id_dip = 3 OR id_loc = 3;
+-------------+------+----------------+------+---------+------+------+-------------+
| table | type | possible_keys | key | key_len | ref | rows | Extra |
+-------------+------+----------------+------+---------+------+------+-------------+
| persone | ALL | id_dip, id_loc | NULL | NULL | NULL | 2200 | Using where |
+-------------+------+----------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)

Se la nostra tabella ha milioni di record, questo tipo di ricerca significa avere un “full table scan”, cioè una lettura sequenziale di tutta la tabella. Il fatto è che quando MySQL analizza la query, tenta di trovare un indice che possa soddisfare entrambe le condizione in WHERE e, siccome gli indici possibili risolvono solo metà delle condizioni, costringendo a una lettura sequenziale per risolvere la seconda parte, il motore decide che è più conveniente abbandonare gli indici del tutto ed eseguire la lettura completa già in partenza.

Ma non ci scoraggiamo. Esiste una semplice soluzione che, tenendo conto della limitazione di un indice per ogni tabella, risolve l’impasse felicemente.


mysql> explain SELECT * FROM persone WHERE id_dip = 3
UNION
SELECT * FROM persone WHERE id_loc = 3;
+---------+------+---------------+---------+---------+-------+------+-------------+
| table | type | possible_keys | key | key_len | ref | rows | Extra |
+---------+------+---------------+---------+---------+-------+------+-------------+
| persone | ref | id_dip | id_dip | 4 | const | 134 | Using where |
| persone | ref | id_loc | id_loc | 4 | const | 21 | Using where |
+---------+------+---------------+---------+---------+-------+------+-------------+
2 rows in set (0.00 sec)

Usando una UNION, le query diventano due, consentendo a MySQL di usare un indice per ciascuna. Il risultato è che non viene più effettuata una lettura sequenziale della tabella, sostituita da due letture a indice. Per tabelle di grosse dimensioni il guadagno in termini di efficienza può talvolta misurarsi in minuti!