2013-06-19 3 views
11

Ich führe eine große Abfrage in einem Python-Skript gegen meine Postgres-Datenbank mit psycopg2 (Ich habe auf Version 2.5 aktualisiert). Nachdem die Abfrage beendet ist, schließe ich den Cursor und die Verbindung, und sogar gc, aber der Prozess verbraucht immer noch eine Tonne Speicher (7,3 gb um genau zu sein). Fehle ich einen Reinigungsschritt?psycopg2 undichtem Speicher nach großer Abfrage

import psycopg2 
conn = psycopg2.connect("dbname='dbname' user='user' host='host'") 
cursor = conn.cursor() 
cursor.execute("""large query""") 
rows = cursor.fetchall() 
del rows 
cursor.close() 
conn.close() 
import gc 
gc.collect() 

Antwort

8

Bitte beachten Sie die nächste Antwort durch die bessere Lösung @joeblog.


Zuerst sollten Sie nicht alle RAM an erster Stelle benötigen. Was Sie hier tun sollten, ist das Holen Chunks der Ergebnismenge. Tun Sie keine fetchall(). Verwenden Sie stattdessen die viel effizientere Methode cursor.fetchmany. Siehe the psycopg2 documentation.

Nun, die Erklärung dafür, warum es nicht freigegeben wird, und warum das kein Speicherverlust in der formal korrekten Verwendung dieses Begriffs ist.

Die meisten Prozesse geben Speicher nicht an das Betriebssystem zurück, wenn es freigegeben wird, sie stellen es nur für die Wiederverwendung an anderer Stelle im Programm zur Verfügung.

Speicher kann nur dann an das Betriebssystem freigegeben werden, wenn das Programm die verbleibenden im Speicher verstreuten Objekte komprimieren kann. Dies ist nur möglich, wenn indirekte Handle-Verweise verwendet werden, da sonst das Verschieben eines Objekts bestehende Zeiger auf das Objekt ungültig machen würde. Indirekte Referenzen sind ziemlich ineffizient, insbesondere bei modernen CPUs, bei denen die Verfolgung von Zeigern schreckliche Dinge an die Leistung bringt.

Was normalerweise passiert, wenn das Programm keine besondere Vorsicht walten lässt, ist, dass jeder große Speicherplatz, der mit brk() belegt ist, mit ein paar kleinen Stücken belegt ist.

Das Betriebssystem kann nicht feststellen, ob das Programm diesen Speicher noch verwendet oder nicht verwendet, so dass er es nicht einfach zurückfordern kann. Da das Programm nicht dazu tendiert, auf den Speicher zuzugreifen, tauscht das Betriebssystem es normalerweise im Laufe der Zeit aus und gibt den physischen Speicher für andere Zwecke frei. Dies ist einer der Gründe, warum Sie Swap Space haben sollten.

Es ist möglich, Programme zu schreiben, die Speicher an das Betriebssystem zurückgeben, aber ich bin mir nicht sicher, ob Sie das mit Python machen können. auch

Siehe:

Also: Das ist nicht wirklich ein Speicher Leck. Wenn Sie etwas anderes machen, das viel Speicher verwendet, sollte der Prozess nicht viel wachsen, wenn überhaupt, er wird den zuvor freigegebenen Speicher der letzten großen Zuweisung wiederverwenden.

+0

Danke! Bestätigt, dass Speicher erneut verwendet wird, indem der obige Code zweimal im selben Prozess ausgeführt wird. Der Speicher stieg während des zweiten Laufs nicht an. –

+3

Während hier alles richtig ist, wird ein Abfrageergebnis normalerweise vollständig auf der Clientseite übertragen (nicht durch 'fetch *()', sondern durch 'execute()'). Wenn also 'fetchmany()' anstelle von 'fetchall()' verwendet wird, kann beim Erstellen von Python-Objekten etwas Speicher gespart werden. Die Verwendung eines serverseitigen Cursors, wie von @joeblog vorgeschlagen, ist die richtige Lösung. – piro

27

Ich stieß auf ein ähnliches Problem und nach ein paar Stunden von Blut, Schweiß und Tränen, fand die Antwort einfach die Zugabe von einem Parameter.

Statt

cursor = conn.cursor() 

Schreib

cursor = conn.cursor(name="my_cursor_name") 

oder einfacher noch

cursor = conn.cursor("my_cursor_name") 

Die Details bei http://initd.org/psycopg/docs/usage.html#server-side-cursors

gefunden fand ich die Anweisung ein wenig verwirrend in dem ich obwohl ich meine SQL umschreiben müsste, um "DECLARE my_cursor_name ...." und dann ein "FETCH zählen 2000 FROM my_cursor_name" aber es stellt sich heraus, dass psycopg das alles für Sie unter der Haube tut, wenn Sie überschreiben einfach den Standardparameter "name = None", wenn Sie einen Cursor erstellen.

Der obige Vorschlag zur Verwendung von fetchone oder fetchmany löst das Problem nicht, da psycopg standardmäßig versucht, die gesamte Abfrage in ram zu laden, wenn Sie den name-Parameter unset lassen. Die einzige andere Sache, die Sie möglicherweise dazu brauchen (neben dem Deklarieren eines Namensparameters), ist das cursor.itersize-Attribut von den Standard 2000 auf 1000 zu ändern, wenn Sie immer noch zu wenig Speicher haben.

+0

Ich konnte nichts in 'sqlalchemy' finden, das mir geholfen hat, das OOM-Problem zu vermeiden, aber diese Lösung hat für mich funktioniert. Danke! –

7

Joeblog hat die richtige Antwort. Die Art, wie Sie mit dem Holen umgehen, ist wichtig, aber viel offensichtlicher als die Art, wie Sie den Cursor definieren müssen. Hier ist ein einfaches Beispiel, um dies zu veranschaulichen und Ihnen etwas zum Kopieren-Einfügen zu geben.

import datetime as dt 
import psycopg2 
import sys 
import time 

conPG = psycopg2.connect("dbname='myDearDB'") 
curPG = conPG.cursor('testCursor') 
curPG.itersize = 100000 # Rows fetched at one time from the server 

curPG.execute("SELECT * FROM myBigTable LIMIT 10000000") 
# Warning: curPG.rowcount == -1 ALWAYS !! 
cptLigne = 0 
for rec in curPG: 
    cptLigne += 1 
    if cptLigne % 10000 == 0: 
     print('.', end='') 
     sys.stdout.flush() # To see the progression 
conPG.commit() # Also close the cursor 
conPG.close() 

Wie Sie sehen werden, kam Punkte durch Gruppe als Pause schnell, einen Puffer von Zeilen (itersize) zu bekommen, so dass Sie nicht brauchen, fetchmany für die Leistung zu verwenden. Wenn ich dies mit /usr/bin/time -v ausführe, bekomme ich das Ergebnis in weniger als 3 Minuten, mit nur 200 MB RAM (anstelle von 60 GB mit clientseitigen Cursor) für 10 Millionen Zeilen. Der Server benötigt nicht mehr RAM, da er die temporäre Tabelle verwendet.

Verwandte Themen