Django 在 PostgreSQL 死机时不重新连接,需要自定义后端吗?
Django not reconnecting when PostgreSQL dies, custom backend needed?
我一直在做一些测试,并且已经能够确认将 Django
与 PostgreSQL
和 PGBouncer
一起使用时它不会在失去连接时自动重新连接。老实说,我不确定这是错误还是设计使然。如果它是一个错误,我会很高兴地报告它,如果不是,我想要一些解释为什么以及如何绕过它而不是另一个自定义后端。
通过在 Django 1.8.4
和 Django 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.
我一直在做一些测试,并且已经能够确认将 Django
与 PostgreSQL
和 PGBouncer
一起使用时它不会在失去连接时自动重新连接。老实说,我不确定这是错误还是设计使然。如果它是一个错误,我会很高兴地报告它,如果不是,我想要一些解释为什么以及如何绕过它而不是另一个自定义后端。
通过在 Django 1.8.4
和 Django 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.