Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Warum ist das Laden von SQLAlchemy-Objekten über das ORM 5-8x langsamer als Zeilen über einen rohen MySQLdb-Cursor?

Hier ist die SQLAlchemy-Version Ihres MySQL-Skripts, die in vier Sekunden ausgeführt wird, verglichen mit drei Sekunden bei MySQLdb:

from sqlalchemy import Integer, Column, create_engine, MetaData, Table
import datetime

metadata = MetaData()

foo = Table(
    'foo', metadata,
    Column('id', Integer, primary_key=True),
    Column('a', Integer(), nullable=False),
    Column('b', Integer(), nullable=False),
    Column('c', Integer(), nullable=False),
)


class Foo(object):
    def __init__(self, a, b, c):
        self.a = a
        self.b = b
        self.c = c

engine = create_engine('mysql+mysqldb://scott:[email protected]/test', echo=True)
start = datetime.datetime.now()

with engine.connect() as conn:
    foos = [
        Foo(row['a'], row['b'], row['c'])
        for row in
        conn.execute(foo.select().limit(1000000)).fetchall()
    ]


print "total time: ", datetime.datetime.now() - start

Laufzeit:

total time:  0:00:04.706010

Hier ist ein Skript, das das ORM verwendet, um Objektzeilen vollständig zu laden; indem die Erstellung einer festen Liste mit allen 1 Mio. Objekten auf einmal mit yield per vermieden wird, dauert dies 13 Sekunden mit SQLAlchemy master (18 Sekunden mit rel 0.9):

import time
from sqlalchemy import Integer, Column, create_engine, Table
from sqlalchemy.orm import Session
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class Foo(Base):
    __table__ = Table(
        'foo', Base.metadata,
        Column('id', Integer, primary_key=True),
        Column('a', Integer(), nullable=False),
        Column('b', Integer(), nullable=False),
        Column('c', Integer(), nullable=False),
    )


engine = create_engine('mysql+mysqldb://scott:[email protected]/test', echo=True)

sess = Session(engine)

now = time.time()

# avoid using all() so that we don't have the overhead of building
# a large list of full objects in memory
for obj in sess.query(Foo).yield_per(100).limit(1000000):
    pass

print("Total time: %d" % (time.time() - now))

Wir können dann den Unterschied zwischen diesen beiden Ansätzen aufteilen und nur einzelne Spalten mit dem ORM laden:

for obj in sess.query(Foo.id, Foo.a, Foo.b, Foo.c).yield_per(100).limit(1000000):
    pass

Das Obige läuft wieder in 4 Sekunden .

Der Vergleich von SQLAlchemy Core ist der treffendere Vergleich mit einem rohen MySQLdb-Cursor. Wenn Sie das ORM verwenden, aber nach einzelnen Spalten fragen, dauert es in den neuesten Versionen etwa vier Sekunden.

Auf der ORM-Ebene sind die Geschwindigkeitsprobleme darauf zurückzuführen, dass das Erstellen von Objekten in Python langsam ist und das SQLAlchemy-ORM beim Abrufen dieser Objekte eine große Menge an Buchhaltung anwendet, was erforderlich ist, damit es seinen Nutzungsvertrag einschließlich der Einheit erfüllen kann der Arbeit, Identitätskarte, eifriges Laden, Sammlungen usw.

Um die Abfrage erheblich zu beschleunigen, rufen Sie einzelne Spalten statt vollständiger Objekte ab. Siehe die Techniken unter http://docs .sqlalchemy.org/en/latest/faq/performance.html#result-fetching-slowness-orm die dies beschreiben.

Für Ihren Vergleich mit PeeWee ist PW ein viel einfacheres System mit viel weniger Funktionen, einschließlich der Tatsache, dass es nichts mit Identitätskarten zu tun hat. Selbst mit PeeWee, einem ungefähr so ​​einfachen ORM wie möglich, dauert es immer noch 15 Sekunden , was ein Beweis dafür ist, dass cPython wirklich sehr langsam ist verglichen mit dem rohen MySQLdb-Abruf, der direkt in C ist.

Im Vergleich zu Java ist die Java VM viel viel schneller als cPython . Hibernate ist lächerlich kompliziert, aber die Java VM ist aufgrund des JIT extrem schnell und selbst diese Komplexität läuft am Ende schneller. Wenn Sie Python mit Java vergleichen möchten, verwenden Sie Pypy.