2008-11-07 16 views
73

Estoy comenzando una nueva aplicación y estoy mirando usando un ORM, en particular, SQLAlchemy.Actualización eficiente de la base de datos usando SQLAlchemy ORM

Digamos que tengo una columna 'foo' en mi base de datos y quiero incrementarla. En sqlite recta, esto es fácil:

db = sqlite3.connect('mydata.sqlitedb') 
cur = db.cursor() 
cur.execute('update table stuff set foo = foo + 1') 

me di cuenta de SQL-constructor SQLAlchemy equivalente:

engine = sqlalchemy.create_engine('sqlite:///mydata.sqlitedb') 
md = sqlalchemy.MetaData(engine) 
table = sqlalchemy.Table('stuff', md, autoload=True) 
upd = table.update(values={table.c.foo:table.c.foo+1}) 
engine.execute(upd) 

Esto es un poco más lento, pero no hay mucho en ella.

Aquí es mi mejor conjetura de un enfoque SQLAlchemy ORM:

# snip definition of Stuff class made using declarative_base 
# snip creation of session object 
for c in session.query(Stuff): 
    c.foo = c.foo + 1 
session.flush() 
session.commit() 

Esto hace lo correcto, pero tiene poco menos de cincuenta veces más largo que los otros dos enfoques. Supongo que es porque tiene que traer todos los datos a la memoria antes de que pueda funcionar con ellos.

¿Hay alguna manera de generar el SQL eficiente utilizando SQLAlchemy's ORM? ¿O usando cualquier otro ORM de python? ¿O debería volver a escribir el SQL a mano?

+0

Ok, asumo que la respuesta es "esto no es algo que los ORM hacen bien". Oh bien; Yo vivo y aprendo –

+0

Se han realizado algunos experimentos en diferentes ORM y cómo funcionan bajo carga y coacción. No tiene un enlace a mano, pero vale la pena leerlo. –

+0

Otro problema que existe con el último ejemplo (ORM) es que no es [atómico] (http://en.wikipedia.org/wiki/Atomic_operation). – Marian

Respuesta

130

El ORM de SQLAlchemy está diseñado para ser utilizado junto con la capa SQL, no para ocultarla. Pero debe tener en cuenta una o dos cosas cuando usa el ORM y el SQL simple en la misma transacción. Básicamente, por un lado, las modificaciones de datos ORM solo llegarán a la base de datos cuando elimine los cambios de su sesión. Por otro lado, las instrucciones de manipulación de datos SQL no afectan a los objetos que están en su sesión.

Así que si usted dice

for c in session.query(Stuff).all(): 
    c.foo = c.foo+1 
session.commit() 

que va a hacer lo que dice, ir a buscar todos los objetos de la base de datos, modificar todos los objetos y luego, cuando llega el momento de limpiar los cambios en la base de datos, actualizar el filas una a una.

su lugar debe hacer esto:

session.execute(update(stuff_table, values={stuff_table.c.foo: stuff_table.c.foo + 1})) 
session.commit() 

Esto ejecutará como una consulta como era de esperar, y porque al menos la configuración de sesión predeterminado expira todos los datos en la sesión de comprometerse usted no tiene ninguna rancio problemas de datos.

En la serie lanzado casi-0.5 también se puede utilizar este método para la actualización:

session.query(Stuff).update({Stuff.foo: Stuff.foo + 1}) 
session.commit() 

que, básicamente, se ejecutará la misma instrucción SQL como el fragmento anterior, sino también seleccionar las filas modificados y venza cualquier rancio datos en la sesión. Si sabe que no está utilizando ningún dato de sesión después de la actualización, también puede agregar synchronize_session = False a la declaración de actualización y deshacerse de esa selección.

+0

en la 3ra manera, ¿activará el evento orm (como after_update)? – Ken

0

withough pruebas, que iba a tratar:

for c in session.query(Stuff).all(): 
    c.foo = c.foo+1 
session.commit() 

(IIRC, commit() funciona sin flush()).

He encontrado que a veces hacer una consulta grande y luego iterar en python puede ser hasta 2 órdenes de magnitud más rápido que muchas consultas. Supongo que iterar sobre el objeto de consulta es menos eficiente que iterar sobre una lista generada por el método all() del objeto de consulta.

[Tenga en cuenta el comentario a continuación - esto no aceleró las cosas en absoluto].

+2

Agregar .all() y eliminar .flush() no cambió el tiempo en absoluto. –

0

Si se debe a la sobrecarga en términos de creación de objetos, entonces probablemente no se pueda acelerar con SA.

Si es porque está cargando objetos relacionados, entonces es posible que pueda hacer algo con la carga diferida. ¿Se crean muchos objetos debido a las referencias? (Es decir, obtener un objeto de compañía también obtiene todos los objetos de personas relacionados).

+0

Nah, la mesa está sola. Nunca antes había usado un ORM, ¿es algo en lo que son malos? –

+1

Hay una sobrecarga debido a la creación de Objetos, pero en mi opinión vale la pena: poder almacenar objetos de manera persistente en una base de datos es asombroso. –

61
session.query(Clients).filter(Clients.id == client_id_list).update({'status': status}) 
session.commit() 

Prueba esto =)

+0

Este método funcionó para mí. Pero el problema es que es lento. Necesita una buena cantidad de tiempo para unos pocos 100k registros de datos. ¿Hay quizás un método más rápido? – saitam

1

Aquí hay un ejemplo de cómo resolver el mismo problema sin tener que asignar los campos manualmente:

from sqlalchemy import Column, ForeignKey, Integer, String, Date, DateTime, text, create_engine 
from sqlalchemy.exc import IntegrityError 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm import sessionmaker 
from sqlalchemy.orm.attributes import InstrumentedAttribute 

engine = create_engine('postgres://[email protected]:5432/database') 
session = sessionmaker() 
session.configure(bind=engine) 

Base = declarative_base() 


class Media(Base): 
    __tablename__ = 'media' 
    id = Column(Integer, primary_key=True) 
    title = Column(String, nullable=False) 
    slug = Column(String, nullable=False) 
    type = Column(String, nullable=False) 

    def update(self): 
    s = session() 
    mapped_values = {} 
    for item in Media.__dict__.iteritems(): 
     field_name = item[0] 
     field_type = item[1] 
     is_column = isinstance(field_type, InstrumentedAttribute) 
     if is_column: 
     mapped_values[field_name] = getattr(self, field_name) 

    s.query(Media).filter(Media.id == self.id).update(mapped_values) 
    s.commit() 

Así que para actualizar una instancia de Medios, que puede hacer algo como esto:

media = Media(id=123, title="Titular Line", slug="titular-line", type="movie") 
media.update() 
7

Hay varias maneras de ACTUALIZAR usando sqlalchemy

1) for c in session.query(Stuff).all(): 
     c.foo += 1 
    session.commit() 

2) session.query().\ 
     update({"foo": (Stuff.foo + 1)}) 
    session.commit() 

3) conn = engine.connect() 
    stmt = Stuff.update().\ 
     values(Stuff.foo = (Stuff.foo + 1)) 
    conn.execute(stmt) 
Cuestiones relacionadas