Tornado和pyodbc的异步调用

2 投票
1 回答
3535 浏览
提问于 2025-04-16 12:56

我想用tornado来实现一个网络服务,提供数据库查询的功能给用户。我使用了pyodbc模块来连接数据库并进行查询。在实际操作中,我发现打印查询结果的时间很长。也就是说,如果我用以下代码来打印查询结果

while 1:
    data = cursor.fetchone()
    if not data: break
    self.write(data + '\n')
    self.flush()

而SQL命令大概是这样的

select * from <a large dummy table>

tornado不会在循环结束之前打印查询结果,而这个循环会花费很长时间。

我想利用tornado的非阻塞异步特性,这样即使当前用户的查询请求还没打印完,其他用户也能使用这个网络服务。

所以我写了类似这样的代码:

@tornado.web.asynchronous
def get(self):
    try:
        cnxn = pyodbc.connect(self.server, self.driver, self.table, self.uid, self.pwd)
    except Exception, e:
        print e
        return

    try:
        self.cur = cnxn.execute(self.sql)
    except Exception, e:
        print e
        return

    self.wait_for_query(callback=self.async_callback(self.on_finish))

def wait_for_query(self, callback):
    while 1:
       data = self.cur.fetchone()
       if not data: break
       self.write(data)
       self.flush()
    callback()

def on_finish(self):
    self.finish()

我看了这篇帖子: 使用Tornado和Prototype的异步COMET查询 我明白我的解决方案行不通。但我确实不能使用add_timeout,因为我无法知道每次循环会持续多久。那么我该如何解决这个问题,达到我的目标呢?

1 个回答

0

为了让单线程的Tornado服务器在处理请求时能够异步工作,你需要把控制权交还给I/O循环。可以试试下面的代码:

class LongRequestHandler(tornado.web.RequestHandler):
    def database_callback(self):
        data = self.cur.fetchone()
        if not data:
            self.finish()
            self.cnxn.close()
        else:
            self.write(data)
            self.flush()
            tornado.ioloop.IOLoop.instance().add_callback(self.database_callback)

    @tornado.web.asynchronous
    def get(self):
        try:
            self.cnxn = pyodbc.connect(self.server, self.driver, self.table, self.uid, self.pwd)
        except Exception, e:
            print e
            return

        try:
            self.cur = self.cnxn.execute(self.sql)
        except Exception, e:
            print e
            return

        tornado.ioloop.IOLoop.instance().add_callback(self.database_callback)

不过要注意,每个数据库提供商的情况都不一样。我了解到,对于MySQL来说,大部分时间和处理过程其实都是花在execute()这个调用上,而不是在循环处理数据,因为MySQL会处理整个查询并返回完整的结果集。如果你使用的数据库提供商也是这样,你可能需要在Tornado后面设置一个工作进程来处理这些请求。

补充说明:我给出的例子只是个示范。实际上,你应该测试一下你的回调函数,并可能需要循环处理很多行数据后再返回结果,否则你会浪费很多CPU时间在I/O循环中切换函数,而不是实际处理请求。经过一些测试,我担心的事情在MySQL上确实是对的——execute/query语句本身就是导致锁定的原因,所以在这种情况下,这个解决方案并没有帮助。

撰写回答