Mysql
 sql >> Base de Dados >  >> RDS >> Mysql

Por que o carregamento de objetos SQLAlchemy por meio do ORM 5-8x é mais lento que as linhas por meio de um cursor MySQLdb bruto?


Aqui está a versão SQLAlchemy do seu script MySQL que executa em quatro segundos, comparado a três para 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 de execução:
total time:  0:00:04.706010

Aqui está um script que usa o ORM para carregar totalmente as linhas do objeto; evitando a criação de uma lista fixa com todos os 1 milhão de objetos de uma vez usando yield per, isso é executado em 13 segundos com mestre SQLAlchemy (18 segundos com 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))

Podemos então dividir a diferença entre essas duas abordagens e carregar apenas colunas individuais com o ORM:
for obj in sess.query(Foo.id, Foo.a, Foo.b, Foo.c).yield_per(100).limit(1000000):
    pass

O acima é executado novamente em 4 segundos .

A comparação do SQLAlchemy Core é a comparação mais adequada com um cursor MySQLdb bruto. Se você usa o ORM, mas consulta colunas individuais, são cerca de quatro segundos nas versões mais recentes.

No nível do ORM, os problemas de velocidade são porque a criação de objetos em Python é lenta, e o SQLAlchemy ORM aplica uma grande quantidade de contabilidade a esses objetos à medida que os busca, o que é necessário para cumprir seu contrato de uso, incluindo unidade de trabalho, mapa de identidade, carregamento antecipado, coleções, etc.

Para acelerar drasticamente a consulta, busque colunas individuais em vez de objetos completos. Veja as técnicas emhttp://docs .sqlalchemy.org/en/latest/faq/performance.html#result-fetching-slowness-orm que descrevem isso.

Para sua comparação com o PeeWee, o PW é um sistema muito mais simples com muito menos recursos, incluindo o fato de não fazer nada com mapas de identidade. Mesmo com o PeeWee, um ORM tão simples quanto possível, ainda leva 15 segundos , que é uma evidência de que cPython é realmente muito lento comparado com a busca bruta do MySQLdb que está em C.

Para comparação com o Java, o Java VM é muito mais rápido que o cPython . Hibernar é ridiculamente complicado, mas o Java VM é extremamente rápido devido ao JIT e mesmo toda essa complexidade acaba rodando mais rápido. Se você quiser comparar Python com Java, use Pypy.