Mysql
 sql >> Base de données >  >> RDS >> Mysql

Pourquoi le chargement des objets SQLAlchemy via l'ORM est-il 5 à 8 fois plus lent que les lignes via un curseur MySQLdb brut ?

Voici la version SQLAlchemy de votre script MySQL qui s'exécute en quatre secondes, contre trois pour 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

temps d'exécution :

total time:  0:00:04.706010

Voici un script qui utilise l'ORM pour charger complètement les lignes d'objets ; en évitant la création d'une liste fixe avec tous les objets 1M à la fois en utilisant yield per, cela s'exécute en 13 secondes avec maître SQLAlchemy (18 secondes avec 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))

Nous pouvons ensuite diviser la différence entre ces deux approches et charger uniquement des colonnes individuelles avec l'ORM :

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

Ce qui précède s'exécute à nouveau en 4 secondes .

La comparaison de SQLAlchemy Core est la comparaison la plus appropriée avec un curseur MySQLdb brut. Si vous utilisez l'ORM mais interrogez des colonnes individuelles, cela prend environ quatre secondes dans les versions les plus récentes.

Au niveau ORM, les problèmes de vitesse sont dus au fait que la création d'objets en Python est lente, et l'ORM SQLAlchemy applique une grande quantité de comptabilité à ces objets lorsqu'il les récupère, ce qui est nécessaire pour qu'il remplisse son contrat d'utilisation, y compris l'unité de travail, carte d'identité, chargement avide, collections, etc.

Pour accélérer considérablement la requête, récupérez des colonnes individuelles au lieu d'objets complets. Voir les techniques sur http://docs .sqlalchemy.org/en/latest/faq/performance.html#result-fetching-slowness-orm qui décrivent cela.

Pour votre comparaison avec PeeWee, PW est un système beaucoup plus simple avec beaucoup moins de fonctionnalités, y compris qu'il ne fait rien avec les cartes d'identité. Même avec PeeWee, à peu près aussi simple qu'un ORM possible, cela prend toujours 15 secondes , ce qui est la preuve que cPython est vraiment très lent par rapport à la récupération brute de MySQLdb qui est en C pur.

À titre de comparaison avec Java, la machine virtuelle Java est bien plus rapide que cPython . Hiberner est ridiculement compliqué, mais la machine virtuelle Java est extrêmement rapide en raison du JIT et même toute cette complexité finit par s'exécuter plus rapidement. Si vous voulez comparer Python à Java, utilisez Pypy.