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

在Tornado中使用Django的ORM的注意事项 – 进出自由,我的分享

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

2012-12-16 02:44 更新 邹业盛

    如何在Django外使用它的ORM Django环境和Tornado的不同之处 Tornado中要做的事 2012-7-15更新 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 运行方式,一个请求过来,就开一个新的“进程/线程”去处理它,在数据库的连接处理上:

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

评论评论功能需要javascript的支持 天有泪,烛有泪,天泪有声,烛泪有形,

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

相关文章:

你感兴趣的文章:

标签云: