簡體   English   中英

如何使 Tornado 中的 SQLAlchemy 異步?

[英]How to make SQLAlchemy in Tornado to be async?

如何使Tornado SQLAlchemy async 我在async mongo 示例上找到了 MongoDB 的示例,但我找不到任何類似於motor for SQLAlchemy東西。 有誰知道如何使用tornado.gen執行SQLAlchemy查詢(我在SQLAlchemy使用MySQL ,目前我的處理程序從數據庫讀取並返回結果,我想讓這個異步)。

ORM 不太適合顯式異步編程,也就是說,程序員必須在任何使用網絡訪問的情況發生時生成顯式回調。 一個主要原因是 ORM 廣泛使用延遲加載模式,這或多或少與顯式異步不兼容。 看起來像這樣的代碼:

user = Session.query(User).first()
print user.addresses

實際上會發出兩個單獨的查詢-一個當你說first()來加載一排,而下時,你說user.addresses ,在的情況下,該.addresses集已不存在,或者已經過期。 從本質上講,幾乎每一行處理 ORM 結構的代碼都可能在 IO 上阻塞,所以你會在幾秒鍾內陷入大量的回調意大利面——更糟糕的是,這些代碼行中的絕大多數實際上不會在 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 語句。 但是使用像 psycopg2 的異步擴展這樣的完全異步 DBAPI,上面的代碼在 IO 上至少阻塞了 3 次。 因此,以顯式異步風格編寫上述代碼,即使沒有使用異步引擎並且回調實際上沒有阻塞,也意味着上述外部函數調用至少變為三個函數調用,而不是一個,不包括施加的開銷通過顯式異步系統或 DBAPI 調用自身。 因此,圍繞語句執行的簡單抽象,一個簡單的應用程序會自動受到 3 倍的函數調用開銷的懲罰。 而在 Python 中, 函數調用開銷就是一切

由於這些原因,我對圍繞顯式異步系統的炒作仍然不那么興奮,至少在某種程度上,有些人似乎想要對所有事情都采用異步,比如交付網頁(參見 node.js)。 我建議改用隱式異步系統,最顯着的是gevent ,在那里您可以獲得異步模型的所有非阻塞 IO 優點,並且沒有顯式回調的結構冗長/缺點。 我繼續嘗試理解這兩種方法的用例,所以我對顯式異步方法作為所有問題的解決方案的吸引力感到困惑,即正如您在 node.js 中看到的那樣 - 我們在第一個減少冗長和代碼復雜性的地方,對於像交付網頁這樣的簡單事情的顯式異步似乎什么都不做,只是添加可以由 gevent 或類似工具自動化的樣板,如果阻塞 IO 在一個像這樣的情況(很多大容量網站都可以使用同步 IO 模型)。 基於 Gevent 的系統已在生產環境中得到驗證,並且它們的受歡迎程度正在增長,因此如果您喜歡 ORM 提供的代碼自動化,您可能還希望采用像 gevent 這樣的系統提供的異步 IO 調度自動化。

更新:Nick Coghlan 指出了他關於顯式與隱式異步主題的精彩文章,這也是這里必須閱讀的內容。 而且我還了解到, pep-3156 現在歡迎與 gevent 的互操作性,扭轉了先前聲明的對 gevent 不感興趣的事實,這在很大程度上要歸功於 Nick 的文章。 因此,將來,一旦集成這些方法的系統可用,我將推薦使用 gevent 作為數據庫邏輯的 Tornado 的混合體。

我過去遇到過同樣的問題,但找不到可靠的 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)))

async def create_table(engine):
    async with engine.acquire() as conn:
        await conn.execute('DROP TABLE IF EXISTS tbl')
        await conn.execute('''CREATE TABLE tbl (
                                  id serial PRIMARY KEY,
                                  val varchar(255))''')

async def go():
    async with create_engine(user='aiopg',
                             database='aiopg',
                             host='127.0.0.1',
                             password='passwd') as engine:

        async with engine.acquire() as conn:
            await conn.execute(tbl.insert().values(val='abc'))

            async for row in conn.execute(tbl.select()):
                print(row.id, row.val)

loop = asyncio.get_event_loop()
loop.run_until_complete(go())

如@cglacet所述更新

不是龍卷風,但是我們做排序異步的SQLAlchemy在ASYNCIO GINO項目

import asyncio
from gino import Gino, enable_task_local
from sqlalchemy import Column, Integer, Unicode, cast

db = Gino()


class User(db.Model):
    __tablename__ = 'users'

    id = Column(Integer(), primary_key=True)
    nickname = Column(Unicode(), default='noname')


async def main():
    await db.create_pool('postgresql://localhost/gino')

    # Create object, `id` is assigned by database
    u1 = await User.create(nickname='fantix')
    print(u1.id, u1.nickname)  # 1 fantix

    # Retrieve the same row, as a different object
    u2 = await User.get(u1.id)
    print(u2.nickname)  # fantix

    # Update affects only database row and the operating object
    await u2.update(nickname='daisy')
    print(u2.nickname)  # daisy
    print(u1.nickname)  # fantix

    # Returns all user objects with "d" in their nicknames
    users = await User.query.where(User.nickname.contains('d')).gino.all()

    # Find one user object, None if not found
    user = await User.query.where(User.nickname == 'daisy').gino.first()

    # Execute complex statement and return command status
    status = await User.update.values(
        nickname='No.' + cast(User.id, Unicode),
    ).where(
        User.id > 10,
    ).gino.status()

    # Iterate over the results of a large query in a transaction as required
    async with db.transaction():
        async for u in User.query.order_by(User.id).gino.iterate():
            print(u.id, u.nickname)


loop = asyncio.get_event_loop()
enable_task_local(loop)
loop.run_until_complete(main())

它看起來有點像,但實際上與 SQLAlchemy ORM大不相同 因為我們只使用了 SQLAlchemy 核心的一部分,並在其上構建了一個簡單的 ORM。 它在下面使用asyncpg ,因此它僅適用於 PostgreSQL

更新:GINO 現在支持 Tornado,感謝 Vladimir Goncharov 的貢獻。 在此處查看文檔

我正在以下一種方式將龍卷風與 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 工具來構建查詢。

我正在以下一種方式將 tornado6 與 sqlalchemy 一起使用:

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})

SQLAlchemy 1.4 原生支持 asyncio(目前處於測試階段):

https://docs.sqlalchemy.org/en/14/orm/extensions/asyncio.html

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM