SqlAlchemy映射批量更新 - 更安全,更快?

时间:2014-03-11 19:32:31

标签: python sql postgresql sqlalchemy

我使用的是Postgres 9.2和SqlAlchemy。目前,这是我的代码,用于更新我的数据库中Thing的排名:

lock_things = session.query(Thing).\
                filter(Thing.group_id == 4).\
                with_for_update().all()

tups = RankThings(lock_things) # return sorted tuple (<numeric>, <primary key Thing id>)
rank = 1
for prediction, id in tups:
    thing = session.query(Thing).\
        filter(Thing.group_id == 4).\
        filter(Thing.id == id).one()
    thing.rank = rank
    rank += 1

session.commit()

然而,这似乎很慢。它也是我想成为原子的东西,我为什么使用with_for_update()语法。

我觉得必须有一种方法来拉链&#34;查询,以这种方式更新。

如何在一次查询中更快地完成此操作?


编辑:我想我需要创建一个临时表来加入并进行快速更新,请参阅:

https://stackoverflow.com/a/20224370/712997

http://tapoueh.org/blog/2013/03/15-batch-update

如何在SqlAlchemy中执行此操作?

1 个答案:

答案 0 :(得分:2)

一般来说,通过这种操作,您可以做两件事:

  1. 不要在循环中执行查询

  2. 通过在SQL端执行计算来减少所需的查询数

  3. 此外,如果可能,您可能希望合并一些查询。

    让我们从2)开始,因为这是非常具体的,通常不容易实现。通常,这里最快的操作是编写返回排名的单个查询。这有两种选择:

    • 查询可以快速运行,因此您只需在需要排名时执行它。这将是一个非常简单的例子:

      SELECT 
          thing.*, 
          (POINTS_QUERY) as score
      FROM thing
      ORDER BY score DESC
      

      在这种情况下,这将通过一些人工评分给你一个有序的事物清单(例如,如果你建立某种竞争)。 POINTS_QUERY将使用子查询中的特定thing来确定其分数,例如汇总已解决的所有任务的点数。

      在SQLAlchemy中,这看起来像这样:

      score = session.query(func.sum(task.points)).filter(task.thing_id == Thing.id).correlate(Thing).label("score")
      thing_ranking = session.query(thing, score).order_by(desc("score"))
      

      这是SQLAlchemy的一些更高级的用法:我们构造一个子查询,返回我们标注score的标量值。使用correlate,我们告诉它thing将来自外部查询(这很重要)。

      因此,您可以运行单个查询,为您提供排名(根据列表中的索引确定排名,并取决于您的ranking strategy)。如果你能做到这一点,那就是最好的情况

    • 您想要缓存的值,查询本身很昂贵。这意味着您可以使用上面的解决方案并将值缓存在数据库之外(例如在dict中或使用缓存库)。或者您像上面一样计算它们但更新数据库字段(如Thing.rank)。同样,上面的查询给我们排名。另外,我假设最简单的排名:索引表示排名:

      for rank, (thing, score) in enumerate(thing_ranking):
           thing.rank = rank
      

      请注意我如何使用enumerate根据索引建立排名。另外,我利用了这个事实,因为我刚刚查询thing,我已经在会话中拥有它,所以不需要额外的查询。所以这可能是你的解决方案,但请继续阅读其他信息。

    使用上面的最后一个想法,我们现在可以解决1):在循环外获取查询。一般来说,我注意到你将一个事物列表传递给一个只能返回ID的排序函数。为什么?如果您可以更改它,请将其作为一个整体返回。

    但是,您可能无法更改此功能,因此如果我们无法更改此功能,请考虑我们的工作。我们已经列出了所有相关内容。我们得到了他们的ID的排序列表。那么为什么不建立dict作为ID的查找 - &gt;事?

    things_dict = dict(thing.id, thing for thing in lock_things)
    

    我们可以使用这个dict而不是在循环内查询:

    for prediction, id in tups:
        thing = things_dict[id]
    

    然而,可能(由于某些原因,我在您的示例中错过了)并非所有ID都先前已返回。在这种情况下(或者一般情况下),您可以利用SQLAlchemy保留的类似映射:您可以要求它提供主键,如果数据库已经拥有它,它将查询数据库:

    for prediction, id in tups:
        thing = session.query(Thing).get(id)
    

    这样我们就可以减少问题,只对我们不具备的对象执行查询。

    最后一件事:如果我们没有大部分内容怎么办?然后我没有解决你的问题,我只是替换了查询。在这种情况下,您将不得不创建一个新查询来获取所需的所有元素。一般来说,这取决于ID的来源以及如何确定它们,但是你总是可以采用效率最低的方式(这仍然比内部循环查询更快):使用SQL IN

    all_things = session.query(Thing).filter(Thing.group_id == 4).filter(Thing.id.in_([id for _, id in tups]).all()
    

    这将构建一个使用IN关键字进行过滤的查询。但是,由于存在大量事物,因此效率非常低,因此如果您处于这种情况下,最好在SQL中构建一种更有效的方法来确定这是否是您想要的ID。

    摘要

    所以这是一篇很长的文字。总结一下:

    • 如果您可以在那里有效地编写SQL中的查询

    • 使用SQLAlchemy的优势,例如:创建子查询

    • 尝试永远不要在循环中执行查询

    • 为自己创建一些映射(或者使用SQLAlchemy的映射)

    • 以pythonic的方式做:保持简单,明确。

    最后一个想法:如果您的查询变得非常复杂并且您担心您无法控制ORM执行的查询,请将其删除并使用Core。它几乎和ORM一样棒,并且在您自己构建查询时可以对查询进行大量控制。有了这个,您几乎可以构建任何您能想到的SQL查询,并且我确信您提到的批量更新也可以在这里(如果您看到上面的查询导致了许多UPDATE语句,您可能想要使用核心)。