【发布时间】:2021-06-16 05:24:16
【问题描述】:
总结:我正在运行一个简单(但很长)的查询,在该查询中,我正在获取与 source_id/owner_id 对列表中的条目匹配的所有行。没有连接,没有什么复杂的。 SQLAlchemy 生成的 SQL 语句直接运行大约需要 200ms,但通过 SQLAlchemy 运行需要 2 分钟以上。
我的模型课:
class MyModel(BaseModel):
__table_args__ = {"schema": "my_database.dbo"}
__tablename__ = "tblMyModel"
source_id = Column("SourceID", INT, primary_key=True)
owner_id = Column("OwnerID", INT, primary_key=True)
part_number = Column("PartNumber", NVARCHAR(64), nullable=False)
查询:
class ModelKey(NamedTuple):
source_id: int
owner_id: int
def get_by_ids(
session: Session, id_keys: Set[ModelKey]
) -> List[MyModel]:
filters = [
and_(
MyModel.source_id == key.source_id,
MyModel.owner_id == key.owner_id,
)
for key in id_keys
]
query = session.query(MyModel).filter(or_(*filters))
result = query.all()
return result
(我们将会话传递给此方法,因为我们正在跨多个查询和插入进行会话管理。在调用 get_by_ids() 之前,还有一些其他 SQL 查询被调用,但没有插入操作。)
id_keys 中最多可包含 400 个条目。我检查了 SQLAlchemy 生成的 SQL 语句:
SELECT
my_database.dbo.tblMyModel.SourceID,
my_database.dbo.tblMyModel.OwnerID,
my_database.dbo.tblMyModel.PartNumber
FROM my_database.dbo.tblMyModel
WHERE
my_database.dbo.tblMyModel.SourceID = 18396 AND my_database.dbo.tblMyModel.OwnerID = 99312703
OR my_database.dbo.tblMyModel.SourceID = 18396 AND my_database.dbo.tblMyModel.OwnerID = 99255569
OR my_database.dbo.tblMyModel.SourceID = 34512 AND my_database.dbo.tblMyModel.OwnerID = 8675309
...
OR my_database.dbo.tblMyModel.SourceID = 36785 AND my_database.dbo.tblMyModel.OwnerID = 1234567
这是一个很长的查询(464 个OR 子句),但是如果我使用 SQLAlchemy 生成的 SQL 语句并直接在 DBeaver 中运行它,它会在大约 200 毫秒内执行,而对同一个数据库运行相同查询的 SQLAlchemy 会接管2 分钟。
我使用SQLAlchemy suggetions 分析了我的代码并发现了以下内容:
ncalls tottime percall cumtime percall filename:lineno(function)
1 113.133 113.133 113.133 113.133 {method 'execute' of 'pyodbc.Cursor' objects}
1 3.578 3.578 3.578 3.578 {method 'fetchall' of 'pyodbc.Cursor' objects}
1 0.033 0.033 0.035 0.035 selectable.py:3144(_froms)
18348 0.006 0.000 0.006 0.000 {built-in method builtins.isinstance}
1 0.005 0.005 0.005 0.005 {method 'close' of 'pyodbc.Cursor' objects}
930 0.005 0.000 0.014 0.000 elements.py:965(__init__)
930 0.005 0.000 0.008 0.000 elements.py:3251(__init__)
933 0.005 0.000 0.013 0.000 compiler.py:866(visit_column)
930 0.004 0.000 0.037 0.000 compiler.py:1361(_generate_generic_binary)
3264/1 0.004 0.000 0.086 0.086 visitors.py:87(_compiler_dispatch)
根据 SQLAlchemy 的文档,“这表明数据库需要很长时间才能开始返回结果,这意味着应该通过添加索引或重组查询和/或底层架构来优化您的查询。”但正如我所说,查询本身在 SQLAlchemy 之外运行时运行得非常快。
感谢任何帮助!
【问题讨论】:
-
那么多
OR子句总是很困难。可能优化器能够将查询的一个版本转换为VALUES表上的连接,而另一个版本不能。看起来您应该使用两列表值参数或临时表,然后加入它。 -
我想知道它是否在运行时将值内联到查询中与发送带有占位符的查询,以及这是否会影响查询计划。
-
Ilja 这是一个非常好的问题,查看我们的 DataDog 仪表板中的查询显示
WHERE my_database.dbo. [ tblMyModel ] . [ SourceID ] = ? AND my_database.dbo. [ tblMyModel ] . [ OwnerID ] = ? OR ...但我不知道这是否是 DataDog 格式化查询。当我将 sqlalchemy 查询编译成 SQL 时,我调用了query.statement.compile(compile_kwargs={"literal_binds": True})),这可能与实际查询不同,因为我将literal_binds设置为 true。
标签: python sql sql-server sqlalchemy