Das SQLAlchemy ORM verwendet das Arbeitseinheitsmuster beim Synchronisieren von Änderungen an der Datenbank. Dieses Muster geht weit über einfache "Einfügungen" von Daten hinaus. Es beinhaltet, dass Attribute, die Objekten zugewiesen sind, unter Verwendung eines Attributinstrumentierungssystems empfangen werden, das Änderungen an Objekten verfolgt, während sie vorgenommen werden, und dass alle eingefügten Zeilen in einer Identitätszuordnung verfolgt werdenDies hat zur Folge, dass SQLAlchemy für jede Zeile die "zuletzt eingefügte ID" abrufen muss, sofern dies nicht bereits angegeben ist, und dass die einzufügenden Zeilen nach Bedarf gescannt und nach Abhängigkeiten sortiert werden. Objekte unterliegen auch einem angemessenen Maß an Buchhaltung, um all dies am Laufen zu halten, was für eine sehr große Anzahl von Zeilen gleichzeitig einen übermäßig langen Zeitaufwand für große Datenstrukturen verursachen kann. Daher ist es am besten, diese zu zerlegen.
Grundsätzlich ist die Arbeitseinheit ein großer Automatisierungsgrad, um die Aufgabe zu automatisieren, ein komplexes Objektdiagramm in einer relationalen Datenbank ohne expliziten Persistenzcode zu speichern, und diese Automatisierung hat ihren Preis.
ORMs sind daher grundsätzlich nicht für Hochleistungs-Bulk-Einsätze gedacht. Dies ist der ganze Grund, warum SQLAlchemy über zwei separate Bibliotheken verfügt. Wenn Sie sich http://docs.sqlalchemy.org/en/latest/index.html ansehen, sehen Sie zwei unterschiedliche Hälften der Indexseite. eine für das ORM und eine für den Core. Sie können SQLAlchemy nicht effektiv einsetzen, ohne beide zu verstehen.
Für den Anwendungsfall schneller Masseneinfügungen stellt SQLAlchemy den Kern bereit , bei dem es sich um das SQL-Generierungs- und Ausführungssystem handelt, auf dem der ORM aufbaut. Mit diesem System können wir effektiv ein INSERT erstellen, das mit der SQLite-Rohversion konkurriert. Das folgende Skript veranschaulicht dies sowie eine ORM-Version, die Primärschlüssel-IDs vorab zuweist, damit der ORM Executemany () zum Einfügen von Zeilen verwenden kann. Beide ORM-Versionen teilen die Flushes ebenfalls auf 1000 Datensätze gleichzeitig, was erhebliche Auswirkungen auf die Leistung hat.
Die hier beobachteten Laufzeiten sind:
SqlAlchemy ORM: Total time for 100000 records 16.4133379459 secs
SqlAlchemy ORM pk given: Total time for 100000 records 9.77570986748 secs
SqlAlchemy Core: Total time for 100000 records 0.568737983704 secs
sqlite3: Total time for 100000 records 0.595796823502 sec
Skript:
import time
import sqlite3
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
Base = declarative_base()
DBSession = scoped_session(sessionmaker())
class Customer(Base):
__tablename__ = "customer"
id = Column(Integer, primary_key=True)
name = Column(String(255))
def init_sqlalchemy(dbname = 'sqlite:///sqlalchemy.db'):
global engine
engine = create_engine(dbname, echo=False)
DBSession.remove()
DBSession.configure(bind=engine, autoflush=False, expire_on_commit=False)
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
def test_sqlalchemy_orm(n=100000):
init_sqlalchemy()
t0 = time.time()
for i in range(n):
customer = Customer()
customer.name = 'NAME ' + str(i)
DBSession.add(customer)
if i % 1000 == 0:
DBSession.flush()
DBSession.commit()
print "SqlAlchemy ORM: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"
def test_sqlalchemy_orm_pk_given(n=100000):
init_sqlalchemy()
t0 = time.time()
for i in range(n):
customer = Customer(id=i+1, name="NAME " + str(i))
DBSession.add(customer)
if i % 1000 == 0:
DBSession.flush()
DBSession.commit()
print "SqlAlchemy ORM pk given: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"
def test_sqlalchemy_core(n=100000):
init_sqlalchemy()
t0 = time.time()
engine.execute(
Customer.__table__.insert(),
[{"name":'NAME ' + str(i)} for i in range(n)]
)
print "SqlAlchemy Core: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs"
def init_sqlite3(dbname):
conn = sqlite3.connect(dbname)
c = conn.cursor()
c.execute("DROP TABLE IF EXISTS customer")
c.execute("CREATE TABLE customer (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY(id))")
conn.commit()
return conn
def test_sqlite3(n=100000, dbname = 'sqlite3.db'):
conn = init_sqlite3(dbname)
c = conn.cursor()
t0 = time.time()
for i in range(n):
row = ('NAME ' + str(i),)
c.execute("INSERT INTO customer (name) VALUES (?)", row)
conn.commit()
print "sqlite3: Total time for " + str(n) + " records " + str(time.time() - t0) + " sec"
if __name__ == '__main__':
test_sqlalchemy_orm(100000)
test_sqlalchemy_orm_pk_given(100000)
test_sqlalchemy_core(100000)
test_sqlite3(100000)
Siehe auch: http://docs.sqlalchemy.org/en/latest/faq/performance.html
Hervorragende Antwort von @zzzeek. Für diejenigen, die sich über dieselben Statistiken für Abfragen wundern, habe ich den @ zzezeek-Code leicht geändert, um dieselben Datensätze direkt nach dem Einfügen abzufragen und diese Datensätze dann in eine Liste von Diktaten zu konvertieren.
Hier sind die Ergebnisse
SqlAlchemy ORM: Total time for 100000 records 11.9210000038 secs SqlAlchemy ORM query: Total time for 100000 records 2.94099998474 secs SqlAlchemy ORM pk given: Total time for 100000 records 7.51800012589 secs SqlAlchemy ORM pk given query: Total time for 100000 records 3.07699990273 secs SqlAlchemy Core: Total time for 100000 records 0.431999921799 secs SqlAlchemy Core query: Total time for 100000 records 0.389000177383 secs sqlite3: Total time for 100000 records 0.459000110626 sec sqlite3 query: Total time for 100000 records 0.103999853134 secs
Interessanterweise ist das Abfragen mit Bare SQLite3 immer noch etwa dreimal schneller als mit SQLAlchemy Core. Ich denke, das ist der Preis, den Sie für die Rückgabe eines ResultProxy anstelle einer bloßen sqlite3-Zeile zahlen.
SQLAlchemy Core ist ungefähr 8-mal schneller als ORM. Das Abfragen mit ORM ist also viel langsamer, egal was passiert.
Hier ist der Code, den ich verwendet habe:
import time import sqlite3 from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, create_engine from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.sql import select Base = declarative_base() DBSession = scoped_session(sessionmaker()) class Customer(Base): __tablename__ = "customer" id = Column(Integer, primary_key=True) name = Column(String(255)) def init_sqlalchemy(dbname = 'sqlite:///sqlalchemy.db'): global engine engine = create_engine(dbname, echo=False) DBSession.remove() DBSession.configure(bind=engine, autoflush=False, expire_on_commit=False) Base.metadata.drop_all(engine) Base.metadata.create_all(engine) def test_sqlalchemy_orm(n=100000): init_sqlalchemy() t0 = time.time() for i in range(n): customer = Customer() customer.name = 'NAME ' + str(i) DBSession.add(customer) if i % 1000 == 0: DBSession.flush() DBSession.commit() print "SqlAlchemy ORM: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs" t0 = time.time() q = DBSession.query(Customer) dict = [{'id':r.id, 'name':r.name} for r in q] print "SqlAlchemy ORM query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs" def test_sqlalchemy_orm_pk_given(n=100000): init_sqlalchemy() t0 = time.time() for i in range(n): customer = Customer(id=i+1, name="NAME " + str(i)) DBSession.add(customer) if i % 1000 == 0: DBSession.flush() DBSession.commit() print "SqlAlchemy ORM pk given: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs" t0 = time.time() q = DBSession.query(Customer) dict = [{'id':r.id, 'name':r.name} for r in q] print "SqlAlchemy ORM pk given query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs" def test_sqlalchemy_core(n=100000): init_sqlalchemy() t0 = time.time() engine.execute( Customer.__table__.insert(), [{"name":'NAME ' + str(i)} for i in range(n)] ) print "SqlAlchemy Core: Total time for " + str(n) + " records " + str(time.time() - t0) + " secs" conn = engine.connect() t0 = time.time() sql = select([Customer.__table__]) q = conn.execute(sql) dict = [{'id':r[0], 'name':r[0]} for r in q] print "SqlAlchemy Core query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs" def init_sqlite3(dbname): conn = sqlite3.connect(dbname) c = conn.cursor() c.execute("DROP TABLE IF EXISTS customer") c.execute("CREATE TABLE customer (id INTEGER NOT NULL, name VARCHAR(255), PRIMARY KEY(id))") conn.commit() return conn def test_sqlite3(n=100000, dbname = 'sqlite3.db'): conn = init_sqlite3(dbname) c = conn.cursor() t0 = time.time() for i in range(n): row = ('NAME ' + str(i),) c.execute("INSERT INTO customer (name) VALUES (?)", row) conn.commit() print "sqlite3: Total time for " + str(n) + " records " + str(time.time() - t0) + " sec" t0 = time.time() q = conn.execute("SELECT * FROM customer").fetchall() dict = [{'id':r[0], 'name':r[0]} for r in q] print "sqlite3 query: Total time for " + str(len(dict)) + " records " + str(time.time() - t0) + " secs" if __name__ == '__main__': test_sqlalchemy_orm(100000) test_sqlalchemy_orm_pk_given(100000) test_sqlalchemy_core(100000) test_sqlite3(100000)
Ich habe auch getestet, ohne das Abfrageergebnis in Diktate umzuwandeln, und die Statistiken sind ähnlich:
SqlAlchemy ORM: Total time for 100000 records 11.9189999104 secs SqlAlchemy ORM query: Total time for 100000 records 2.78500008583 secs SqlAlchemy ORM pk given: Total time for 100000 records 7.67199993134 secs SqlAlchemy ORM pk given query: Total time for 100000 records 2.94000005722 secs SqlAlchemy Core: Total time for 100000 records 0.43700003624 secs SqlAlchemy Core query: Total time for 100000 records 0.131000041962 secs sqlite3: Total time for 100000 records 0.500999927521 sec sqlite3 query: Total time for 100000 records 0.0859999656677 secs
Das Abfragen mit SQLAlchemy Core ist im Vergleich zu ORM etwa 20-mal schneller.
Es ist wichtig zu beachten, dass diese Tests sehr oberflächlich sind und nicht zu ernst genommen werden sollten. Möglicherweise fehlen mir einige offensichtliche Tricks, die die Statistiken vollständig ändern könnten.
Der beste Weg, um Leistungsverbesserungen zu messen, ist direkt in Ihrer eigenen Anwendung. Nimm meine Statistiken nicht als selbstverständlich an.
quelle
Ich würde den Insert-Expression- Test und dann den Benchmark versuchen .
Es wird wahrscheinlich immer noch langsamer sein, weil der OP-Mapper-Overhead ist, aber ich würde hoffen, dass es nicht viel langsamer ist.
Würde es Ihnen etwas ausmachen, Ergebnisse zu versuchen und zu veröffentlichen? Das ist sehr interessantes Zeug.
quelle