sql >> Database >  >> RDS >> Mysql

Waarom is het laden van SQLAlchemy-objecten via de ORM 5-8x langzamer dan rijen via een onbewerkte MySQLdb-cursor?

Hier is de SQLAlchemy-versie van uw MySQL-script die in vier seconden wordt uitgevoerd, vergeleken met drie voor 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

looptijd:

total time:  0:00:04.706010

Hier is een script dat de ORM gebruikt om objectrijen volledig te laden; door te voorkomen dat er een vaste lijst met alle 1 miljoen objecten tegelijk wordt gemaakt met behulp van opbrengst per, loopt dit in 13 seconden met SQLAlchemy master (18 seconden met 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))

We kunnen dan het verschil tussen deze twee benaderingen splitsen en alleen individuele kolommen laden met de ORM:

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

Het bovenstaande wordt opnieuw uitgevoerd in 4 seconden .

De vergelijking van SQLAlchemy Core is de meest geschikte vergelijking met een onbewerkte MySQLdb-cursor. Als u de ORM gebruikt maar voor afzonderlijke kolommen zoekt, duurt dit ongeveer vier seconden in de meest recente versies.

Op het ORM-niveau zijn de snelheidsproblemen omdat het maken van objecten in Python traag is, en de SQLAlchemy ORM past een grote hoeveelheid boekhouding toe op deze objecten terwijl het ze ophaalt, wat nodig is om zijn gebruikscontract te vervullen, inclusief eenheid van werk, identiteitskaart, gretig laden, collecties, enz.

Om de zoekopdracht aanzienlijk te versnellen, haalt u afzonderlijke kolommen op in plaats van volledige objecten. Zie de technieken ophttp://docs .sqlalchemy.org/en/latest/faq/performance.html#result-fetching-slowness-orm die dit beschrijven.

Ter vergelijking met PeeWee:PW is een veel eenvoudiger systeem met veel minder functies, waaronder dat het niets doet met identiteitskaarten. Zelfs met PeeWee, ongeveer zo eenvoudig als een ORM als mogelijk is, duurt het nog steeds 15 seconden , wat het bewijs is dat cPython echt heel traag is vergeleken met de onbewerkte MySQLdb-fetch die in rechte C staat.

Ter vergelijking met Java:de Java VM is veel sneller dan cPython . Hibernate is belachelijk ingewikkeld, maar de Java VM is extreem snel vanwege de JIT en zelfs al die complexiteit wordt uiteindelijk sneller. Als je Python met Java wilt vergelijken, gebruik dan Pypy.



  1. Slow bulk insert voor tafel met veel indexen

  2. MySQL:bigint versus int

  3. Een databasenaam in SQL Server wijzigen met T-SQL

  4. InnoDB-tabellen samenvoegen met MyISAM-tabellen