python - Comment faire pour que SQLAlchemy dans Tornado soit asynchrone?





python-2.7 (5)


J'utilise tornado avec sqlalchemy de la manière suivante:


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

Dans ce cas, nous pouvons utiliser les modèles sqlalchemy et les outils sqlalchemy pour les requêtes de constructig.

Comment faire pour que SQLAlchemy dans Tornado soit async ? J'ai trouvé un exemple pour MongoDB sur un exemple async mongo mais je n'ai pas trouvé de motor pour SQLAlchemy . Est-ce que quelqu'un sait comment faire des requêtes SQLAlchemy à exécuter avec tornado.gen (J'utilise MySQL ci-dessous SQLAlchemy , au moment où mes gestionnaires lit à partir de la base de données et le résultat de retour, je voudrais faire async).




Les ORM sont mal adaptés à la programmation asynchrone explicite, c'est-à-dire que le programmeur doit produire des rappels explicites chaque fois que quelque chose qui utilise l'accès au réseau se produit. Une raison principale de ceci est que les ORM font un usage intensif du modèle de chargement paresseux , qui est plus ou moins incompatible avec l'async explicite. Code qui ressemble à ceci:

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

émettra en fait deux requêtes distinctes - une quand vous dites first() pour charger une ligne, et l'autre quand vous dites user.addresses , dans le cas où la collection .addresses n'est pas déjà présente, ou a expiré. Essentiellement, presque chaque ligne de code qui traite des constructions ORM pourrait bloquer sur IO, de sorte que vous seriez dans spaghetti de rappel en quelques secondes - et pour aggraver les choses, la grande majorité de ces lignes de code ne bloquera pas réellement sur IO, Ainsi, tous les coûts liés à la connexion des rappels pour ce qui serait autrement de simples opérations d'accès aux attributs rendront votre programme beaucoup moins efficace.

Un problème majeur avec les modèles asynchrones explicites est qu'ils ajoutent un surcroît énorme d'appels de fonctions Python aux systèmes complexes - pas seulement du côté de l'utilisateur comme avec un chargement paresseux, mais aussi du côté interne de l'abstraction autour du système. API de base de données Python (DBAPI). Pour que SQLAlchemy ait même un support asynchrone de base, cela imposerait de lourdes pénalités de performance à la grande majorité des programmes qui n'utilisent pas de modèles asynchrones, et même aux programmes asynchrones qui ne sont pas très concurrentiels. Considérez SQLAlchemy, ou n'importe quelle autre couche ORM ou abstraction, peut avoir le code comme suit:

def execute(connection, statement):
     cursor = connection.cursor()
     cursor.execute(statement)
     results = cursor.fetchall()
     cursor.close()
     return results

Le code ci-dessus effectue ce qui semble être une opération simple, en exécutant une instruction SQL sur une connexion. Mais en utilisant un DBAPI entièrement asynchrone comme l'extension async de psycopg2, le code ci-dessus bloque sur IO au moins trois fois. Donc, pour écrire le code ci-dessus dans un style asynchrone explicite, même si aucun moteur asynchrone n'est utilisé et que les callbacks ne bloquent réellement, l'appel de fonction externe ci-dessus devient au moins trois appels de fonction, au lieu d'un. par le système asynchrone explicite ou les appels DBAPI eux-mêmes. Ainsi, une application simple reçoit automatiquement une pénalité de 3 fois la surcharge de l'appel de fonction entourant une abstraction simple autour de l'exécution de l'instruction. Et en Python, la surcharge de l'appel de fonction est tout .

Pour ces raisons, je continue d'être moins excité par le hype entourant les systèmes asynchrones explicites, au moins dans la mesure où certains semblent vouloir tout async pour tout, comme la livraison de pages web (voir node.js). Je recommanderais d'utiliser des systèmes asynchrones implicites à la place, notamment gevent , où vous obtenez tous les avantages d'E / S non bloquants d'un modèle asynchrone et aucun des verbos structurels / inconvénients des callbacks explicites. Je continue à essayer de comprendre les cas d'utilisation pour ces deux approches, donc je suis intrigué par l'attrait de l'approche asynchrone explicite comme solution à tous les problèmes, comme vous le voyez avec node.js - nous utilisons des langages de script dans le première place pour réduire la verbosité et la complexité du code, et async explicite pour des choses simples comme la livraison de pages Web semble ne rien faire, mais ajouter un passe-partout qui peut être aussi bien automatisé par gevent ou similaire, si bloquer IO est même un problème dans un un cas comme celui-là (beaucoup de sites à volume élevé font bien avec un modèle d'E / S synchrone). Les systèmes basés sur Gevent sont éprouvés en production et leur popularité ne cesse de croître. Si vous appréciez l'automatisation des codes fournie par les ORM, vous pouvez également adopter l'automatisation d'ordonnancement asynchrone-IO fournie par un système comme gevent.

Mise à jour : Nick Coghlan a souligné son excellent article sur le sujet de l'async explicite ou implicite qui est aussi un must à lire ici. Et j'ai également été informé du fait que pep-3156 se réjouit maintenant de l'interopérabilité avec gevent , inversant son désintérêt précédemment déclaré pour gevent, en grande partie grâce à l'article de Nick. Donc à l'avenir je recommanderais un hybride de Tornado utilisant gevent pour la logique de base de données, une fois que le système d'intégration de ces approches sera disponible.




J'ai eu ce même problème dans le passé et je ne pouvais pas trouver une bibliothèque Async-MySQL fiable. Cependant, il existe une solution cool en utilisant Asyncio + Postgres . Vous avez juste besoin d'utiliser la bibliothèque aiopg , qui vient avec le support SQLAlchemy aiopg à l'emploi:

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



Pas de tornade, mais nous avons en quelque sorte fait de SQLAlchemy async en asyncio dans le projet 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())

Il ressemble un peu, mais en fait assez différent de SQLAlchemy ORM. Parce que nous n'avons utilisé qu'une partie du noyau de SQLAlchemy et construit un simple ORM au-dessus. Il utilise asyncpg dessous, donc c'est seulement pour PostgreSQL .

Mise à jour : GINO soutient maintenant Tornado, grâce à la contribution de Vladimir Goncharov. Voir les documents ici




La manière simple vérifie que la longueur est égale à zéro.

if len(a) == 0:
    print("a is empty")




python python-2.7 sqlalchemy tornado