带有 Peewee 连接池的 Django MySQL 断开连接

Django with Peewee Connection Pooling MySQL disconnect

我 运行 在 Python 3.6 中使用 Peewee 构建一个 Django 项目,并试图找出连接池的问题所在。我在开发服务器上不断收到以下错误(出于某种原因,我在本地计算机上从未遇到过此问题):

Lost connection to MySQL server during query

重现步骤是可靠的,并且是:

  1. 在实例上重新启动 Apache。
  2. 转到我的 Django 页面并按下触发数据库操作的按钮。
  3. 工作正常。
  4. 正好等待 10 分钟(我已经进行了足够的测试以获得确切的数字)。
  5. 按另一个按钮触发另一个数据库操作。
  6. 获取上面的丢失连接错误。

代码的结构使得我将所有数据库操作都放在一个独立的 Python 模块中,该模块被导入到 Django 模块中。

在主 class 构造函数中,我将数据库设置为:

from playhouse.pool import PooledMySQLDatabase

def __init__(self, host, database, user, password, stale_timeout=300):
    self.mysql_db = PooledMySQLDatabase(host=host, database=database, user=user, password=password, stale_timeout=stale_timeout)
    db_proxy.initialize(self.mysql_db)

每个需要调用数据库的调用都是这样完成的:

def get_user_by_id(self, user_id):
    db_proxy.connect(reuse_if_open=True)
    user = (User.get(User.user_id == user_id))
    db_proxy.close()
    return {'id': user.user_id, 'first_name': user.first_name, 'last_name': user.last_name, 'email': user.email }

我查看了 MySQL 实例上的 wait_timeout 值,它的值为 3600,所以这似乎不是问题所在(我还是尝试更改它只是为了看看)。

关于我在这里可能做错了什么有什么想法吗?

更新:

我发现 MySQL 的 /etc/my.cnf 配置文件将 wait-timeout 值设置为 600,这与我遇到的情况相符。我不知道为什么当我在 MySQL 数据库(returns 3600)上 运行SHOW VARIABLES LIKE 'wait_timeout'; 时这个值没有显示,但看起来问题可能来了来自等待超时。

考虑到这一点,我尝试将过时超时设置为 60,假设如果它小于等待超时,它可能会解决问题,但没有任何区别。

您需要确保正确地回收连接——这意味着当请求开始时您打开连接,当响应被传递时您关闭连接。池没有回收 conn 很可能是因为你永远不会把它放回池中,所以它看起来仍然 "in use"。这可以使用中间件轻松完成,并在此处进行了描述:

http://docs.peewee-orm.com/en/latest/peewee/database.html#django

在尝试了 许多 个想法后,我终于想出了一个适用于我的案例的修复程序。这并不理想,但确实有效。 This post on Connection pooling 为我指明了正确的方向。

我创建了一个 Django 中间件 class 并将其配置为 Django 中间件列表中的第一个。

from peewee import OperationalError
from playhouse.pool import PooledMySQLDatabase

database = PooledMySQLDatabase(None)

class PeeweeConnectionMiddleware(object):

    CONN_FAILURE_CODES = [ 2006, 2013, ]

    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if database.database: # Is DB initialized?
            response = None
            try:
                database.connect(reuse_if_open=True)
                with database.atomic() as transaction:
                    try:
                        response = self.get_response(request)
                    except:
                        transaction.rollback()
                        raise
            except OperationalError as exception:
                if exception.args[0] in self.CONN_FAILURE_CODES:
                    database.close_all()
                    database.connect()
                    response = None
                    with database.atomic() as transaction:
                        try:
                            response = self.get_response(request)
                        except:
                            transaction.rollback()
                            raise
                else:
                    raise
            finally:
                if not database.is_closed():
                    database.close()
            return response
        else:
            return self.get_response(request)