有没有办法让SQLAlchemy进行批量插入而不是插入每个单独的对象。即,
做的:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
而不是:
INSERT INTO `foo` (`bar`) VALUES (1)
INSERT INTO `foo` (`bar`) VALUES (2)
INSERT INTO `foo` (`bar`) VALUES (3)
我刚刚转换了一些代码来使用sqlalchemy而不是原始的sql,虽然它现在更好用,但它现在看起来更慢(高达10倍),我想知道这是不是原因。
可能我可以更有效地使用会话改善情况。目前我已autoCommit=False
并在添加一些内容后执行了session.commit()
。虽然这似乎会导致数据在其他地方更改数据库时变得陈旧,即使我做了一个新查询,我仍然会得到旧结果?
感谢您的帮助!
答案 0 :(得分:118)
SQLAlchemy在版本#!/bin/bash
groovy script.groovy
中介绍了
Bulk operations - SQLAlchemy docs
通过这些操作,您现在可以进行批量插入或更新!
例如,你可以这样做:
1.0.0
此处,将制作批量插入物。
答案 1 :(得分:28)
据我所知,没有办法让ORM发出批量插入。我认为潜在的原因是SQLAlchemy需要跟踪每个对象的身份(即新的主键),而批量插入会干扰它。例如,假设您的foo
表包含id
列并映射到Foo
类:
x = Foo(bar=1)
print x.id
# None
session.add(x)
session.flush()
# BEGIN
# INSERT INTO foo (bar) VALUES(1)
# COMMIT
print x.id
# 1
由于SQLAlchemy选择x.id
的值而不发出另一个查询,我们可以推断它直接从INSERT
语句获得了值。如果您不需要通过相同的实例随后访问创建的对象,则可以跳过插入的ORM层:
Foo.__table__.insert().execute([{'bar': 1}, {'bar': 2}, {'bar': 3}])
# INSERT INTO foo (bar) VALUES ((1,), (2,), (3,))
SQLAlchemy无法将这些新行与任何现有对象匹配,因此您必须重新查询它们以进行任何后续操作。
就陈旧数据而言,记住会话没有内置方式来了解数据库何时在会话之外进行更改是有帮助的。要通过现有实例访问外部修改的数据,必须将实例标记为过期。默认情况下会在session.commit()
上执行此操作,但可以通过调用session.expire_all()
或session.expire(instance)
手动完成。一个例子(SQL省略):
x = Foo(bar=1)
session.add(x)
session.commit()
print x.bar
# 1
foo.update().execute(bar=42)
print x.bar
# 1
session.expire(x)
print x.bar
# 42
session.commit()
到期x
,因此第一个print语句隐式打开一个新事务并重新查询x
的属性。如果您注释掉第一个print语句,您会注意到第二个print语句现在选择了正确的值,因为直到更新后才会发出新查询。
从事务隔离的角度来看,这是有道理的 - 您应该只在事务之间进行外部修改。如果这会给您带来麻烦,我建议您澄清或重新考虑您的应用程序的交易边界,而不是立即联系到session.expire_all()
。
答案 2 :(得分:18)
sqlalchemy文档对可用于批量插入的各种技术的性能有很好的写作:
ORM基本上不适用于高性能批量插件 - 这就是SQLAlchemy除了提供Core之外还提供Core的全部原因 ORM作为一流的组件。
对于快速批量插入的用例,SQL生成和 ORM构建在其上的执行系统是Core的一部分。 直接使用这个系统,我们可以生成一个INSERT 与直接使用原始数据库API相比具有竞争力。
或者,SQLAlchemy ORM提供批量操作套件 方法,它为工作单元的子部分提供钩子 进程以发出核心级INSERT和UPDATE结构 一小部分基于ORM的自动化。
以下示例说明了几种不同的基于时间的测试 插入行的方法,从最自动化到最小化。 使用cPython 2.7,观察到运行时:
classics-MacBook-Pro:sqlalchemy classic$ python test.py SQLAlchemy ORM: Total time for 100000 records 12.0471920967 secs SQLAlchemy ORM pk given: Total time for 100000 records 7.06283402443 secs SQLAlchemy ORM bulk_save_objects(): Total time for 100000 records 0.856323003769 secs SQLAlchemy Core: Total time for 100000 records 0.485800027847 secs sqlite3: Total time for 100000 records 0.487842082977 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()) engine = None 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 xrange(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 xrange(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_orm_bulk_insert(n=100000): init_sqlalchemy() t0 = time.time() n1 = n while n1 > 0: n1 = n1 - 10000 DBSession.bulk_insert_mappings( Customer, [ dict(name="NAME " + str(i)) for i in xrange(min(10000, n1)) ] ) DBSession.commit() print( "SQLAlchemy ORM bulk_save_objects(): 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 xrange(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 xrange(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_orm_bulk_insert(100000) test_sqlalchemy_core(100000) test_sqlite3(100000)
答案 3 :(得分:13)
我通常使用add_all
。
from app import session
from models import User
objects = [User(name="u1"), User(name="u2"), User(name="u3")]
session.add_all(objects)
session.commit()
答案 4 :(得分:10)
从版本0.8开始,SQLAlchemy添加了直接支持
根据docs,connection.execute(table.insert().values(data))
应该可以解决问题。 (请注意,这不是 与connection.execute(table.insert(), data)
相同,这会通过调用executemany
导致许多单独的行插入。除了本地连接之外,性能上的差异可能是巨大的。
答案 5 :(得分:6)
SQLAlchemy在版本1.0.0
中介绍了
Bulk operations - SQLAlchemy docs
通过这些操作,您现在可以进行批量插入或更新!
例如(如果您希望简单表INSERT的开销最低),可以使用Session.bulk_insert_mappings()
:
loadme = [
(1, 'a')
, (2, 'b')
, (3, 'c')
]
dicts = []
for i in range(len(loadme)):
dicts.append(dict(bar=loadme[i][0], fly=loadme[i][1]))
s = Session()
s.bulk_insert_mappings(Foo, dicts)
s.commit()
或者,如果您愿意,可以跳过loadme
元组并将字典直接写入dicts
(但我发现更容易从数据中删除所有字数并加载字典列表在循环中。)
答案 6 :(得分:6)
Piere的回答是正确的,但有一个问题是bulk_save_objects
默认情况下不会返回对象的主键,如果你担心的话。将return_defaults
设置为True
即可获得此行为。
文档为here。
foos = [Foo(bar='a',), Foo(bar='b'), Foo(bar='c')]
session.bulk_save_objects(foos, return_defaults=True)
for foo in foos:
assert foo.id is not None
session.commit()
答案 7 :(得分:4)
这是一种方式:
values = [1, 2, 3]
Foo.__table__.insert().execute([{'bar': x} for x in values])
这将插入如下:
INSERT INTO `foo` (`bar`) VALUES (1), (2), (3)
参考:SQLAlchemy FAQ包含各种提交方法的基准。
答案 8 :(得分:4)
所有道路通往罗马,但其中一些穿越山脉,需要渡轮,但如果您想快速到达那里,只需走高速公路。
在这种情况下,高速公路将使用execute_batch()的psycopg2功能。文档说它是最好的:
executemany()
的当前实施(使用非常慈善的轻描淡写)并不是特别有效。这些函数可用于加速针对一组参数重复执行语句。通过减少服务器往返次数,性能可以比使用executemany()
好几个数量级。
在我自己的测试中execute_batch()
大约是executemany()
的两倍,并提供了配置page_size以进一步调整的选项(如果你想挤压最后一个)驱动程序性能的2-3%。)
使用use_batch_mode=True
create_engine()
设置为参数来使用SQLAlchemy,可以轻松启用相同的功能
答案 9 :(得分:1)
到目前为止,我找到的最佳答案是sqlalchemy文档:
有一个完整的可能解决方案基准的例子。
如文档中所示:
bulk_save_objects不是最佳解决方案,但性能正确。
在可读性方面,我认为第二个最好的实现是SQLAlchemy Core:
def test_sqlalchemy_core(n=100000):
init_sqlalchemy()
t0 = time.time()
engine.execute(
Customer.__table__.insert(),
[{"name": 'NAME ' + str(i)} for i in xrange(n)]
)
此功能的上下文在文档文章中给出。