升级后Psycopg2偶发can't adapt type 'tuple'错误求助
你提到的“可以排除依赖或Psycopg2扩展问题”的判断不成立,该偶发现象恰恰指向Psycopg2 2.9.1版本的兼容问题,和数据库数据无关。
根因说明
这是Psycopg2 2.9.x系列版本在Django长连接复用场景下的已知偶发Bug:当同一个数据库连接被多次请求长时间复用后,Psycopg2内置的类型适配器会异常丢失元组类型的适配规则,导致IN子句的元组参数无法被正确解析,就会抛出can't adapt type 'tuple'错误。
你观察到的「重启Httpd后前几小时正常、仅涉及元组参数的查询报错」的特征完全匹配该Bug的触发规律:新启动的Httpd worker进程会重新初始化Psycopg2适配器,此时运行正常,连接复用一段时间后触发适配器异常,就会开始随机报错。
解决方案
方案1:降级Psycopg2版本(最便捷,无需修改业务代码)
直接将Psycopg2降级到2.8.6稳定版即可,该版本不存在该适配Bug,升级依赖后重启服务即可验证生效。
pip install psycopg2-binary==2.8.6 # 若使用源码安装则执行 pip install psycopg2==2.8.6
方案2:修改查询写法(适合必须保留Psycopg2 2.9.x版本的场景)
两种修改方式二选一即可:
- 改用Django ORM实现查询,ORM底层会自动处理参数适配,避免原生cursor的适配问题:
from your_app.models import XyzTableName from django.db.models import Sum rows = XyzTableName.objects.filter( employer_id=200176, approximate_age_band__in=['30-39', '50-59'] ).values('month_key').annotate( eligible=Sum('eligible_count') ).order_by('month_key')
- 若必须使用原生SQL,手动拼接IN子句占位符,避免传入元组参数:
from django.db import connection age_param = ['30-39', '50-59'] # 生成对应数量的占位符 placeholders = ', '.join(['%s'] * len(age_param)) sql = f"""SELECT month_key as cadence_val, sum(eligible_count) as eligible FROM xyz_table_name WHERE employer_id = %s AND approximate_age_band IN ({placeholders}) GROUP BY 1 ORDER BY 1;""" # 参数扁平化处理,不嵌套元组 params = [200176] + age_param with connection.cursor() as cursor: cursor.execute(sql, params) rows = cursor.fetchall()
方案3:关闭Django长连接复用(无代码修改成本)
在Django配置文件settings.py的数据库配置项中添加CONN_MAX_AGE=0,每次请求结束后自动关闭数据库连接,避免长连接复用触发的适配器异常:
DATABASES = { 'default': { # 保留原有数据库配置(ENGINE、NAME、USER、PASSWORD等) 'CONN_MAX_AGE': 0, } }
注意该方案会略微增加数据库连接建立的开销,高并发场景建议配合数据库连接池中间件使用。
内容的提问来源于stack exchange,提问作者Naveen B
相关产品推荐
相关产品推荐

