如何让SQLAlchemy
中的Tornado
成为async
?
我在async mongo example上找到了MongoDB的示例,但我找不到像motor
SQLAlchemy
这样的内容。有没有人知道如何使用SQLAlchemy
执行tornado.gen
查询(我在MySQL
下面使用SQLAlchemy
,此时我的处理程序从数据库中读取并返回结果,我会喜欢这个异步)。
答案 0 :(得分:74)
ORM非常适合显式异步编程,也就是说,程序员必须在使用网络访问的任何时候产生显式回调。主要原因是ORM广泛使用lazy loading模式,这种模式或多或少与显式异步不兼容。代码如下:
user = Session.query(User).first()
print user.addresses
实际上会发出两个单独的查询 - 一个是当你说first()
要加载一行时,另一个是当你说user.addresses
时,.addresses
集合不是已经存在,或者已经过期。从本质上讲,几乎所有处理ORM结构的代码都可能会阻塞IO,所以你会在几秒钟之内进入广泛的回调意大利面 - 更糟糕的是,绝大多数代码行都不会实际< / em>阻止IO,所以将回调连接在一起的所有开销都会使你的程序效率大大降低。
显式异步模型的一个主要问题是它们会给复杂系统增加巨大的Python函数调用开销 - 不仅仅是面向用户的一方,就像延迟加载一样,而是内部方面以及系统如何提供围绕Python数据库API(DBAPI)的抽象。对于SQLAlchemy来说,即使拥有基本的异步支持,也会对绝大多数不使用异步模式的程序造成严重的性能损失,甚至是那些不是高度并发的异步程序。考虑SQLAlchemy或任何其他ORM或抽象层,可能具有如下代码:
def execute(connection, statement):
cursor = connection.cursor()
cursor.execute(statement)
results = cursor.fetchall()
cursor.close()
return results
上面的代码执行看似简单的操作,在连接上执行SQL语句。但是使用完全异步DBAPI(如psycopg2的异步扩展),上面的代码至少会在IO上阻塞三次。所以用显式异步样式编写上面的代码,即使没有使用异步引擎并且回调实际上没有阻塞,也意味着上面的外部函数调用至少变成了三个函数调用,而不是一个,不包括强加的开销通过显式异步系统或DBAPI调用自身。因此,一个简单的应用程序会自动给出围绕语句执行的简单抽象的函数调用开销的3倍的惩罚。在Python中,function call overhead is everything。
由于这些原因,我对围绕显式异步系统的炒作持续不那么兴奋,至少在一定程度上,有些人似乎想要对所有事情都进行异步,比如提供网页(参见node.js) 。我建议使用隐式异步系统,最值得注意的是gevent,它可以获得异步模型的所有非阻塞IO优势,而不会显示明确回调的结构冗余/缺点。我继续尝试理解这两种方法的用例,所以我对显式异步方法作为所有问题的解决方案的吸引力感到困惑,就像你在node.js中看到的那样 - 我们正在使用脚本语言减少详细程度和代码复杂性的第一个地方,以及提供网页这样的简单事物的显式异步似乎什么都不做,只是添加样板,如果阻塞IO甚至是一个问题,那么也可以通过gevent或类似方法实现自动化。这样的情况(大量的高容量网站使用同步IO模型做得很好)。基于Gevent的系统已经过生产验证,而且它们越来越受欢迎,因此如果您喜欢ORM提供的代码自动化,您可能还希望采用像gevent这样的系统提供的async-IO-scheduling自动化。
更新:Nick Coghlan指出他的great article on the subject of explicit vs. implicit async也是必读的。而且我也更新了pep-3156 now welcomes interoperability with gevent这一事实,扭转了之前所说的对gevent的不感兴趣,这在很大程度上要归功于Nick的文章。因此,一旦集成了这些方法的系统可用,我将推荐将Tornado混合使用gevent作为数据库逻辑。
答案 1 :(得分:24)
我过去遇到过同样的问题,但我找不到可靠的Async-MySQL库。但是,使用Asyncio + Postgres 有一个很酷的解决方案。您只需要使用aiopg库,它随附SQLAlchemy支持:
import asyncio
from aiopg.sa import create_engine
import sqlalchemy as sa
metadata = sa.MetaData()
tbl = sa.Table('tbl', metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('val', sa.String(255)))
@asyncio.coroutine
def go():
engine = yield from create_engine(user='aiopg',
database='aiopg',
host='127.0.0.1',
password='passwd')
with (yield from engine) as conn:
yield from conn.execute(tbl.insert().values(val='abc'))
res = yield from conn.execute(tbl.select().where(tbl.c.val=='abc'))
for row in res:
print(row.id, row.val)
loop = asyncio.get_event_loop()
loop.run_until_complete(go())
答案 2 :(得分:2)
我正在使用sqlalchemy龙卷风:
from tornado_mysql import pools
from sqlalchemy.sql import table, column, select, join
from sqlalchemy.dialects import postgresql, mysql
# from models import M, M2
t = table(...)
t2 = table(...)
xxx_id = 10
j = join(t, t2, t.c.t_id == t2.c.id)
s = select([t]).select_from(j).where(t.c.xxx == xxx_id)
sql_str = s.compile(dialect=mysql.dialect(),compile_kwargs={"literal_binds": True})
pool = pools.Pool(conn_data...)
cur = yield pool.execute(sql_str)
data = cur.fetchone()
在这种情况下,我们可以使用sqlalchemy模型和sqlalchemy工具进行构造查询。
答案 3 :(得分:0)
我正以下面的方式在sqlalchemy中使用tornado6:
from tornado.ioloop import IOLoop
def sql_function():
pass
class Handler(tornado.web.RequestHandler):
async def post(self):
args = get_front_end_args()
result = await IOLoop.current().run_in_executor(None,sql_function,*(args))
self.write({"result":result})
答案 4 :(得分:0)
SQLAlchemy 1.4 原生支持 asyncio(目前处于测试阶段):
https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html