如何使用psycopg2重置PostgreSQL游标?
我来帮你搞定这个问题!你遇到的情况是因为用了命名游标(就是创建游标时指定了name=f'cursor_{i}'),PostgreSQL的命名游标状态是保存在服务器端的——哪怕你中断脚本,如果连接没有被彻底关闭,服务器端的游标会保留上次的位置,下次重启脚本创建同名游标时就会接着之前的位置继续读。而且你的异常处理逻辑里,用了os._exit(0)直接终止进程,导致finally块里的连接关闭代码没机会执行,服务器端的游标就一直留着了。
要解决这个问题,有几个简单的方案:
方案一:改用普通游标(推荐)
普通游标是客户端维护的状态,每次重启脚本都会重新执行查询,从头开始读取数据,完全不会有残留状态的问题。修改起来很简单,只要去掉游标创建时的name参数就行:
# 把原来的命名游标改成普通游标 cur = conn.cursor() # 去掉name参数
这样每次启动脚本,执行cur.execute()后,游标都会从结果集的开头开始,不管之前有没有中断过。
方案二:修复异常处理,确保连接和游标被彻底关闭
如果你一定要用命名游标,那得保证脚本中断时,连接和游标被正确关闭,让服务器端清理掉游标状态。你原来的代码里,except KeyboardInterrupt块里用os._exit(0)会直接终止进程,跳过finally块的执行,导致连接没关闭。可以改成这样:
import psycopg2 import pandas as pd from datetime import datetime, date import time import sys import os for i in range(3,7): conn = None cur = None try: conn = psycopg2.connect( host="localhost", database="dbx", port=5432, user="whatever", options="-c search_path=dbo,data", password="xxxx") cur = conn.cursor(name=f'cursor_{i}') start_date = date(2023, i, 1) end_date = date(2023, i+1, 1) if i < 12 else date(2024, 1, 1) chunk_size = 100 cur.execute("SELECT * FROM data.table WHERE datetime >= %s AND datetime <= %s", (start_date, end_date)) rows = cur.fetchmany(chunk_size) while rows: for row in rows: time.sleep(0.004) rmq_tx= {"...some db stuff here..."} print(rmq_tx) rows = cur.fetchmany(chunk_size) except KeyboardInterrupt: print('Interrupted') finally: # 不管有没有异常,都确保关闭游标和连接 if cur is not None: cur.close() if conn is not None: conn.rollback() conn.close() sys.exit(0)
这里把conn和cur的初始化放在外面,finally块里不管是否发生中断,都会关闭游标和连接,服务器端的命名游标就会被清理,下次启动脚本时创建的新游标就会从头开始。
方案三:每次启动时显式销毁同名游标(可选)
如果担心服务器端可能残留同名游标,可以在创建新游标前,先执行一条命令销毁它:
# 在创建游标前,先执行销毁命令 try: cur_temp = conn.cursor() cur_temp.execute("CLOSE cursor_%s;", (i,)) cur_temp.close() except psycopg2.ProgrammingError: # 游标不存在,忽略错误 pass # 然后再创建命名游标 cur = conn.cursor(name=f'cursor_{i}')
这样就能确保每次创建命名游标前,之前的同名游标已经被销毁,新游标会从头开始读取。
总结一下,最简单的就是方案一,改用普通游标,完全避免服务器端状态的问题,代码改动最小,也最可靠。
备注:内容来源于stack exchange,提问作者stanvooz

