SQLAlchemy ORM использует шаблон единицы работы при синхронизации изменений в базе данных. Этот шаблон выходит далеко за рамки простой «вставки» данных. Он включает в себя то, что атрибуты, которые назначаются объектам, принимаются с использованием системы инструментовки атрибутов, которая отслеживает изменения объектов по мере их внесения, включает то, что все вставленные строки отслеживаются в карте идентичностичто приводит к тому, что для каждой строки SQLAlchemy должен получить свой «последний вставленный идентификатор», если он еще не задан, а также включает в себя то, что вставляемые строки сканируются и сортируются на зависимости по мере необходимости. Объекты также подлежат достаточному учету, чтобы все это работало, что для очень большого количества строк одновременно может привести к чрезмерному количеству времени, потраченному на большие структуры данных, поэтому лучше всего их разбить на части.
По сути, единица работы - это большая степень автоматизации, предназначенная для автоматизации задачи сохранения сложного графа объектов в реляционной базе данных без явного кода сохранения, и эта автоматизация имеет свою цену.
Таким образом, ORM в основном не предназначены для высокопроизводительных объемных вставок. Это вся причина, по которой SQLAlchemy имеет две отдельные библиотеки, которые вы заметите, если посмотрите http://docs.sqlalchemy.org/en/latest/index.html, вы увидите две отдельные половины на странице индекса: один для ORM и один для ядра. Вы не можете эффективно использовать SQLAlchemy, не понимая обоих.
Для случая использования быстрой массовой вставки SQLAlchemy предоставляет ядро , которое представляет собой систему генерации и выполнения SQL, поверх которой строится ORM. Эффективно используя эту систему, мы можем создать INSERT, который конкурирует с необработанной версией SQLite. Приведенный ниже сценарий иллюстрирует это, а также версию ORM, которая предварительно назначает идентификаторы первичного ключа, чтобы ORM мог использовать executemany () для вставки строк. Обе версии ORM также разделяют сбросы по 1000 записей за раз, что оказывает значительное влияние на производительность.
Здесь наблюдаются следующие режимы работы:
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
сценарий:
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)
См. Также: http://docs.sqlalchemy.org/en/latest/faq/performance.html