Django Rest Framework结合psycopg2出现InterfaceError: cursor already closed问题求助
cursor already closed,部分请求触发HTTP 500错误 我在使用Django Rest Framework开发接口时遇到了一个棘手的问题:部分请求会执行失败,抛出django.db.utils.InterfaceError: cursor already closed异常。我已经查阅了网上的类似案例并尝试了所有建议,但问题依然存在。目前监控error.log发现有15-20次请求因HTTP 500错误失败,且该错误仅在部分请求中出现。
环境信息
软件版本
- djangorestframework == 3.12.1
- django == 3.0.5
- Python 3.8
uWSGI配置(与NGINX通过Socket配合)
[uwsgi] project = DjangoProject base = /opt/django-project chdir = %(base) module = %(project).wsgi:application home = %(base)/venv gid = www-data uid = www-data master = true processes = 5 socket = /tmp/%(project).sock chmod-socket = 664 vacuum = true harakiri = 60 max-requests = 10000
PostgreSQL配置片段
max_connections = 2000 shared_buffers = 800MB
注:我曾尝试增加PostgreSQL的连接数,但没有任何变化,错误仍然存在。
完整错误栈追踪
Internal Server Error: /api/users/ Traceback (most recent call last): File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/utils.py", line 97, in inner return func(*args, **kwargs) psycopg2.InterfaceError: cursor already closed The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/opt/django-project/venv/lib/python3.8/site-packages/django/core/handlers/exception.py", line 34, in inner response = get_response(request) File "/opt/django-project/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 115, in _get_response response = self.process_exception_by_middleware(e, request) File "/opt/django-project/venv/lib/python3.8/site-packages/django/core/handlers/base.py", line 113, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/opt/django-project/venv/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view return view_func(*args, **kwargs) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/viewsets.py", line 125, in view return self.dispatch(request, *args, **kwargs) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch response = self.handle_exception(exc) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception self.raise_uncaught_exception(exc) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception raise exc File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/views.py", line 497, in dispatch self.initial(request, *args, **kwargs) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/views.py", line 414, in initial self.perform_authentication(request) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/views.py", line 324, in perform_authentication request.user File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/request.py", line 227, in user self._authenticate() File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/request.py", line 380, in _authenticate user_auth_tuple = authenticator.authenticate(self) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/authentication.py", line 193, in authenticate return self.authenticate_credentials(token) File "/opt/django-project/venv/lib/python3.8/site-packages/rest_framework/authentication.py", line 198, in authenticate_credentials token = model.objects.select_related('user').get(key=key) File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/models/query.py", line 411, in get num = len(clone) File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/models/query.py", line 258, in __len__ self._fetch_all() File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/models/query.py", line 1261, in _fetch_all self._result_cache = list(self._iterable_class(self)) File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/models/query.py", line 57, in __iter__ results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size) File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1184, in execute_sql return list(result) File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1583, in cursor_iter for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel): File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/models/sql/compiler.py", line 1583, in <lambda> for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel): File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/utils.py", line 97, in inner return func(*args, **kwargs) File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/utils.py", line 90, in __exit__ raise dj_exc_value.with_traceback(traceback) from exc_value File "/opt/django-project/venv/lib/python3.8/site-packages/django/db/utils.py", line 97, in inner return func(*args, **kwargs) django.db.utils.InterfaceError: cursor already closed
解决方案建议
从错误栈来看,问题出在DRF的Token认证环节执行数据库查询时,游标已经被关闭。结合你的环境配置,这里有几个针对性的排查和解决方向:
1. 检查数据库连接的生命周期管理
Django默认会在请求开始时创建数据库连接,请求结束时关闭。但如果你的代码中存在手动操作数据库连接/游标的情况(比如直接使用psycopg2而非Django ORM,或者手动调用connection.close()),可能会导致后续请求复用已关闭的连接。
- 排查所有自定义的数据库操作代码,确保没有在请求中间手动关闭连接或游标。
- 如果使用了第三方库或自定义中间件,检查是否存在异常处理中错误关闭连接的情况。
2. 调整uWSGI的连接回收配置
你的uWSGI设置了max-requests = 10000,这个参数会让worker进程处理完指定数量的请求后重启,避免内存泄漏。但如果数据库连接没有被正确回收,可能会导致旧连接被复用。
- 尝试添加
reload-on-rss参数,比如reload-on-rss = 200,当worker进程内存占用超过200MB时自动重启,强制回收所有资源。 - 可以暂时降低
max-requests的值(比如设为1000),观察错误是否减少,验证是否是连接复用导致的问题。
3. 检查Django的数据库连接池配置
Django 3.0+默认使用数据库自带的连接池,但你可以显式配置CONN_MAX_AGE参数来控制连接的存活时间:
- 在
settings.py中添加或修改:DATABASES['default']['CONN_MAX_AGE'] = 60(设置连接最大存活60秒),避免长时间存活的连接因PostgreSQL端关闭而失效。 - 注意:
CONN_MAX_AGE设为0会禁用连接池,每个请求都创建新连接,虽然会增加开销,但可以排查是否是连接池的问题。
4. 排查异步/并发操作问题
如果你的接口中存在异步任务(比如使用Celery)或者并发请求处理,可能会出现多个线程/进程共享同一个数据库连接的情况,导致游标被意外关闭。
- 检查所有异步任务代码,确保每个任务都使用独立的数据库连接(Celery默认会处理,但如果自定义了连接需要注意)。
- 如果使用了多线程的中间件或视图,确保每个线程都获取自己的数据库连接,不要共享连接实例。
5. 升级相关依赖版本
你使用的Django 3.0.5和DRF 3.12.1都比较旧,可能存在已知的数据库连接管理bug:
- 尝试升级Django到3.0.x的最新版本(比如3.0.14),DRF升级到3.12.x的最新版本,修复可能存在的底层bug。
- 同时升级psycopg2到最新兼容版本(比如psycopg2-binary==2.9.9),确保与Python 3.8和Django的兼容性。
6. 检查PostgreSQL的连接超时设置
PostgreSQL端可能存在连接超时关闭的情况,比如idle_in_transaction_session_timeout或statement_timeout:
- 查看postgresql.conf中的相关参数:
idle_in_transaction_session_timeout = 0 # 默认是0,即不超时,但如果被修改过可能导致连接被关闭 statement_timeout = 0 - 如果设置了非零值,考虑调整为更合理的时间,或者在Django中捕获连接失效的异常,自动重新连接。
内容的提问来源于stack exchange,提问作者StoneSomber

