pyodbc 中断查询后报 Connection is busy:检查旧游标结果
中断 SQL Server 大查询后,旧语句或未消费结果可能使同一 ODBC 连接拒绝新查询。区分结果消费、游标关闭、取消、事务结束与 MARS 的适用条件。
用 pyodbc 读取 SQL Server,大查询被中断后,下一次查询报 Connection is busy with results for another hstmt。先检查应用是否还在复用同一连接,以及先前游标是否仍有正在执行的语句或未消费的结果。这个报错不足以证明服务器上存在需要强制终止的“僵尸连接”。
ODBC 游标对应语句句柄。一个连接上有旧语句占用结果处理状态时,再创建 cursor 并不等于换了一条数据库连接。交互环境停止当前代码,也不能替代应用对取消和游标释放的管理。
找到仍使用同一连接的读取路径
记录连接对象的创建、复用和释放位置,再检查大查询、pandas 读取、流式迭代及提前返回的代码。两个游标来自同一个连接,仍共享连接级状态;不要只数 cursor 的数量。
如果查询尚未结束,要处理正在执行的语句;如果已返回结果但应用只读了一部分,要处理剩余结果。批次或存储过程还可能返回多个结果集,仅消费第一个不一定表示整个语句已经完成。
完整读取时可以分批 fetchmany,避免把几十万行全部 fetchall 到内存。需要保留的后续结果集按程序约定通过 nextset 消费;不需要的结果也应明确结束。nextset 会丢弃当前结果集的未读行,因此不能在仍需要这些行时提前调用。
不再需要结果时,明确关闭游标
查询结果只在一个函数中使用时,可以将生命周期写清楚。以下是单个 SELECT 的结构示例:cnxn 是已有连接,sql 与 params 由项目提供并保留参数绑定,process_rows 是应用的消费函数:
cursor = cnxn.cursor()
try:
cursor.execute(sql, params)
while True:
rows = cursor.fetchmany(1000)
if not rows:
break
process_rows(rows)
finally:
cursor.close()
这段代码没有替项目决定提交或回滚,也不适用于还要把游标返回给调用方继续读取的接口。process_rows 出错时会走释放路径,但先前写入事务和外部副作用仍要由业务处理。
cursor.close 会丢弃剩余结果集及消息,之后不能继续使用这个游标。它不会把同一连接上所有游标的写事务自动按业务意图提交或回滚。也不要假定 pyodbc 的 cursor 上下文管理器与这个 finally 释放结构完全等价,改动时核对实际 API 生命周期。
若另一个线程仍在执行该游标,不能同时随意读取、关闭或重用它。先沿应用的取消与线程协调流程结束任务,再决定连接是否可以继续使用。
正在执行的查询,需要取消流程
pyodbc 从 4.0.21 提供 cursor.cancel。它用于取消正在处理的查询,通常需由另一个线程发起;实际效果还取决于 ODBC 驱动与调用状态。它不是把连接上的全部业务回滚的替代方法,也不保证应用可以忽略取消结果立即发送下一条 SQL。
交互环境中断不可靠时,记录原任务状态,采用项目已经验证的取消机制,并确认执行线程结束。若连接状态已经不能确认,可以按受控流程弃用这条连接并建立新连接;释放原连接前先处理未提交事务和恢复责任。
不同连接有各自的事务。把第二条查询移到新连接,会改变原先共享事务的语义,不能在要求同一事务原子性的流程中随手替换。连接池也要确保异常连接不会直接交还给下一个请求。
只有需要并存结果集时才评估 MARS
SQL Server 的 MARS 允许同一连接存在多个活动结果集。当前 Microsoft ODBC Driver 支持连接关键字 MARS_Connection=Yes,但应按实际驱动和版本核对;启用后要重新建立使用该配置的连接。
MARS 提供的是结果处理的交错能力,不等于同一连接上的查询都能并行运行,也不是游标泄漏的清理机制。它还会影响事务和会话状态的使用规则。原本只需顺序完成一个查询的应用,应先修正读取与释放流程,再判断是否确实需要启用。
保存连接配置、驱动版本与调用结构,在隔离环境检查实际并存读取需求,再按项目流程上线。不能为了恢复一个交互查询批量终止服务器会话,或关闭认证与 TLS;这些做法不处理结果集生命周期。
验收要覆盖中断后的再次查询
通过原来的应用入口检查正常读取、提前返回、消费函数异常及获准的取消场景。确认旧游标结束后,同一连接在适用条件下能执行下一条查询,且没有遗漏需要保留的结果。
存在写事务时,还要核对提交或回滚结果。暂时重启 Python 后不再报错,只能说明旧运行状态被清除,不能证明下一次中断不会留下同样的问题。回退修改时恢复调用与连接配置,并先结束受影响的任务和事务,避免让旧、新读取流程同时争用同一连接。


