sql >> Databasteknik >  >> RDS >> Mysql

Varför är laddning av SQLAlchemy-objekt via ORM 5-8x långsammare än rader via en rå MySQLdb-markör?

Här är SQLAlchemy-versionen av ditt MySQL-skript som fungerar på fyra sekunder, jämfört med tre för 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

körtid:

total time:  0:00:04.706010

Här är ett skript som använder ORM för att ladda objektrader helt; genom att undvika att skapa en fast lista med alla 1M objekt på en gång med yield per, körs detta på 13 sekunder med SQLAlchemy master (18 sekunder med 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))

Vi kan sedan dela skillnaden mellan dessa två tillvägagångssätt och ladda bara enskilda kolumner med ORM:

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

Ovanstående körs igen på 4 sekunder .

Jämförelsen av SQLAlchemy Core är den mer passande jämförelsen med en rå MySQLdb-markör. Om du använder ORM men frågar efter enskilda kolumner, är det ungefär fyra sekunder i de senaste versionerna.

På ORM-nivå beror hastighetsproblemen på att det går långsamt att skapa objekt i Python, och SQLAlchemy ORM tillämpar en stor mängd bokföring på dessa objekt när den hämtar dem, vilket är nödvändigt för att det ska kunna uppfylla sitt användningskontrakt, inklusive enhet av arbete, identitetskarta, ivrig lastning, samlingar etc.

För att snabba upp frågan dramatiskt, hämta enskilda kolumner istället för hela objekt. Se teknikerna påhttp://docs .sqlalchemy.org/en/latest/faq/performance.html#result-fetching-slowness-orm som beskriver detta.

För din jämförelse med PeeWee är PW ett mycket enklare system med mycket färre funktioner, inklusive att det inte gör något med identitetskartor. Även med PeeWee, ungefär så enkel ORM som möjligt, tar det fortfarande 15 sekunder , vilket är ett bevis på att cPython verkligen är väldigt långsam jämfört med den råa MySQLdb-hämtningen som är i rak C.

För jämförelse med Java är Java VM mycket mycket snabbare än cPython . Hibernate är löjligt komplicerat, men Java VM är extremt snabb på grund av JIT och till och med all den komplexiteten går snabbare. Om du vill jämföra Python med Java, använd Pypy.



  1. Ska vi inkludera sorteringskolumn, primärnyckel på sammansatt index (MySQL)

  2. SQL - Kontrollera om en kolumn ökar automatiskt

  3. Care To Know-klausuler:Allt om SELECT, FROM, WHERE, GROUP BY, HAVING, ORDER BY och LIMIT

  4. Jämföra datum i MySQL och ignorera tidsdelen av ett DateTime-fält