SQLAlchemy 'bulk_save_objects' vs 'add_all' 底层逻辑差异?
·
问题:SQLAlchemy 'bulk_save_objects' vs 'add_all' 底层逻辑差异?
考虑以下三种使用 sqlalchemy ORM 插入对象的方法:
(1)
for obj in objects:
session.add(obj)
(2)
session.add_all(objects)
(3)
session.bulk_save_objects(objects)
假设objects[]
的长度是50000
-
方法(1)是否形成并发送
50000
插入SQL查询? -
方法(2)是否只形成并发送
1
SQL查询? -
方法(3)是否只形成并发送
1
SQL查询?
我知道这三种方法的速度差别很大。但是关于底层实现细节有什么区别?
解答
(2) 基本上实现了作为 (1),如果 ORM 必须获取生成的值(例如主键),则两者都可能在刷新期间发出 50,000 次插入。如果这 50,000 个对象具有级联关系,它们甚至可能发出更多。
In [4]: session.add_all([Foo() for _ in range(5)])
In [5]: session.commit()
BEGIN (implicit)
INSERT INTO foo DEFAULT VALUES RETURNING foo.id
{}
... (repeats 3 times)
INSERT INTO foo DEFAULT VALUES RETURNING foo.id
{}
COMMIT
如果您事先提供主键和其他数据库生成的值,那么当参数匹配时,Session
可以将单独的插入组合到单个“executemany”操作中。
In [8]: session.add_all([Foo(id=i) for i in range(5)])
In [9]: session.commit()
BEGIN (implicit)
INSERT INTO foo (id) VALUES (%(id)s)
({'id': 0}, {'id': 1}, {'id': 2}, {'id': 3}, {'id': 4})
COMMIT
如果您的 DB-API 驱动程序实现executemany()
或等效使用允许它发出具有多个数据的单个语句的方法,那么它可以导致单个查询。例如,在启用executemany_mode='values'
后,Postgresql 日志包含上述内容
LOG: statement: INSERT INTO foo (id) VALUES (0),(1),(2),(3),(4)
批量操作跳过大部分Session
机制——例如持久化相关对象——以换取性能提升。例如,默认情况下,它不获取默认值,例如主键,这允许它尝试和批量更改操作和参数匹配的更少的“executemany”操作。
In [12]: session.bulk_save_objects([Foo() for _ in range(5)])
BEGIN (implicit)
INSERT INTO foo DEFAULT VALUES
({}, {}, {}, {}, {})
In [13]: session.commit()
COMMIT
它可能仍会发出多个语句,这再次取决于数据和正在使用的 DB-API 驱动程序。文档是一个很好的阅读。
使用psycopg2 快速执行助手启用上述在 Postgresql 日志中产生
LOG: statement: INSERT INTO foo DEFAULT VALUES;INSERT INTO foo DEFAULT VALUES;INSERT INTO foo DEFAULT VALUES;INSERT INTO foo DEFAULT VALUES;INSERT INTO foo DEFAULT VALUES
换句话说,多个语句已加入到发送到服务器的“单个”语句中。
所以,最终所有 3 的答案都是“视情况而定”,这当然看起来令人沮丧。
更多推荐
所有评论(0)