Django 在 PostgreSQL 死机时不重新连接,需要自定义后端吗?

Django not reconnecting when PostgreSQL dies, custom backend needed?

我一直在做一些测试,并且已经能够确认将 DjangoPostgreSQLPGBouncer 一起使用时它不会在失去连接时自动重新连接。老实说,我不确定这是错误还是设计使然。如果它是一个错误,我会很高兴地报告它,如果不是,我想要一些解释为什么以及如何绕过它而不是另一个自定义后端。

通过在 Django 1.8.4Django 1.8.6 上执行以下操作,我相当轻松地完成了这些测试:

>>>Model.objects.all().count()
24
# Restart postgres with `sudo service postgres restart`
>>>Model.objects.all().count()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 318, in count
    return self.query.get_count(using=self.db)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 466, in get_count
    number = obj.get_aggregation(using, ['__count'])['__count']
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 447, in get_aggregation
    result = compiler.execute_sql(SINGLE)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 840, in execute_sql
    cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
  File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 98, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/utils.py", line 64, in execute
    return self.cursor.execute(sql, params)
OperationalError: server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.

重新启动 PostgreSQL 到 运行 查询后等待多长时间并不重要,我仍然得到相同的结果。我看到 Django 中有 运行 和 Select 1 的代码来检查连接,但这似乎不起作用。回到 Django 1.5.x 我们已经编写了一个自定义的 PostgreSQL 后端来做同样的事情但是删除了它因为它似乎 Django 在我们升级时内置了它。这是一个错误吗?

编辑 11/6/2015: 在 Django 中,PostgreSQL 后端实现 is_usable 函数,该函数在数据库上执行 SELECT 1。但是,除了 close_if_unusable_or_obsolete 之外,我找不到 is_usable 的任何用法,但我在任何地方都找不到它的任何用法。我认为它会在一些数据库包装器中使用,这些包装器可以捕获异常和基于 is_usable 的 retries/reconnects。

上述代码可在 Django 1.8 的 django/db/backends/postgresql_psychopg2/base.py 找到。

编辑 2 11/6/2015: 好的,我为数据库编写了自己的自定义包装器,只是覆盖了 ensure_connection 方法以尝试重新连接到数据库当连接丢失时。但是,在第一次尝试为会话访问数据库时,我得到了另一个粗糙的回溯。但是,如果我立即再次查询,它就会起作用。如果我将我正在做的事情包装在 try/except: pass 块中,那么所有 似乎 都可以正常工作,但无法真正判断它是否会在以后引起问题。这是回溯和代码。

>>> c = Model.objects.all().count()
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/query.py", line 318, in count
    return self.query.get_count(using=self.db)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 466, in get_count
    number = obj.get_aggregation(using, ['__count'])['__count']
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/query.py", line 447, in get_aggregation
    result = compiler.execute_sql(SINGLE)
  File "/usr/local/lib/python2.7/dist-packages/django/db/models/sql/compiler.py", line 838, in execute_sql
    cursor = self.connection.cursor()
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 164, in cursor
    cursor = self.make_cursor(self._cursor())
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 135, in _cursor
    self.ensure_connection()
  File "/usr/lib/python2.7/dist-packages/custom/db/backends/postgresql_psycopg2/base.py", line 73, in ensure_connection
    self._reconnect()
  File "/usr/lib/python2.7/dist-packages/custom/db/backends/postgresql_psycopg2/base.py", line 63, in _reconnect
    self.connect()
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 120, in connect
    self.set_autocommit(self.settings_dict['AUTOCOMMIT'])
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/base/base.py", line 295, in set_autocommit
    self._set_autocommit(autocommit)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/postgresql_psycopg2/base.py", line 218, in _set_autocommit
    self.connection.autocommit = autocommit
  File "/usr/local/lib/python2.7/dist-packages/django/db/utils.py", line 97, in __exit__
    six.reraise(dj_exc_type, dj_exc_value, traceback)
  File "/usr/local/lib/python2.7/dist-packages/django/db/backends/postgresql_psycopg2/base.py", line 218, in _set_autocommit
    self.connection.autocommit = autocommit
ProgrammingError: autocommit cannot be used inside a transaction

现在输入代码:

from django.db.backends.postgresql_psycopg2.base import DatabaseError, \
    IntegrityError, DatabaseWrapper as PostgresWrapper

class DatabaseWrapper(PostgresWrapper):
    def _reconnect(self):
        try:
            self.connect()
        except (DatabaseError, OperationalError):
            pass

    def ensure_connection(self):
        """
        Guarantees that a connection to the database is established.
        """
        if self.connection is None:
            with self.wrap_database_errors:
                self._reconnect()
        else:
            try:
                self.connection.cursor().execute('SELECT 1')
            except (DatabaseError, OperationalError):
                self._reconnect()

我想我明白了……终于。不完全确定我的第一种方法有什么问题,但这种方法似乎效果更好。

class DatabaseWrapper(PostgresWrapper):
    def _cursor(self):
        if self.connection is not None:
            if not self.is_usable():
                self.connection.close()
                self.connection = None
        return super(DatabaseWrapper, self)._cursor()

编辑: 结束开源。我不确定它是否 100% 需要,但在服务器上重新启动 Postgres 服务后它工作正常。您可以在 pypi 上找到它 django-postgreconnect,在 GitHub 上:https://github.com/mackeyja92/django-postgreconnect.