小编典典

如何配置SQLAlchemy驱动的应用程序?

python

有没有人对Python / SQLAlchemy应用进行性能分析?找到瓶颈和设计缺陷的最佳方法是什么?

我们有一个Python应用程序,其中数据库层由SQLAlchemy处理。该应用程序使用批处理设计,因此许多数据库请求是在有限的时间内顺序执行的。当前运行时间太长,因此需要一些优化。我们不使用ORM功能,数据库是PostgreSQL。


阅读 176

收藏
2020-12-20

共1个答案

小编典典

有时,仅是普通的SQL日志记录(通过python的日志记录模块或通过上的echo=True参数启用create_engine())就可以使您了解需要花费多长时间。例如,如果您在执行SQL操作后立即记录某些内容,则您会在日志中看到类似以下内容的内容:

17:37:48,325 INFO  [sqlalchemy.engine.base.Engine.0x...048c] SELECT ...
17:37:48,326 INFO  [sqlalchemy.engine.base.Engine.0x...048c] {<params>}
17:37:48,660 DEBUG [myapp.somemessage] 

如果您myapp.somemessage在操作后立即登录,则知道完成SQL部分操作花费了334ms。

记录SQL还可以说明是否发出了数十/百个查询,而这些查询可以通过联接更好地组织成更少的查询。使用SQLAlchemy ORM时,提供了“紧急加载”功能,可以部分(contains_eager())或完全(eagerload(),eagerload_all())自动执行此活动,但是如果没有ORM,则仅意味着要使用联接,以便可以将多个表中的结果加载到一个结果集中而不是随着查询深度的增加而乘以查询数量(即r + r*r2 + r*r2*r3…)

如果日志记录表明单个查询花费的时间太长,则需要对数据库处理查询,通过网络发送结果,由DBAPI处理,最后由SQLAlchemy的结果集接收到的时间进行细分。和/或ORM层。这些阶段中的每一个阶段都可以根据具体情况呈现自己的瓶颈。

为此,您需要使用分析,例如cProfile或hotshot。这是我使用的装饰器:

import cProfile as profiler
import gc, pstats, time

def profile(fn):
    def wrapper(*args, **kw):
        elapsed, stat_loader, result = _profile("foo.txt", fn, *args, **kw)
        stats = stat_loader()
        stats.sort_stats('cumulative')
        stats.print_stats()
        # uncomment this to see who's calling what
        # stats.print_callers()
        return result
    return wrapper

def _profile(filename, fn, *args, **kw):
    load_stats = lambda: pstats.Stats(filename)
    gc.collect()

    began = time.time()
    profiler.runctx('result = fn(*args, **kw)', globals(), locals(),
                    filename=filename)
    ended = time.time()

    return ended - began, load_stats, locals()['result']

要分析一段代码,请将其放置在带有装饰器的函数中:

@profile
def go():
    return Session.query(FooClass).filter(FooClass.somevalue==8).all()
myfoos = go()

概要分析的输出可用于给出花费时间的想法。例如,如果您看到所有时间都用在上cursor.execute(),那是对数据库的低级DBAPI调用,这意味着应该通过添加索引或重组查询和/或基础架构来优化查询。对于该任务,我建议使用pgadmin及其图形化EXPLAIN实用程序,以查看查询正在执行的工作类型。

如果看到成千上万的与获取行有关的调用,则可能意味着查询返回的行比预期的多-由于联接不完全而导致的笛卡尔乘积会导致此问题。另一个问题是在类型处理上花费的时间-一种SQLAlchemy类型,例如Unicode将对绑定参数和结果列执行字符串编码/解码,这并不是在所有情况下都需要的。

配置文件的输出可能有点令人生畏,但经过一些练习后,它们很容易阅读。邮件列表上曾经有人声称运行缓慢,在让他发布配置文件的结果后,我能够证明速度问题是由于网络延迟所致-花费在cursor.execute()以及所有Python中的时间方法非常快,而大部分时间都花在socket.receive()上。

如果您有雄心壮志,那么在http://www.sqlalchemy.org/trac/browser/sqlalchemy/trunk/test/aaa_profiling中,也可以在SQLAlchemy单元测试中找到更多涉及SQLAlchemy分析的示例。在那里,我们进行了使用装饰器的测试,该装饰器声明了用于特定操作的最大方法调用数,因此,如果检入效率低下的东西,测试将揭示该方法调用(请注意,在Python中,函数调用具有最高的任何操作的开销,并且调用次数通常与所花费的时间几乎不成比例)。值得注意的是“ zoomark”测试,它使用了一种奇特的“ SQL捕获”方案,该方案从等式中削减了DBAPI的开销-尽管该技术不是“

2020-12-20