python - tutorial - sqlalchemy documentation
Comprender mejor los problemas de `yield_per()` de SQLalchemy (1)
Las dos estrategias de carga problemáticas yield_per
excepciones si intentas usarlas con yield_per
, por lo que no tienes que preocuparte demasiado.
Creo que el único problema con la subqueryload
es que la carga por lotes de la segunda consulta aún no está implementada. Nada saldría mal semánticamente, pero si está utilizando yield_per
, probablemente tenga una buena razón para no querer cargar todos los resultados a la vez. Así que SQLAlchemy se niega cortésmente a ir en contra de sus deseos.
joinedload
es un poco más sutil. Solo está prohibido en el caso de una colección, donde una fila primaria puede tener varias filas asociadas. Digamos que su consulta produce resultados en bruto como este, donde A y B son claves principales de diferentes tablas:
A | B
---+---
1 | 1
1 | 2
1 | 3
1 | 4
2 | 5
2 | 6
Ahora yield_per(3)
estos con yield_per(3)
. El problema es que SQLAlchemy solo puede limitar la cantidad que obtiene por filas , pero tiene que devolver objetos . Aquí, SQLAlchemy solo ve las primeras tres filas, por lo que crea un objeto A
con clave 1 y tres hijos B
: 1, 2 y 3.
Cuando carga el siguiente lote, quiere crear un nuevo objeto A
con la tecla 1 ... ah, pero ya tiene uno de esos, por lo que no es necesario volver a crearlo. El extra B
, 4, se pierde. (Por lo tanto, no, incluso la lectura de colecciones unidas con yield_per
no es segura, es yield_per
se yield_per
fragmentos de sus datos).
Podría decir "bueno, simplemente siga leyendo filas hasta que tenga un objeto completo", pero ¿y si ese A
tiene cien hijos? ¿O un millón? SQLAlchemy no puede garantizar razonablemente que pueda hacer lo que usted pidió y producir resultados correctos, por lo que se niega a intentarlo.
Recuerde que el DBAPI está diseñado para que cualquier base de datos se pueda usar con la misma API, incluso si esa base de datos no admite todas las funciones de DBAPI. Tenga en cuenta que el DBAPI está diseñado alrededor de los cursores, ¡pero MySQL en realidad no tiene cursores! Los adaptadores DBAPI para MySQL tienen que falsificarlos, en cambio.
Entonces, mientras cursor.fetchmany(100)
funcionará , se puede ver en el código fuente de MySQLdb
que no se extrae perezosamente del servidor; reúne todo en una gran lista, luego devuelve una porción cuando llama a fetchmany
.
Lo que admite psycopg2
es la transmisión real, donde los resultados se recuerdan de forma persistente en el servidor, y el proceso de Python solo ve algunos de ellos a la vez.
Aún puedes usar yield_per
con MySQLdb
, o cualquier otro DBAPI; Ese es el punto central del diseño de DBAPI. Tendrá que pagar el costo de la memoria para todas las filas en bruto ocultas en el DBAPI (que son tuplas, bastante baratas), pero no tendrá que pagar todos los objetos ORM al mismo tiempo.
Para citar la documentación de SQLalchemy :
El método Query.yield_per () no es compatible con la mayoría de los esquemas de carga impacientes, incluida la subconsulta y la carga con colecciones.
Advertencia
Utilice este método con precaución; Si la misma instancia está presente en más de un lote de filas, los cambios de los usuarios finales a los atributos se sobrescribirán.
En particular, generalmente es imposible usar esta configuración con colecciones cargadas con entusiasmo (es decir, cualquier flojo = ''unido'' o ''subconsulta'') ya que esas colecciones se borrarán para una nueva carga cuando se encuentren en un lote de resultados posterior. En el caso de la carga de ''subconsultas'', se obtiene el resultado completo de todas las filas, lo que generalmente anula el propósito de yield_per ().
También tenga en cuenta que mientras yield_per () configurará la opción de ejecución de stream_results en True, en la actualidad esto solo lo entiende el dialecto psycopg2, que transmitirá los resultados utilizando los cursores del lado del servidor en lugar del búfer previo a todas las filas para esta consulta. Otros DBAPIs pre-almacenan todas las filas antes de ponerlas a disposición. El uso de memoria de las filas de la base de datos sin procesar es mucho menor que el de un objeto mapeado por ORM, pero aún debe tenerse en cuenta cuando se hace una evaluación comparativa.
Realmente tengo un problema para entender cómo yield_per()
rendimiento de yield_per()
y cuál es exactamente el problema al usar este método. Además, ¿cuál es la forma correcta de solucionar estos problemas y seguir utilizando esta función para recorrer una gran cantidad de filas?
Estoy interesado en toda la información constructiva que tiene, pero aquí hay algunas preguntas de sugerencias:
- ¿Cómo puede haber múltiples instancias de la misma fila? ¿Solo a través de relaciones (si dos filas de la tabla de iteración tienen un FK a la misma fila en otra tabla)? ¿Hay algún problema si no sabe que sucede o solo lee los atributos de las relaciones?
- lazy = ''join'' o ''subquery'' no son posibles, pero ¿por qué exactamente? Ambos son simplemente partes de su consulta en la que llama a
yield_per()
.- Si se borran en un lote de resultados posterior, simplemente vuelva a cargarlo. ¿Entonces, dónde está el problema? ¿O es el único problema que pierde los cambios de sus relaciones si ha realizado cambios?
- En el caso de una carga de ''subconsulta'', ¿por qué se recuperan todas las filas? El servidor SQL puede tener que guardar una tabla grande, pero ¿por qué no devolver simplemente el resultado en lotes uno tras otro para la consulta completa?
- En un ejemplo en el
yield_per()
docq = sess.query(Object).yield_per(100).options(lazyload(''*''), joinedload(Object.some_related))
desactivan eagerload withlazyload(''*'')
pero mantener una sola carga unida. ¿Hay alguna manera de seguir utilizandoyield_per()
con eagerload? ¿Cuales son las condiciones?
- Dicen que
psycopg2
es el único DBAPI que soporta resultados de flujo. Entonces, ¿ese es el único DBAPI que puede usar conyield_per()
? Según tengo entendido,yield_per
usa la funcióncursor.fetchmany()
( example ) de DBAPI que admite muchos de ellos. Y, por lo que entiendo,cursor.fetchmany()
admite lacursor.fetchmany()
solo partes del resultado y no captura todo (si fuera a buscar todo, ¿por qué existe la función?) - Tengo la sensación de que
yield_per()
es completamente seguro (incluso con eagerload) si solo tiene acceso de lectura (por ejemplo, para estadísticas). ¿Es eso correcto?