Tornado和pyodbc的异步调用
我想用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语句本身就是导致锁定的原因,所以在这种情况下,这个解决方案并没有帮助。