tutorial query many delete binds python memory sqlalchemy memory-leaks

query - ¿Es esto una pérdida de memoria(un programa en python con sqlalchemy/sqlite)



sqlalchemy many to many (2)

La mayoría de las DBAPI, incluidas psycopg2 y mysql-python, cargan completamente todos los resultados en la memoria antes de liberarlos al cliente. La opción yield_per () de SQLA no funciona alrededor de esto, con una excepción más abajo, por lo que generalmente no es una opción muy útil (edit: útil en el sentido de que comienza a transmitir los resultados antes de que las filas reales sean recuperadas).

Las excepciones a este comportamiento son:

  1. Usando un DBAPI que no almacena en búfer filas. cx_oracle es uno, como resultado de la forma natural en que OCI trabaja. No estoy seguro del comportamiento de pg8000, y también hay un nuevo MySQL DBAPI llamado OurSQL que, según me dijo su creador, no almacena las filas. pg8000 y OurSQL son compatibles con SQLAlchemy 0.6.
  2. Con psycopg2, se puede usar un "cursor del lado del servidor". SQLAlchemy admite un indicador create_engine () "server_side_cursors = True" que utiliza cursores del lado del servidor para todas las operaciones de selección de filas. Sin embargo, dado que los cursores del lado del servidor generalmente son caros y por lo tanto reducirán el rendimiento para consultas más pequeñas, SQLAlchemy 0.6 ahora admite el cursor del lado del servidor de psycopg2 en una declaración o por consulta utilizando .execution_options (stream_results = True), donde execution_options está disponible en Consultar, seleccionar (), texto () y Conexión. El objeto Query llama a esta opción cuando se utiliza yield_per (), por lo que en 0.6 yield_per () junto con psycopg2 es realmente útil.

Tengo el siguiente código se ejecuta sobre un gran conjunto de datos (2M). Se come toda mi memoria 4G antes de terminar.

for sample in session.query(CodeSample).yield_per(100): for proj in projects: if sample.filename.startswith(proj.abs_source): sample.filename = "some other path" session.add(sample)

Luego lo ejecuté a través de un conjunto reducido de datos y analicé el montón con Heapy. get_rp () me dio la siguiente pista

0: _ --- [-] 47821 (0x9163aec | 0x9165fec | 0x916d6cc | 0x9251414 | 0x925704... 1: a [-] 8244 tuple: 0x903ec8c*37, 0x903fcfc*13, 0x9052ecc*46... 2: aa ---- [S] 3446 types.CodeType: parseresult.py:73:src_path... 3: ab [S] 364 type: __builtin__.Struct, _random.Random, sqlite3.Cache... 4: ac ---- [-] 90 sqlalchemy.sql.visitors.VisitableType: 0x9162f2c... 5: aca [S] 11 dict of module: ..sql..., codemodel, sqlalchemy 6: acb ---- [-] 48 sqlalchemy.sql.visitors.VisitableType: 0x9162f2c... 7: acba [S] 9 dict of module: ..sql..., codemodel, sqlalchemy 8: acbb ---- [-] 45 sqlalchemy.sql.visitors.VisitableType: 0x9165fec... 9: acbba [S] 8 dict of module: ..sql..., codemodel, sqlalchemy

Soy nuevo en sqlalchemy. ¿Es esto una pérdida de memoria? Gracias.


La sesión hará un seguimiento de todos los objetos CodeSample que recupere. Entonces, después de iterar sobre objetos de 2M, la sesión mantiene una referencia a todos ellos. La sesión necesita estas referencias para que pueda escribir los cambios correctos en la base de datos en flush . Entonces creo que lo que estás viendo es de esperar.

Para mantener solo N objetos en la memoria a la vez, puede hacer algo como el siguiente código (inspirado por esta respuesta , descargo de responsabilidad: no lo he probado).

offset = 0 N = 10000 got_rows = True while got_rows: got_rows = False for sample in session.query(CodeSample).limit(N).offset(offset): got_rows = True for proj in projects: if sample.filename.startswith(proj.abs_source): sample.filename = "some other path" offset += N session.flush() # writes changes to DB session.expunge_all() # removes objects from session

Pero lo anterior es un poco torpe, tal vez algunos gurús de SQLAlchemy saben cómo hacer esto mejor.

Por cierto, no debería necesitar session.add (), la sesión rastrea los cambios a los objetos. ¿Por qué usas yield_per ( EDIT: supongo que esto es para obtener las filas en fragmentos de la base de datos, ¿es correcto? La sesión hará un seguimiento de todos ellos de todos modos).

EDITAR:

Hmm, parece que hay algo que he malentendido. De los documentos :

weak_identity_map: cuando se establece en el valor predeterminado de True, se utiliza un mapa de referencia débil; las instancias a las que no se haga referencia externa serán basura recolectada inmediatamente. Para las instancias desreferenciadas que tienen cambios pendientes, el sistema de administración de atributos creará una referencia fuerte temporal al objeto que durará hasta que los cambios se descarguen a la base de datos, en cuyo punto se vuelve a desreferenciar. Alternativamente, al usar el valor False, el mapa de identidad usa un diccionario de Python regular para almacenar instancias. La sesión mantendrá todas las instancias presentes hasta que se eliminen mediante expunge (), clear () o purge ().

y

podar (): eliminar instancias sin referencia almacenadas en caché en el mapa de identidad.

Tenga en cuenta que este método solo es significativo si "weak_identity_map" está establecido en False. El mapa de identidad débil predeterminado es autodetección.

Elimina cualquier objeto en el mapa de identidad de esta sesión que no esté referenciado en el código de usuario, modificado, nuevo o programado para borrado. Devuelve la cantidad de objetos podados.