Python 如何在数据库中使tornado请求原子化

Python 如何在数据库中使tornado请求原子化,python,transactions,tornado,atomic,python-decorators,Python,Transactions,Tornado,Atomic,Python Decorators,我有一个用Tornado异步框架编写的python应用程序。当HTTP请求传入时,将调用此方法: @classmethod def my_method(cls, my_arg1): # Do some Database Transaction #1 x = get_val_from_db_table1(id=1, 'x') y = get_val_from_db_table2(id=7, 'y') x += x + (2 * y) # Do some

我有一个用Tornado异步框架编写的python应用程序。当HTTP请求传入时,将调用此方法:

@classmethod
def my_method(cls, my_arg1):

    # Do some Database Transaction #1
    x = get_val_from_db_table1(id=1, 'x')
    y = get_val_from_db_table2(id=7, 'y')
    x += x + (2 * y) 

    # Do some Database Transaction #2
    set_val_in_db_table1(id=1, 'x', x)

    return True
这三个数据库操作是相互关联的。这是一个并发应用程序,因此多个这样的HTTP调用可以并发发生,并且访问同一个数据库

出于数据完整性的目的,重要的是此方法中的三个数据库操作都被调用,而不需要另一个进程读取或写入中间的数据库行


如何确保此方法具有数据库原子性?Tornado对此是否有一个修饰符?

由于您希望依次运行这三个db操作,因此函数
my\u方法
必须是非异步的

但这也意味着
my_method
将阻塞服务器。你肯定不想那样。我能想到的一种方法是在另一个线程中运行这个函数。这不会阻塞服务器,并会在操作运行时继续接受新请求。因为它是非异步的,所以db原子性是有保证的

以下是让您开始学习的相关代码:

import concurrent.futures

executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
# Don't set `max_workers` more than 1, because then multiple 
# threads will be able to perform db operations

class MyHandler(...):
    @gen.coroutine
    def get(self):

        yield executor.submit(MyHandler.my_method, my_arg1)
        # above, `yield` is used to wait for 
        # db operations to finish
        # if you don't want to wait and return
        # a response immediately remove the 
        # `yield` keyword

        self.write('Done')

    @classmethod
    def my_method(cls, my_arg1):
        # do db stuff ...
        return True
同步数据库访问 您尚未说明如何访问数据库。如果您在
get\val\u from\u DB\u table1
和friends(例如with)中有同步DB访问权限,并且
my\u方法
正在阻塞(不会将控制返回IO循环),则您会阻塞服务器(这会影响服务器的性能和响应能力)但是有效地序列化您的客户机,一次只有一个客户机可以执行
my\u方法
。因此,在数据一致性方面,您不需要做任何事情,但通常这是一个糟糕的设计。您可以在短期内使用@xyres的解决方案解决这两个问题(因为Tornado的大部分功能都需要考虑线程安全问题)

异步数据库访问 如果您在
get_val_from_DB_table 1
和朋友(例如with)中具有异步数据库访问权限,则可以使用。下面是一个例子:

from tornado import web, gen, locks, ioloop


_lock = locks.Lock()

def synchronised(coro):
    async def wrapper(*args, **kwargs):  
        async with _lock:
            return await coro(*args, **kwargs)

    return wrapper


class MainHandler(web.RequestHandler):

    async def get(self):
        result = await self.my_method('foo')
        self.write(result)

    @classmethod
    @synchronised
    async def my_method(cls, arg):
        # db access
        await gen.sleep(0.5)
        return 'data set for {}'.format(arg)


if __name__ == '__main__':
    app = web.Application([('/', MainHandler)])
    app.listen(8080)
    ioloop.IOLoop.current().start()

请注意,以上所述是关于正常的单进程Tornado应用。如果使用
tornado.process.fork\u进程
,则只能使用