Comment télécharger un énorme Oracle LOB avec cx_Oracle sur un système à mémoire limitée ?
Question
Je développe une partie d'un système où les processus sont limités à environ 350 Mo de RAM ;nous utilisons cx_Oracle pour télécharger des fichiers à partir d'un système externe pour traitement.
Le système externe stocke les fichiers sous forme de BLOB, et nous pouvons les récupérer en faisant quelque chose comme ceci :
# ... set up Oracle connection, then
cursor.execute(u"""SELECT filename, data, filesize
FROM FILEDATA
WHERE ID = :id""", id=the_one_you_wanted)
filename, lob, filesize = cursor.fetchone()
with open(filename, "w") as the_file:
the_file.write(lob.read())
lob.read()
échouera évidemment avec MemoryError
lorsque nous atteignons un fichier de plus de 300 à 350 Mo, nous avons donc essayé quelque chose comme ceci au lieu de tout lire d'un coup :
read_size = 0
chunk_size = lob.getchunksize() * 100
while read_size < filesize:
data = lob.read(chunk_size, read_size + 1)
read_size += len(data)
the_file.write(data)
Malheureusement, nous obtenons toujours MemoryError
après plusieurs itérations.À partir du moment lob.read()
prend, et la condition de manque de mémoire que nous obtenons finalement, il semble que lob.read()
extrait ( chunk_size + read_size ) octets de la base de données à chaque fois.Autrement dit, les lectures prennent du temps O(n) et de la mémoire O(n), même si le tampon est un peu plus petit.
Pour contourner ce problème, nous avons essayé quelque chose comme :
read_size = 0
while read_size < filesize:
q = u'''SELECT dbms_lob.substr(data, 2000, %s)
FROM FILEDATA WHERE ID = :id''' % (read_bytes + 1)
cursor.execute(q, id=filedataid[0])
row = cursor.fetchone()
read_bytes += len(row[0])
the_file.write(row[0])
Cela extrait 2 000 octets (argh) à la fois et prend une éternité (quelque chose comme deux heures pour un fichier de 1,5 Go).Pourquoi 2000 octets ?Selon la documentation Oracle, dbms_lob.substr()
stocke sa valeur de retour dans un format RAW, limité à 2 000 octets.
Existe-t-il un moyen de stocker le dbms_lob.substr()
entraîne un objet de données plus volumineux et lit peut-être quelques mégaoctets à la fois ?Comment faire cela avec cx_Oracle ?
La solution
Je pense que l'ordre des arguments dans lob.read() est inversé dans votre code.Le premier argument doit être le décalage, le deuxième argument doit être le montant à lire.Cela expliquerait le temps O(n) et l'utilisation de la mémoire.