2011-12-08 11 views
6

Mi piacerebbe sapere il modo più efficiente in termini di memoria per estrarre campi di dati arbitrariamente grandi da un db Oracle con Perl DBI. Il metodo che so usare è impostare l'attributo 'LongReadLen' sull'handle del database su qualcosa di sufficientemente grande. Tuttavia, la mia applicazione ha bisogno di tirare diverse migliaia di record, quindi fare questo in modo arbitrario è estremamente inefficiente.Perl DBI alternativa a LongReadLen

Il doc suggerisce di eseguire una query in anticipo per trovare il valore potenziale più elevato e impostarlo.

$dbh->{LongReadLen} = $dbh->selectrow_array(qq{ 
    SELECT MAX(OCTET_LENGTH(long_column_name)) 
    FROM table WHERE ... 
}); 
$sth = $dbh->prepare(qq{ 
    SELECT long_column_name, ... FROM table WHERE ... 
}); 

Tuttavia, questo è ancora inefficiente, poiché i dati periferici non sono rappresentativi di ogni record. I valori più grandi sono superiori a un MB, ma il record medio è inferiore a un KB. Voglio essere in grado di estrarre tutta l'informazione (cioè senza troncamenti) mentre spreco il minor spazio possibile di memoria sui buffer inutilizzati.

Un metodo che ho considerato è quello di estrarre i dati in blocchi, ad esempio 50 record alla volta e impostare LongReadLen sulla lunghezza massima dei record di quel blocco. Un'altra soluzione, che potrebbe, ma non è necessario, basarsi sull'idea del blocco, sarebbe quella di eseguire il fork di un processo figlio, recuperare i dati e quindi uccidere il figlio (portando con sé la memoria sprecata). La cosa più meravigliosa sarebbe la possibilità di forzare i buffer DBI, ma non penso sia possibile.

Qualcuno ha affrontato un problema simile con un successo? Grazie per l'aiuto!

EDIT

Perl v5.8.8, DBI v1.52

Per chiarire: l'inefficienza di memoria è venuta da utilizzare 'LongReadLen' insieme {ora_pers_lob => 1} nel preparare. Utilizzando questo codice:

my $sql = "select myclob from my table where id = 68683"; 
my $dbh = DBI->connect("dbi:Oracle:$db", $user, $pass) or croak $DBI::errstr; 

print "before"; 
readline(*STDIN); 

$dbh->{'LongReadLen'} = 2 * 1024 * 1024; 
my $sth = $dbh->prepare($sql, {'ora_pers_lob' => 1}) or croak $dbh->errstr; 
$sth->execute() or croak('Cant execute_query '. $dbh->errstr . ' sql: ' . $sql); 
my $row = $sth->fetchrow_hashref; 

print "after"; 
readline(*STDIN); 

utilizzo della memoria residente "prima" è a 18MB e l'uso di "dopo" è a 30MB. Questo è inaccettabile per un numero elevato di query.

+0

hai pensato di farlo in modo "parallelo"?! – amrfaissal

+0

Cosa intendi per "memoria estremamente inefficiente"? Credo che qualsiasi imposta sulla memoria di LongReadLen sia _per istruzioni handle_, non per riga recuperata. (Cioè, il recupero di un migliaio di record con LOB non è _non una pena di memoria millimetrica.) Infatti, la misurazione di 'DBD :: Oracle' 1.36 con' Devel :: Size' suggerisce che un LOB "buffer di recupero" è tanto necessario per il più grande LOB recuperato finora. Quindi, se tenere un singolo buffer di grandi dimensioni è fastidioso, forse potresti semplicemente ORDINARE DI LUNGHEZZA (long_col) e lasciare che il DBI lo cresca come necessario. – pilcrow

+0

Ho aggiornato il post con ulteriori dettagli.Non ho menzionato il mio uso di ora_pers_lob; hai ragione che LongReadLen, da solo, non ha fame di memoria. L'utilizzo di LongReadLen senza {ora_pers_lob => 1} nella preparazione si traduceva in ORA-24812, che ho appena risolto. Quindi credo che la mia domanda abbia una risposta - Posso usare LongReadLen ora con il resto del mondo. –

risposta

5

Le colonne sono con LOB di grandi dimensioni (CLOB o BLOB)? In tal caso, non è necessario utilizzare LongReadLen; DBD :: Oracle fornisce un'interfaccia di streaming LOB.

Quello che si vuole fare è bind the param come tipo ORA_CLOB o ORA_BLOB, che otterrà un "localizzatore LOB" restituito dalla query, anziché tex. Quindi si utilizza ora_lob_read insieme al localizzatore LOB per ottenere i dati. Ecco un esempio di codice che ha funzionato per me:

sub read_lob { 
    my ($dbh, $clob) = @_; 

    my $BLOCK_SIZE = 16384; 

    my $out; 
    my $offset = 1; 

    while (my $data = $dbh->ora_lob_read($clob, $offset, $BLOCK_SIZE)) { 
    $out .= $data; 
    $offset += $BLOCK_SIZE; 
    } 
    return $out; 
} 
+1

+1 per l'API speciale LOB. Ma non restituirà $ out' copia di quello che potrebbe essere uno scalare molto grande? – pilcrow

+0

Sì, ma almeno è usato solo quando necessario. E, naturalmente, se è possibile elaborare i dati in modalità streaming, è possibile farlo al posto di '$ out. = $ Data'. – hobbs

+0

Giusto. Se l'uso della memoria è un problema, lo streaming su disco o altre elaborazioni sarebbe un buon approccio. (È anche possibile restituire '\ $ out' per evitare la copia.) – pilcrow

0

ci penso in questo modo:

use Parallel::ForkManager 
use strict; 

# Max 50 processes for parallel data retrieving 
my $pm = new Parallel::ForkManager(50); 

# while loop goes here 
while (my @row = $sth->fetchrow_array) { 

# do the fork 
$pm->start and next; 

# 
# Data retreiving goes here 
# 

# do the exit in the child process 
$pm->finish; 
} 
$pm->wait_all_children; 

controllo Parallel::ForkManager in CPAN di saperne di più.

+0

Grazie per il suggerimento. Se non riesco a risolvere il mio problema ORA-24812, probabilmente prenderò questa strada. –