2008-11-07 7 views
73

Ich beginne eine neue Anwendung und suche mit einem ORM - insbesondere SQLAlchemy.Datenbank mit SQLAlchemy ORM effizient aktualisieren

Angenommen, ich habe eine Spalte 'foo' in meiner Datenbank und möchte sie erhöhen. In gerade SQLite ist dies relativ einfach:

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

ich die SQLAlchemy SQL-Builder-Äquivalent herausgefunden:

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) 

Dies ist etwas langsamer, aber es ist in ihm nicht viel.

Hier ist meine beste Vermutung für einen Ansatz 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() 

Dies tut das Richtige, aber es dauert knapp fünfzig mal so lang wie die beiden anderen Ansätze. Ich gehe davon aus, dass es alle Daten in den Speicher bringen muss, bevor es damit arbeiten kann.

Gibt es eine Möglichkeit, die effiziente SQL mit SQLAlchemy ORM zu generieren? Oder mit einem anderen Python ORM? Oder sollte ich einfach zurück zum Schreiben der SQL von Hand gehen?

+0

Ok, ich nehme an die Antwort ist "das ist nicht etwas ORMs gut tun". Naja; Ich lebe und lerne. –

+0

Es wurden einige Experimente mit verschiedenen ORMs durchgeführt und wie sie unter Last und Nötigung funktionieren. Einen Link nicht griffbereit, aber lesenswert. –

+0

Ein weiteres Problem, das mit dem letzten (ORM) Beispiel existiert, ist, dass es nicht [atomar] ist (http://en.wikipedia.org/wiki/Atomic_operation). – Marian

Antwort

130

SQLAlchemy ORM soll zusammen mit der SQL-Ebene verwendet werden, nicht ausblenden. Bei der Verwendung von ORM und normalem SQL in derselben Transaktion müssen Sie jedoch ein oder zwei Dinge beachten. Von einer Seite werden ORM-Datenänderungen nur dann auf die Datenbank treffen, wenn Sie die Änderungen aus Ihrer Sitzung entfernen. Auf der anderen Seite haben SQL-Datenmanipulationsanweisungen keinen Einfluss auf die Objekte in Ihrer Sitzung.

Also, wenn Sie sagen

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

es wird tun, was er sagt, gehen alle Objekte aus der Datenbank abrufen, ändern alle Objekte und dann, wenn es Zeit ist, die Änderungen in der Datenbank zu spülen, aktualisieren Sie die Zeilen eins nach dem anderen.

Stattdessen sollten Sie dies tun:

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

Dies als eine Abfrage ausgeführt wird, wie man es erwarten würde, und weil atleast die Standardsitzungskonfiguration alle Daten in der Sitzung läuft am begehen Sie haben keine abgestanden Datenprobleme.

In der fast veröffentlichten 0.5er Serie können Sie auch diese Methode für die Aktualisierung verwenden:

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

, die im Grunde die gleiche SQL-Anweisung wie die vorherige Snippet ausgeführt wird, sondern auch die geänderten Zeilen auswählen und schalen verfallen Daten in der Sitzung. Wenn Sie wissen, dass Sie nach dem Update keine Sitzungsdaten verwenden, können Sie der update-Anweisung auch synchronize_session = False hinzufügen und diese Auswahl löschen.

+0

in der 3. Weise wird es Orm-Ereignis auslösen (wie after_update)? – Ken

0

Withough Tests, ich würde versuchen:

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

(IIRC, commit() funktioniert ohne flush()).

Ich habe festgestellt, dass zu Zeiten eine große Abfrage und dann in Python iterieren kann bis zu 2 Größenordnungen schneller als viele Abfragen. Ich nehme an, dass das Iterieren über das Abfrageobjekt weniger effizient ist als das Iterieren über eine Liste, die von der all() -Methode des Abfrageobjekts generiert wird.

[Bitte beachten Sie den Kommentar unten - das hat die Dinge überhaupt nicht beschleunigt].

+2

Durch das Hinzufügen von .all() und das Entfernen von .flush() wurde die Uhrzeit überhaupt nicht geändert. –

0

Wenn es wegen der Overhead in Bezug auf die Erstellung von Objekten ist, kann es wahrscheinlich nicht mit SA beschleunigt werden.

Wenn dies der Fall ist, weil es verwandte Objekte lädt, können Sie möglicherweise etwas mit Lazy Loading durchführen. Werden aufgrund von Referenzen viele Objekte erstellt? (IE, erhält ein Firmenobjekt auch alle zugehörigen People-Objekte).

+0

Nein, der Tisch ist alles allein. Ich habe noch nie ein ORM benutzt - ist das nur schlecht? –

+1

Es gibt einen Overhead wegen der Erstellung von Objekten, aber meiner Meinung nach ist es die Strafe wert - in der Lage zu sein, Objekte in einer Datenbank dauerhaft zu speichern, ist großartig. –

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

Try this =)

+0

Diese Methode funktionierte für mich. Aber das Problem ist langsam. Es braucht ein gutes Stück Zeit für ein paar 100k Datensätze. Gibt es vielleicht eine schnellere Methode? – saitam

1

Hier ist ein Beispiel dafür, wie das gleiche Problem zu lösen, ohne die Felder manuell zuordnen müssen:

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() 

So eine Medien-Instanz zu aktualisieren, können Sie tun etwas wie das:

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

Es gibt mehrere Möglichkeiten UPDATE mit 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)