Python 一个简单(但很长)的SQLAlchemy filter()运行速度非常慢(SQL Server)

Python 一个简单(但很长)的SQLAlchemy filter()运行速度非常慢(SQL Server),python,sql,sql-server,sqlalchemy,Python,Sql,Sql Server,Sqlalchemy,小结:我正在运行一个简单(但很长)的查询,其中我将获取与列表中的一个条目匹配的所有行以及一个source\u id/owner\u id对。没有连接,没有复杂的东西。SQLAlchemy生成的SQL语句在直接运行时需要约200毫秒,但在通过SQLAlchemy运行时需要超过2分钟 我的模型课: 类MyModel(基本模型): __表_args_uuu={“schema”:“my_database.dbo”} __tablename=“tblMyModel” source\u id=Column(

小结:我正在运行一个简单(但很长)的查询,其中我将获取与列表中的一个条目匹配的所有行以及一个source\u id/owner\u id对。没有连接,没有复杂的东西。SQLAlchemy生成的SQL语句在直接运行时需要约200毫秒,但在通过SQLAlchemy运行时需要超过2分钟

我的模型课:

类MyModel(基本模型):
__表_args_uuu={“schema”:“my_database.dbo”}
__tablename=“tblMyModel”
source\u id=Column(“SourceID”,INT,primary\u key=True)
所有者id=列(“所有者id”,INT,主键=True)
零件号=列(“零件号”,NVARCHAR(64),可空=假)
查询:

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\u by\u id()
之前,会调用一些其他SQL查询,但不会执行插入操作。)

id\u键
最多可包含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
子句),但如果我使用SQLAlchemy生成的SQL语句并直接在DBeaver中运行它,它将在大约200毫秒内执行,而SQLAlchemy对同一数据库运行同一查询需要2分钟以上

我使用分析了我的代码,发现了以下内容:

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之外运行时,查询本身运行速度非常快


感谢您的帮助

我通过使用OPENJSON解决了这个问题:

class ModelKey(NamedTuple):
源代码:int
所有者id:int
def通过ID获取(
会话:会话,id_键:设置[ModelKey]
)->列表[MyModel]:
关键指令=[
{“source\u id”:key.source\u id,“owner\u id”:key.owner\u id}
用于输入id_键
]
json_params=json.dumps(list(key_dicts))
sql=文本(
"""
选择tmm.SourceID、tmm.OwnerID、tmm.PartNumber
来自OPENJSON(:json)和(
source\u id INT'$.source\u id',
所有者id整型“$.owner\u id”
)作为json
内部连接my_database.dbo.tblMyModel tmm
在json.source_id=tmm.SourceID和json.owner_id=tmm.OwnerID上
"""
).params(json=json_params)
结果=session.execute(sql).fetchall()
型号=[]
对于结果中的行:
row\u dict=dict(row.items())
m=MyModel(
source\u id=行目录[“SourceID”],
owner\u id=行目录[“OwnerID”],
零件号=行号[“零件号”],
)
模型。附加(m)
返回模型
这个版本的查询(在SQLAlchemy中执行)大约需要5毫秒,这是对原始版本的巨大改进,原始版本大约需要两分钟。(这是在同一硬件上使用相同的
id\u键运行的。)


我仍然不知道为什么最初的版本这么慢,如果有人有任何见解的话,我想这样做。

那么多
子句总是很困难的。可能优化器能够将查询的一个版本转换为
VALUES
表上的联接,而另一个版本则不能。看起来您应该使用一个两列表值的参数或临时表,并将其连接起来。我想知道在运行与发送带有占位符的查询时,它是否会将值内联到查询中,以及这是否会影响查询计划。Ilja这是一个非常好的问题,查看DataDog仪表板中的查询,将显示my_database.dbo所在的
。[tblMyModel]。[SourceID]=?还有我的_database.dbo。[tblMyModel]。[所有者ID]=?或者…
,但我不知道这是否是DataDog格式化查询。当我将sqlalchemy查询编译成SQL时,我调用了
query.statement.compile(compile\u kwargs={“literal\u binds”:True})
,这可能与实际查询不同,因为我将
literal\u binds设置为True。