¿Hay alguna manera de hacer que SQLAlchemy haga una inserción masiva en lugar de insertar cada objeto individual? es decir,
haciendo:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
más bien que:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
Acabo de convertir un código para usar sqlalchemy en lugar de sql sin procesar y, aunque ahora es mucho más agradable trabajar con él, parece ser más lento ahora (hasta un factor de 10), me pregunto si esta es la razón.
Puede ser que pueda mejorar la situación usando sesiones de manera más eficiente. Por el momento tengo autoCommit=False
y hago un session.commit()
después de haber agregado algunas cosas. Aunque esto parece hacer que los datos se vuelvan obsoletos si la base de datos se cambia en otro lugar, como si incluso hiciera una nueva consulta, ¿todavía obtengo resultados antiguos?
¡Gracias por tu ayuda!
Respuestas:
SQLAlchemy introdujo eso en la versión
1.0.0
:Operaciones masivas - SQLAlchemy docs
¡Con estas operaciones, ahora puede hacer inserciones o actualizaciones masivas!
Por ejemplo, puedes hacer:
Aquí, se realizará un inserto a granel.
fuente
\copy
psql (del mismo cliente al mismo servidor), veo una gran diferencia en el rendimiento en el lado del servidor que produce aproximadamente 10 veces más inserciones / s. Aparentemente, es una carga masiva usando\copy
(oCOPY
en el servidor) usando un paquete para comunicarse de cliente a servidor MUCHO mejor que usar SQL a través de SQLAlchemy. Más información: Gran diferencia de rendimiento de inserción masiva PostgreSQL frente a ... .Los documentos de sqlalchemy tienen un resumen del rendimiento de varias técnicas que se pueden usar para inserciones en masa:
fuente
Hasta donde yo sé, no hay forma de que el ORM emita inserciones masivas. Creo que la razón subyacente es que SQLAlchemy necesita realizar un seguimiento de la identidad de cada objeto (es decir, nuevas claves primarias), y las inserciones masivas interfieren con eso. Por ejemplo, suponiendo que su
foo
tabla contiene unaid
columna y está asignada a unaFoo
clase:Como SQLAlchemy recogió el valor
x.id
sin emitir otra consulta, podemos inferir que obtuvo el valor directamente de laINSERT
declaración. Si no necesita acceso posterior a los objetos creados a través de las mismas instancias, puede omitir la capa ORM para su inserción:SQLAlchemy no puede hacer coincidir estas nuevas filas con ningún objeto existente, por lo que tendrá que consultarlas nuevamente para cualquier operación posterior.
En lo que respecta a los datos obsoletos, es útil recordar que la sesión no tiene una forma integrada de saber cuándo se cambia la base de datos fuera de la sesión. Para acceder a datos modificados externamente a través de instancias existentes, las instancias deben marcarse como caducadas . Esto ocurre de forma predeterminada en
session.commit()
, pero se puede hacer manualmente llamandosession.expire_all()
osession.expire(instance)
. Un ejemplo (SQL omitido):session.commit()
expirax
, por lo que la primera declaración de impresión abre implícitamente una nueva transacción y vuelve a consultarx
los atributos. Si comenta la primera declaración impresa, notará que la segunda ahora recoge el valor correcto, porque la nueva consulta no se emite hasta después de la actualización.Esto tiene sentido desde el punto de vista del aislamiento transaccional: solo debe recoger modificaciones externas entre transacciones. Si esto le está causando problemas, sugeriría aclarar o repensar los límites de transacción de su aplicación en lugar de buscarlos de inmediato
session.expire_all()
.fuente
autocommit=False
, creo que debería llamarsession.commit()
al completar la solicitud (no estoy familiarizado con TurboGears, así que ignore esto si se maneja para usted a nivel de marco). Además de asegurarse de que sus cambios se hayan realizado en la base de datos, esto expiraría todo en la sesión. La próxima transacción no comenzaría hasta el próximo uso de esa sesión, por lo que las solicitudes futuras en el mismo hilo no verían datos obsoletos.session.execute(Foo.__table__.insert(), values)
Usualmente lo hago usando
add_all
.fuente
.add
a llevarlos a la sesión uno a la vez?Add the given collection of instances to this Session.
¿Tiene alguna razón para creer que no hace una inserción masiva?.add
cada elemento individualmente.bulk_save_objects()
, con aflush()
, podemos obtener la identificación del objeto, perobulk_save_objects()
no podemos (evento conflush()
llamado).Se agregó soporte directo a SQLAlchemy a partir de la versión 0.8
Según los documentos ,
connection.execute(table.insert().values(data))
debería hacer el truco. (Tenga en cuenta que esto no es mismoconnection.execute(table.insert(), data)
que resulta en muchas inserciones de fila individuales a través de una llamada aexecutemany
). En cualquier cosa que no sea una conexión local, la diferencia en el rendimiento puede ser enorme.fuente
SQLAlchemy introdujo eso en la versión
1.0.0
:Operaciones masivas - SQLAlchemy docs
¡Con estas operaciones, ahora puede hacer inserciones o actualizaciones masivas!
Por ejemplo (si desea la sobrecarga más baja para los INSERT de tabla simple), puede usar
Session.bulk_insert_mappings()
:O, si lo desea, omita las
loadme
tuplas y escriba los diccionarios directamentedicts
(pero me resulta más fácil dejar toda la palabrería de los datos y cargar una lista de diccionarios en un bucle).fuente
La respuesta de Piere es correcta, pero un problema es que,
bulk_save_objects
por defecto, no devuelve las claves principales de los objetos, si eso le preocupa. Establecerreturn_defaults
enTrue
obtener este comportamiento.La documentación está aquí .
fuente
Todos los caminos conducen a Roma , pero algunos de ellos cruzan montañas, requieren transbordadores, pero si desea llegar rápidamente, tome la autopista.
En este caso, la autopista debe utilizar la función execute_batch () de psycopg2 . La documentación lo dice lo mejor:
La implementación actual de
executemany()
es (utilizando un eufemismo extremadamente caritativo) no está funcionando particularmente. Estas funciones se pueden usar para acelerar la ejecución repetida de una declaración contra un conjunto de parámetros. Al reducir el número de viajes de ida y vuelta del servidor, el rendimiento puede ser mucho mayor que el usoexecutemany()
.En mi propia prueba
execute_batch()
es aproximadamente el doble de rápido queexecutemany()
, y le da la opción de configurar el tamaño_página para ajustar aún más (si usted quiere exprimir el último 2-3% de rendimiento del conductor).La misma característica se puede habilitar fácilmente si está utilizando SQLAlchemy configurando
use_batch_mode=True
como parámetro cuando crea una instancia del motor concreate_engine()
fuente
execute_values
es más rápido que psycopg2execute_batch
cuando se hacen inserciones masivas!Esta es una manera:
Esto se insertará así:
Referencia: Las preguntas frecuentes de SQLAlchemy incluyen puntos de referencia para varios métodos de confirmación.
fuente
La mejor respuesta que encontré hasta ahora fue en la documentación de sqlalchemy:
http://docs.sqlalchemy.org/en/latest/faq/performance.html#im-inserting-400-000-rows-with-the-orm-and-it-s-really-slow
Hay un ejemplo completo de un punto de referencia de posibles soluciones.
Como se muestra en la documentación:
bulk_save_objects no es la mejor solución, pero su rendimiento es correcto.
La segunda mejor implementación en términos de legibilidad creo que fue con SQLAlchemy Core:
El contexto de esta función se proporciona en el artículo de documentación.
fuente