使用 sqlalchemy 会话执行 sql 会大大减慢执行时间
Posted
技术标签:
【中文标题】使用 sqlalchemy 会话执行 sql 会大大减慢执行时间【英文标题】:Using sqlalchemy session to execute sql DRASTICALLY slows execution time 【发布时间】:2013-05-10 18:47:54 【问题描述】:我有一个相当长的查询(以前是 7 个连接,现在是 7 个子选择,因为在原始 sql 中,7 个子选择要快得多——我什至不知道如果我让它运行 7 个连接什么时候完成,但超过 1 分钟,而子选择为 0.05-.1 秒)
当我在数据库上运行它时,正如我所说,它需要 0.05-.1 秒才能执行。只需使用session.execute()
即可将其速度减慢一分钟!
有什么我可以做的吗?
如果您需要更多信息,请告诉我——我有点怀疑这是一般的 sqlalchemy 事情——比如 sqlalchemy 可能正在设置查询计划,而不是让 mysql 去做?还是……?
编辑:对两者都进行了解释,它们看起来相同,只是 sqlalchemy 在extra
列中添加了“使用临时;使用文件排序”。这就是让它变慢的原因吗?我该如何阻止它这样做?
编辑 2:绝对是 sqlalchemy。我尝试使用 MySQL 游标而不是 SA 会话来执行并获得相同的 0.05 秒运行时间。
编辑 3:
创建引擎的代码:
engine_ro = create_engine(
config.ro_database_url, #string with username, password, db
pool_size=config.database_pool_size, #int
max_overflow=config.database_max_overflow, #int
pool_timeout=config.database_timeout, # int
echo=config.database_echo, #False
echo_pool=config.database_echo, #same as echo #False
listeners=[GoneAway()] if config.database_use_listeners else None)
其中GoneAway()
是执行SELECT 1
以检查连接的方法。
创建会话对象:
SessionRO = scoped_session(sessionmaker(bind=engine_ro, autocommit=False))
scoped_session
和 sessionmaker
是 sqlalchemy 函数。
然后,执行查询的代码:
session = SessionRO()
results = session.execute(sql, params)
编辑 4:如果有人想知道,如果我注释掉 listeners
位,它仍然很慢。同样,如果我只使用 sessionmaker
而不使用 scoped_session。
【问题讨论】:
sqlalchemy
没有设置查询计划,或者其他任何花哨的东西。它只是生成 SQL 并通过 DB-API-2.0 连接发送它。您可以通过各种方式准确查看它生成的 SQL,但最简单的方法是将 echo=True
作为 create_engine
调用的额外参数传递。
您可能想先查看 SQL 的差异,然后再查看 EXPLAIN 和/或 EXPLAIN QUERY PLAN 的差异。
或者可能有一个快速解决方案使您的问题无关紧要:将您的长查询(或其中相当大的一部分)转换为一个 VIEW,然后对该 VIEW 执行一个简单的查询。 (如果幸运的话,这也可以让 MySQL 一开始就更好地优化查找。)
查询是一样的,到你的第二点。实际上,此时我正面临着这个任务,因为我们正在使用视图并且它们非常慢,所以我认为将其转回视图不会有帮助。似乎 sqlalchemy 在查询计划中发挥了 some 的作用,因为正如我所说,当我在 sqlalchemy 中运行解释时,额外的列是不同的。
session.execute(),就与您发送的字符串的交互以及如何使用 DBAPI 而言,是非常基本的。不会发出诸如“使用临时/文件排序”之类的东西或类似的东西,至少它会是 MySQL-python DBAPI 做的。您需要在此处(或在邮件列表中)向我们说明您是如何创建 Engine
、Session
以及如何在其上调用 execute()
,与您在测试普通 MySQL 游标时所做的对比. SQL 本身也会有所帮助,但这里的调用方式将有助于诊断。
【参考方案1】:
sqlalchemy
没有设置查询计划或其他任何花哨的东西。它只是生成 SQL 并通过 DB-API-2.0 连接发送它。因此,如果您使用sqlalchemy
生成的相同语句显式调用execute
,它将以完全相同的方式运行。*
查看sqlalchemy
正在生成什么查询的最简单方法是将echo=True
作为create_engine
调用的额外参数传递。
在您的情况下,sqlalchemy
生成的查询实际上与您的手动查询不同,因为它使用字符串而不是 int 测试整数参数。
* 这不是 100% 保证的;您必须确保 DB-API-2.0 connect
函数中的任何连接参数都相同,并且您和 sqlalchemy
都没有执行任何 PRAGMA
语句。但是您可以使用与测试查询本身相同的方式来测试它们。
【讨论】:
【参考方案2】:这是一个真实的测试套件,用于将 MySQL 游标与 SQLAlchemy 引擎和会话进行比较。请在底部替换您的连接信息和 SQL,然后运行它。让我们知道时间安排。
import time
def time_thing(fn, description):
print "Running %s" % description
now = time.time()
try:
ret = fn()
return ret
finally:
spent = time.time() - now
print "Finished %s, took %d seconds" % (description, spent)
def with_mysqldb(sql):
import MySQLdb
conn = MySQLdb.connect(db=DBNAME, user=USERNAME, passwd=PASSWORD, host=HOST)
def go():
cursor = conn.cursor()
cursor.execute(sql)
# if result fetching is the issue:
# cursor.fetchall()
cursor.close()
time_thing(go, "Executing SQL with MySQLdb cursor")
def _sqla_engine_w_test_connection():
from sqlalchemy import create_engine
eng = create_engine(SQLALCHEMY_URL)
def test():
conn = eng.connect()
result = conn.execute("select 1")
assert result.fetchall()[0] == (1, )
time_thing(test, "Making a test connection...")
return eng
def with_sqlalchemy(sql):
eng = _sqla_engine_w_test_connection()
def go():
result = eng.execute(sql)
# if result fetching is the issue:
# result.fetchall()
result.close()
time_thing(go, "Executing SQL with SQLA engine")
def with_sqlalchemy_session(sql):
from sqlalchemy.orm import Session
eng = _sqla_engine_w_test_connection()
def go():
sess = Session(eng)
result = sess.execute(sql)
# if result fetching is the issue:
# result.fetchall()
result.close()
time_thing(go, "Executing SQL SQLA session")
SQLALCHEMY_URL = "mysql://scott:tiger@localhost/test"
DBNAME = "test"
HOST = "localhost"
USERNAME = "scott"
PASSWORD = "tiger"
SQL = "SELECT 1"
with_mysqldb(SQL)
with_sqlalchemy(SQL)
with_sqlalchemy_session(SQL)
【讨论】:
这只会让我更加困惑,因为它证明了每个人的观点(他们都在 2 秒内完成),但说真的,如果我将 mysql 连接代码复制并粘贴到我的并运行它在我的完整上下文中(网络服务器访问了一个访问数据库的 rest api),它立即返回。即使使用 fetchall() 并迭代事物。有什么不同?!!!!!! 给你答案,因为你向我证明是我,即使没有人发现实际问题,我将发布。 编辑:将答案归功于 abarnert,因为他们有实际的答案,但给你赏金,因为你付出了很多努力,帮助我真正意识到发生了什么。【参考方案3】:您使用的是哪个 DBAPI?也许尝试将其更改为其他内容。我现在正在使用 PostgreSQL,并且在 pypostgresql 和 psycopg2 之间的性能差异很大(后者要快得多)。
有关 MySQL 的可用 DBAPI 列表,请参阅 SQLAchemy 文档:第 4.1.5 章。
【讨论】:
链接?我在 sqlalchemy 文档中看不到明显的章节编号,只是一个主题列表,搜索索引只会产生 db api error 的东西。编辑:nvm,找到了。但是,是的,在这里任何地方都看不到章节编号,所以将来链接会有所帮助 另外,回答您的问题:我们……没有使用任何?我们的连接字符串只是“mysql://在控制台版本中,我粘贴了正确的内容。 在 SqlAlchemy 版本中,我正在测试 带有字符串的 int 参数。
【讨论】:
以上是关于使用 sqlalchemy 会话执行 sql 会大大减慢执行时间的主要内容,如果未能解决你的问题,请参考以下文章
Flask-SQLAlchemy - 会话如何与多个数据库一起工作?