传递生成器至StreamingHttpResponse时游标为何已关闭?
问题出在
with语句的生命周期不匹配! 我一眼就看出问题所在了——你用with语句创建的游标,会在with代码块执行完毕的瞬间被自动关闭,但StreamingHttpResponse并不会立刻去迭代你的生成器。它要等到响应开始向客户端输出的时候才会逐步调用生成器,这时候游标早就被with的退出逻辑关掉了,自然会返回"cursor closed!"。
为什么fetchall()和shell里能正常跑?
- 用
cursor.fetchall()的时候,所有数据在with块内部就已经全部读到内存里了,游标关了也不影响已经拿到的结果。 - 你在shell里测试时,生成器是被同步迭代完的——也就是在
with块还没退出、游标还开着的时候就把所有数据取完了,所以没问题。
解决办法有两个,推荐第二个
方案1:手动管理游标(不用with)
自己控制游标创建和关闭,确保生成器迭代完再关游标,记得加异常处理避免资源泄漏:
from django.http import StreamingHttpResponse from django.db import connections def queryset_generator(cursor, chunk_size=CHUNK_SIZE): try: while True: rows = cursor.fetchmany(chunk_size) if not rows: break yield rows finally: # 不管成功失败,最后都要关游标 if not cursor.closed: cursor.close() def my_view(request): cursor = connections['mydb'].cursor() try: cursor.execute("SELECT * FROM foobar;") return StreamingHttpResponse(queryset_generator(cursor)) except Exception as e: # 出问题了赶紧关游标再抛异常 if not cursor.closed: cursor.close() raise e
方案2:用Django内置的Postgres服务器端游标(更省心)
Django对Postgres的服务器端游标有原生支持,只要给游标指定一个name参数,就能自动开启服务器端游标,而且完美适配流式响应:
from django.http import StreamingHttpResponse from django.db import connections def queryset_generator(chunk_size=CHUNK_SIZE): # 给游标加name参数,开启服务器端游标 with connections['mydb'].cursor(name='large_result_cursor') as cursor: cursor.execute("SELECT * FROM foobar;") while True: rows = cursor.fetchmany(chunk_size) if not rows: break yield rows def my_view(request): return StreamingHttpResponse(queryset_generator())
这里的关键是,生成器是在with块内部定义并返回的,只有当生成器完全迭代完毕,with块才会退出关闭游标——刚好匹配流式响应的迭代节奏。而且服务器端游标会把结果集留在数据库端,不会一次性加载到内存,完全符合你的需求。
小提示
如果要返回JSON格式的流式响应,可以把每批rows转成JSON字符串再yield,比如yield json.dumps(rows) + '\n',这样客户端可以逐行解析。
内容的提问来源于stack exchange,提问作者Escher
相关产品推荐
相关产品推荐

