在Tornado中使用Django的ORM的注意事项

曹至梧 2016-01-10 16:55 更新
  1. 如何在Django外使用它的ORM
  2. Django环境和Tornado的不同之处
  3. Tornado中要做的事
  4. 2012-7-15更新
  5. 2012-8-15更新

1. 如何在Django外使用它的ORM

Django 的 ORM 虽然功能和性能都不怎么样,但重在简单方便,更重要的是,已经熟悉使用它了。

要在 Django 外使用它的 ORM ,最简单的办法,就是设置一个 DJANGO_SETTINGS_MODULE 的环境变量就可以了:

import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

当然,如果你要是喜欢折腾,也可以从 django.conf 中把 settings 引入,然后使用 configure 方法去配置它的 DATABASES

2. Django环境和Tornado的不同之处

能正确使用 Django 的 API 不代表就万事大吉,它的 ORM 是给自己量身订做的,换到另外一个环境中去,适配的问题只能我们自己解决。

Django 是传统的 Web 运行方式,一个请求过来,就开一个新的“进程/线程”去处理它,在数据库的连接处理上:

  1. 在需要进行数据库操作时,才去处理与数据库的连接。
  2. 因为是传统的 Web 运行方式,所以,每个请求都会使用单独的数据库连接。
  3. 连接一旦创建,那么直到当前请求结束,此连接才会关闭。此请求中的接下来的数据库操作会共用连接。
  4. 每次数据库操作时,都会去获取一次 cursor ,此时,会判断连接的可用性。所以,不用担心所谓的“8小时问题”,对于 MySQL 来说,它使用 ping() 方法判断连接的可用性。
  5. 请求处理完时, Django 会确保连接关闭。

搞清楚 Django 对数据库连接的处理方式,就好办了。

Tornado 的运行方式是单进程的,同时, Tornado 中的 请求 显然与 Django 中的 请求 不是一个东西,那么 Django 的 ORM 机制,不会知道 Tornado 中一个请求什么时候结束。而 Tornado 又是单进程方式,所以,如果你什么都不做,那么结果就是, Tornado 打开一个与数据库的连接,永远不关闭,请求之间也共用这个连接。

看到这里,可以看到,在 Tornado 中要做一个连接池是多少简单的事,当然,除非和数据库的交互支持异步调用的方式,否则永远只能使用一个数据库连接,没连接池什么事。

3. Tornado中要做的事

如果你什么也不做,初看起来,数据库工作得很好, Tornado 自己的数据库封装中面临的“8小时问题”对 Django 的 ORM 来说是不存的,因为它每次获取 cursor 时都会判断连接的可用性。

不过你需要解决的另外一个问题是事务提交。

Tornado 在自己的 MySQL 封装中,使用了 connect.autocommit(True),而 Django 的 ORM 默认是没有打开 autocommit 的。所以,你会看到的一个现象就是,对于 InnoDB 引擎, Tornado 跑起来之后,另外一个客户端对数据库的修改(比如添加了一个记录),在 Tornado 之中是不可见的。你要么打开 autocommit ,要么手动 commit ,我觉得手动 commit 比较好,在每次请求结束时:

# -*- coding: utf-8 -*-

import os

os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from django.db import connections

class BaseHandler(tornado.web.RequestHandler):
    def finish(self, *args, **kargs):
        [c.connection and c.connection.commit() for c in connections.all()]

另外要注意的一点是,在 Tornado 中最好不要使用 Django 的 @transaction.commit_on_success 这类全局性的东西,除非你真的清楚它对整个处理流程有怎样的影响。

4. 2012-7-15更新

finish() 中直接 commit() 有一个问题——虽然 Django 自己在执行数据库操作时会确保数据库的连接已经成功建立,但是,在调用 finish() 之前可能根本就没有任何的数据库操作。这时直接使用 commit() 时可能连接已经失效了(但是连接它还存在,即不为 None),于是就会抛出异常。当然,如果之前没有任何的数据库操作, commit() 本身也是不需要的。我们应该把代码作一些修改:

# -*- coding: utf-8 -*-

import os

os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'
from django.db import connections

class BaseHandler(tornado.web.RequestHandler):
    def finish(self, *args, **kargs):
        for c in connections.all():
            try:
                c._commit()
            except:
                pass

5. 2012-8-15更新

对于 InnoDB 而言, Select 这类读操作也是受事务隔离影响的(参考:http://www.cnblogs.com/shiyangxt/archive/2009/02/11/1388519.html)。简单说,另外的事务已经 commit 的数据,对处于旧事务中的 Select 也是不可见的。

这里有一个很大的影响,就是前面我们在 finish() 中确保执行了 commit(),通常情况下,一个新的请求过来,是处于一个全新的事务当中。但是,如果在 Tornado 当中有一些 callback ,在新请求过来之前就访问了数据库,即打开了一个新的事务,而这些内部的 callback 又不会去调用 finish() 。于是,新的请求就可能处于一个‘旧’的事务当中,这时做 Select 就可能找不到一些数据。所以,为了确保新的请求处于新的事务当中,以保证 Select 操作的正确,我们在 initialize() 也需要做一遍 commit()

def initialize(self):
    for c in connections.all():
        try:
            c._commit()
        except:
            pass

这样我们也只能保证“请求”的事务是新的。对于其它非请求时的数据库访问,比如写的一些定时器类的功能,在查库之前,也需要做一遍 commit() 以保证从数据库取出来的数据是有效的。

评论
©2010-2016 zouyesheng.com All rights reserved. Powered by GitHub , txt2tags , MathJax