使django与数据库保持长链接

   最近遇到一个很蛋疼的问题,写了一个后台管理系统, 因为是后台管理系统,因此使用频率不是很高,当django程序在闲置一段时间后,再次打开后台系统,就变得很慢,而后又好了。查了不少方面,从模板引擎到请求(request),再到django配置,nginx等等,都没有查出缘由。虽然也查过是否是数据库的缘由,但都由于查的不够深刻,没有查出因此然。html

        有一次在处理权限问题时,不断地追朔源代码,因为我使用的是dbroute来控制权限,最后定位到了这个db(具体目录:/usr/local/lib/python2.7/dist-packages/django/db/__init__.py),里面的代码有关于链接的,不看不知道,看了后菊花一紧。我去!原来django每次查询的时候就连一次数据库,并且查询完成以后就立马关掉,真搞不懂做者是怎么想的。每次查询建一次链接不是很耗时间吗?源代码以下:python

 

  1.  
    from django.conf import settings
  2.  
    from django.core import signals
  3.  
    from django.core.exceptions import ImproperlyConfigured
  4.  
    from django.db.utils import (ConnectionHandler, ConnectionRouter,
  5.  
    load_backend, DEFAULT_DB_ALIAS, DatabaseError, IntegrityError)
  6.  
     
  7.  
    __all__ = ( 'backend', 'connection', 'connections', 'router', 'DatabaseError',
  8.  
    'IntegrityError', 'DEFAULT_DB_ALIAS')
  9.  
     
  10.  
     
  11.  
    if settings.DATABASES and DEFAULT_DB_ALIAS not in settings.DATABASES:
  12.  
    raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
  13.  
     
  14.  
    connections = ConnectionHandler(settings.DATABASES)
  15.  
     
  16.  
    router = ConnectionRouter(settings.DATABASE_ROUTERS)
  17.  
     
  18.  
    # `connection`, `DatabaseError` and `IntegrityError` are convenient aliases
  19.  
    # for backend bits.
  20.  
     
  21.  
    # DatabaseWrapper.__init__() takes a dictionary, not a settings module, so
  22.  
    # we manually create the dictionary from the settings, passing only the
  23.  
    # settings that the database backends care about. Note that TIME_ZONE is used
  24.  
    # by the PostgreSQL backends.
  25.  
    # We load all these up for backwards compatibility, you should use
  26.  
    # connections['default'] instead.
  27.  
    class DefaultConnectionProxy(object):
  28.  
    """
  29.  
    Proxy for accessing the default DatabaseWrapper object's attributes. If you
  30.  
    need to access the DatabaseWrapper object itself, use
  31.  
    connections[DEFAULT_DB_ALIAS] instead.
  32.  
    """
  33.  
    def __getattr__(self, item):
  34.  
    return getattr(connections[DEFAULT_DB_ALIAS], item)
  35.  
     
  36.  
    def __setattr__(self, name, value):
  37.  
    return setattr(connections[DEFAULT_DB_ALIAS], name, value)
  38.  
     
  39.  
    connection = DefaultConnectionProxy()
  40.  
    backend = load_backend(connection.settings_dict[ 'ENGINE'])
  41.  
     
  42.  
    # Register an event that closes the database connection
  43.  
    # when a Django request is finished.
  44.  
    def close_connection(**kwargs):
  45.  
    # Avoid circular imports
  46.  
    from django.db import transaction
  47.  
    for conn in connections:
  48.  
    # If an error happens here the connection will be left in broken
  49.  
    # state. Once a good db connection is again available, the
  50.  
    # connection state will be cleaned up.
  51.  
    transaction.abort(conn)
  52.  
    connections[conn].close()
  53.  
    signals.request_finished.connect(close_connection)
  54.  
     
  55.  
    # Register an event that resets connection.queries
  56.  
    # when a Django request is started.
  57.  
    def reset_queries(**kwargs):
  58.  
    for conn in connections.all():
  59.  
    conn.queries = []
  60.  
    signals.request_started.connect(reset_queries)
  61.  
     
  62.  
    # Register an event that rolls back the connections
  63.  
    # when a Django request has an exception.
  64.  
    def _rollback_on_exception(**kwargs):
  65.  
    from django.db import transaction
  66.  
    for conn in connections:
  67.  
    try:
  68.  
    transaction.rollback_unless_managed(using=conn)
  69.  
    except DatabaseError:
  70.  
    pass
  71.  
    signals.got_request_exception.connect(_rollback_on_exception)

发现它是接收到了一个关闭信号后就立马关闭,其实也就是每一次django请求,它就链接一次和查询一次。网上查了下资料,有我的是这样解决的:mysql

在django项目中的__init__.py文件中加入以下代码来实现:nginx

  1.  
    from django.core import signals
  2.  
    from django.db import close_connection
  3.  
     
  4.  
    # 取消信号关联,实现数据库长链接
  5.  
    signals.request_finished.disconnect(close_connection)

 

它采用的原理是最后一行代码相关的Signal对象,其中还有一个disconnect方法,对应实现取消信号关联。sql

       我照着他的实现来作,发现不行,在闲置几小时后,打开后台依然须要10秒左右。因为我使用的是fastcgi来运行django,可能django在没有动静的时候,fastcgi就把它挂起,因此就还会去从新创建链接。若是使用supervisord来运行,估计不会。数据库

       既然这个方法不行,那就再看一下它的源码,发现backends目录下面有mysql,oracle,postgresql_psycopg2,sqlite3等,因而选择msql进去看一下base.py文件。发现django是直接封装MySQLdb,每建立一个MySQLdb对象其实也就进行了一次链接。能不能像线程池同样,一次性建立多个MySQLdb对象呢?答案是确定的。sqlalchemy有个pool可让数据库保持长链接,那就直接把这个文件里的Database改为sqlalchemy的pool。固然,咱们不能暴力地修改去修改源码。由于这个模块是用来创建数据库链接的,因此能够独立出来。其实很简单,只须要修改base.py 文件几处就行。实现方法以下:django

        把django/db/backends/mysql目录下的文件所有拷贝出来,放在项目的一个libs/mysql下面,而后修改base.py文件。找到oracle

try:
    import MySQLdb as Database
except ImportError as e:
    from django.core.exceptions import ImproperlyConfigured
    raise ImproperlyConfigured("Error loading MySQLdb module: %s" % e)

这段代码,在下面添加:app

 

  1.  
    from sqlalchemy import pool
  2.  
    Database = pool.manage(Database)


基本上就能够了。若是还想再修改,就找到less

 

self.connection = Database.connect(**kwargs)

把它注释掉,添加

 

 

  1.  
    self.connection = Database.connect(
  2.  
    host=kwargs.get('host', '127.0.0.1'),
  3.  
    port=kwargs.get('port', 3306),
  4.  
    user=kwargs['user'],
  5.  
    db=kwargs['db'],
  6.  
    passwd=kwargs['passwd'],
  7.  
    use_unicode=kwargs['use_unicode'],
  8.  
    charset='utf8'
  9.  
    )


再修改一下settings.py的数据库配置,把django.db.backends.mysql改成libs.mysql。至此,世界美好了不少。若是精虫上脑,能够移步至:来撸吧,你会发现世界更美好。

相关文章
相关标签/搜索