zoukankan      html  css  js  c++  java
  • 使django与数据库保持长连接

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

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

    from django.conf import settings
    from django.core import signals
    from django.core.exceptions import ImproperlyConfigured
    from django.db.utils import (ConnectionHandler, ConnectionRouter,
        load_backend, DEFAULT_DB_ALIAS, DatabaseError, IntegrityError)
    
    __all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
        'IntegrityError', 'DEFAULT_DB_ALIAS')
    
    
    if settings.DATABASES and DEFAULT_DB_ALIAS not in settings.DATABASES:
        raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
    
    connections = ConnectionHandler(settings.DATABASES)
    
    router = ConnectionRouter(settings.DATABASE_ROUTERS)
    
    # `connection`, `DatabaseError` and `IntegrityError` are convenient aliases
    # for backend bits.
    
    # DatabaseWrapper.__init__() takes a dictionary, not a settings module, so
    # we manually create the dictionary from the settings, passing only the
    # settings that the database backends care about. Note that TIME_ZONE is used
    # by the PostgreSQL backends.
    # We load all these up for backwards compatibility, you should use
    # connections['default'] instead.
    class DefaultConnectionProxy(object):
        """
        Proxy for accessing the default DatabaseWrapper object's attributes. If you
        need to access the DatabaseWrapper object itself, use
        connections[DEFAULT_DB_ALIAS] instead.
        """
        def __getattr__(self, item):
            return getattr(connections[DEFAULT_DB_ALIAS], item)
    
        def __setattr__(self, name, value):
            return setattr(connections[DEFAULT_DB_ALIAS], name, value)
    
    connection = DefaultConnectionProxy()
    backend = load_backend(connection.settings_dict['ENGINE'])
    
    # Register an event that closes the database connection
    # when a Django request is finished.
    def close_connection(**kwargs):
        # Avoid circular imports
        from django.db import transaction
        for conn in connections:
            # If an error happens here the connection will be left in broken
            # state. Once a good db connection is again available, the
            # connection state will be cleaned up.
            transaction.abort(conn)
            connections[conn].close()
    signals.request_finished.connect(close_connection)
    
    # Register an event that resets connection.queries
    # when a Django request is started.
    def reset_queries(**kwargs):
        for conn in connections.all():
            conn.queries = []
    signals.request_started.connect(reset_queries)
    
    # Register an event that rolls back the connections
    # when a Django request has an exception.
    def _rollback_on_exception(**kwargs):
        from django.db import transaction
        for conn in connections:
            try:
                transaction.rollback_unless_managed(using=conn)
            except DatabaseError:
                pass
    signals.got_request_exception.connect(_rollback_on_exception)

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

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

    from django.core import signals  
    from django.db import close_connection  
      
    # 取消信号关联,实现数据库长连接  
    signals.request_finished.disconnect(close_connection) 

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

           我照着他的实现来做,发现不行,在闲置几小时后,打开后台依然需要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/db/backends/mysql目录下的文件全部拷贝出来,放在项目的一个libs/mysql下面,然后修改base.py文件。找到

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

    这段代码,在下面添加:

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

    基本上就可以了。如果还想再修改,就找到

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

    把它注释掉,添加

                self.connection = Database.connect(
                      host=kwargs.get('host', '127.0.0.1'),
                      port=kwargs.get('port', 3306),
                      user=kwargs['user'],
                      db=kwargs['db'],
                      passwd=kwargs['passwd'],
                      use_unicode=kwargs['use_unicode'],
                      charset='utf8'
                  )

    再修改一下settings.py的数据库配置,把django.db.backends.mysql改为libs.mysql。至此,世界美好了很多。如果精虫上脑,可以移步至:来撸吧,你会发现世界更美好。

  • 相关阅读:
    61. 最长不含重复字符的子字符串
    60. 礼物的最大价值 (未理解)
    59. 把数字翻译成字符串
    58. 把数组排成最小的数
    57. 数字序列中某一位的数字 (不懂)
    spring data jpa 官方文档
    idea 编译报错 源发行版 1.8 需要目标发行版 1.8
    idea maven 依赖报错 invalid classes root
    solr
    spring boot 官方文档
  • 原文地址:https://www.cnblogs.com/descusr/p/3566935.html
Copyright © 2011-2022 走看看