Mysql
 sql >> Database >  >> RDS >> Mysql

Perché il caricamento di oggetti SQLAlchemy tramite ORM è 5-8 volte più lento delle righe tramite un cursore MySQLdb grezzo?

Ecco la versione SQLAlchemy del tuo script MySQL che viene eseguito in quattro secondi, rispetto ai tre di 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

tempo di esecuzione:

total time:  0:00:04.706010

Ecco uno script che utilizza l'ORM per caricare completamente le righe degli oggetti; evitando la creazione di un elenco fisso con tutti gli oggetti 1M contemporaneamente utilizzando yield per, questo viene eseguito in 13 secondi con SQLAlchemy master (18 secondi con 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))

Possiamo quindi dividere la differenza tra questi due approcci e caricare solo singole colonne con l'ORM:

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

Quanto sopra viene eseguito nuovamente in 4 secondi .

Il confronto di SQLAlchemy Core è il confronto più appropriato con un cursore MySQLdb grezzo. Se utilizzi l'ORM ma esegui query per singole colonne, nelle versioni più recenti sono necessari circa quattro secondi.

A livello di ORM, i problemi di velocità sono dovuti al fatto che la creazione di oggetti in Python è lenta e SQLAlchemy ORM applica una grande quantità di contabilità a questi oggetti mentre li recupera, il che è necessario per adempiere al suo contratto di utilizzo, inclusa l'unità di lavoro, mappa identità, caricamento ansioso, raccolte, ecc.

Per velocizzare notevolmente la query, recupera le singole colonne anziché gli oggetti interi. Vedere le tecniche suhttp://docs .sqlalchemy.org/en/latest/faq/performance.html#result-fetching-slowness-orm che descrivono questo.

Per il tuo confronto con PeeWee, PW è un sistema molto più semplice con molte meno funzionalità, incluso il fatto che non fa nulla con le mappe di identità. Anche con PeeWee, il più semplice possibile di un ORM, ci vogliono ancora 15 secondi , che è la prova che cPython è davvero molto lento rispetto al recupero grezzo di MySQLdb che è in C diritto.

Per confronto con Java, Java VM è molto più veloce di cPython . L'ibernazione è ridicola complicato, ma Java VM è estremamente veloce grazie al JIT e anche tutta quella complessità finisce per funzionare più velocemente. Se vuoi confrontare Python con Java, usa Pypy.