Here is my answer reposted from http://stackoverflow.com/a/11769768/34549.
The SQLAlchemy ORM uses the unit of work pattern when synchronizing changes to the database. This pattern goes far beyond simple "inserts" of data. It includes that attributes which are assigned on objects are received using an attribute instrumentation system which tracks changes on objects as they are made, includes that all rows inserted are tracked in an identity map which has the effect that for each row SQLAlchemy must retrieve its "last inserted id" if not already given, and also involves that rows to be inserted are scanned and sorted for dependencies as needed. Objects are also subject to a fair degree of bookkeeping in order to keep all of this running, which for a very large number of rows at once can create an inordinate amount of time spent with large data structures, hence it's best to chunk these. Basically, unit of work is a large degree of automation in order to automate the task of persisting a complex object graph into a relational database with no explicit persistence code, and this automation has a price. So ORMs are basically not intended for high-performance bulk inserts. This is the whole reason why SQLAlchemy has two separate libraries, which you'll note if you look at http://docs.sqlalchemy.org/en/rel_0_7/index.html you'll see two distinct halves to the index page - one for the ORM and one for the Core. You cannot use SQLAlchemy effectively without understanding both. For the use case of fast bulk inserts, SQLAlchemy provides the core, which is the SQL generation and execution system that the ORM builds on top of. Using this system effectively we can produce an INSERT that is competitive with the raw SQLite version. The script below illustrates this, as well as an ORM version that pre-assigns primary key identifiers so that the ORM can use executemany() to insert rows. Both ORM versions chunk the flushes at 1000 records at a time as well which has a significant performance impact. Runtimes observed here are: 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 script: 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) On Aug 1, 2012, at 9:05 PM, Braddock Gaskill wrote: > > Why is this simple test case inserting 100,000 rows 25 times slower with > SQLAlchemy than it is using the sqlite3 driver directly? I have seen > similar slowdowns in real-world applications. Am I doing something wrong? > > I have asked this on StackOverflow. The question with my exact code is > at: > > http://stackoverflow.com/questions/11769366/why-is-sqlalchemy-insert-with-sqlite-25-times-slower-than-using-sqlite3-directly > > > > #!/usr/bin/env python > # Whis is SQLAlchemy with SQLite so slow? > # Output from this program: > # SqlAlchemy: Total time for 100000 records 10.74 secs > # sqlite3: Total time for 100000 records 0.40 secs > # By Braddock Gaskill 8/1/2012 > > 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'): > engine = create_engine(dbname, echo=False) > DBSession.configure(bind=engine, autoflush=False, > expire_on_commit=False) > Base.metadata.drop_all(engine) > Base.metadata.create_all(engine) > > def test_sqlalchemy(n=100000): > init_sqlalchemy() > t0 = time.time() > for i in range(n): > customer = Customer() > customer.name = 'NAME ' + str(i) > DBSession.add(customer) > DBSession.commit() > print "SqlAlchemy: Total time for " + str(n) + " records " + > str(time.time() - t0) + " secs" > > def test_sqlalchemy_insert(n=10000): > t0 = time.time() > for i in range(n): > ins = Customer.__table__.insert().values(name = 'NAME ' + str(i)) > DBSession.execute(ins) > DBSession.commit() > print "SqlAlchemy Insert: 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(100000) > test_sqlite3(100000) > test_sqlalchemy_insert(100000) > > ~ > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To post to this group, send email to sqlalchemy@googlegroups.com. > To unsubscribe from this group, send email to > sqlalchemy+unsubscr...@googlegroups.com. > For more options, visit this group at > http://groups.google.com/group/sqlalchemy?hl=en. > -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com. To unsubscribe from this group, send email to sqlalchemy+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en.