拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

mysqlconnector-python 报 Unread result found?先检查 cursor 读取与连接配置

mysqlconnector-python 报 Unread result found?先检查 cursor 读取与连接配置 1. 为什么 cursor 会抛出 Unread result foundmysql-connector-python报Unread result found本质是同一个 cursor 上还有没读完的结果集你又拿它去执行下一条语句。MySQL 的通信协议是「一问一答」的你发一条 SELECT服务端把结果集分批发回来客户端必须把这一批数据全部取完连接才能回到「空闲可发下一条命令」的状态。如果你只fetchone()取了一行就跑去execute()第二条 SQL驱动就会拦住你抛出这个错。它经常伪装成别的样子。很多人第一次遇到时看到的是mysql connection not available因为异常发生在cnn.cursor()或下一次execute()上真正的根因被包在里层。用try/except把mysql.connector.Error打出来才会看到Unread result found这句真话。典型触发场景有这么几类循环里对同一 cursor 反复execute但每次只取部分行execute完一条 SELECT 后忘了 fetch直接进入下个分支用executemany或存储过程产生了多个结果集只处理了第一个以及游标和连接被跨函数复用前一个函数留下的结果集没清干净。这些场景的共同点是结果集的生命周期没管好。这篇就按「先定位、再修、最后验证」的顺序走一遍。适合正在用mysql-connector-python写脚本、被这个错卡住的同学也适合想把连接配置一次性写对的人。下面所有代码都可以直接复制运行改一下账号密码即可。2. 接入前的准备连接骨架与 buffered 参数在动手改业务代码之前先把连接和 cursor 的创建方式固定下来。mysql-connector-python的connect()支持一个buffered参数它决定了 cursor 默认是不是「缓冲游标」。不缓冲时默认execute之后结果集是流式的你必须主动 fetch 完缓冲时驱动会在execute阶段就把整个结果集拉到客户端内存里之后你 fetch 不 fetch、fetch 多少都不影响连接状态也就不会再报Unread result found。import mysql.connector config { host: 127.0.0.1, user: root, password: your_password, port: 3306, database: test_db, charset: utf8mb4, buffered: True, # 关键默认使用缓冲游标 connection_timeout: 10, autocommit: False, } cnn mysql.connector.connect(**config)bufferedTrue的代价是内存。结果集有多大客户端就占多大内存。本地小项目、单次查询几千行完全无感但如果你要扫一张千万行的表还开了 buffered就可能在客户端这边先 OOM。所以更稳的做法是连接级别不开 buffered按需在具体 cursor 上开。# 连接保持默认非缓冲 cnn mysql.connector.connect(**config) # 需要一次性拿完的小结果集用缓冲游标 cur cnn.cursor(bufferedTrue) cur.execute(SELECT id, name FROM users LIMIT 100) rows cur.fetchall() cur.close()这样既避免了Unread result found又不会让大查询把内存吃满。如果你确实要处理大结果集就用非缓冲游标但必须保证 fetch 干净后面第 3 节会讲。提示buffered是连接参数也是 cursor 参数。连接上传了bufferedTrue之后cnn.cursor()默认就是缓冲的单个 cursor 上传会覆盖连接的默认值。3. 可复制的读取顺序与最小复现脚本先看一个能稳定复现报错的最小脚本。它故意只取一行就去执行第二条 SQLimport mysql.connector cnn mysql.connector.connect( host127.0.0.1, userroot, passwordyour_password, databasetest_db, charsetutf8mb4, ) cur cnn.cursor() # 非缓冲游标 cur.execute(SELECT id FROM users) # 结果集有 N 行 first cur.fetchone() # 只取 1 行 print(first:, first) # 结果集还剩 N-1 行没读这里就会炸 cur.execute(SELECT COUNT(*) FROM orders)运行后会看到类似mysql.connector.errors.InternalError: Unread result found修法有三种按场景选。第一种把结果集读干净。适合你确实需要全部数据或者结果集很小cur.execute(SELECT id FROM users) rows cur.fetchall() # 一次读完 for r in rows: print(r) cur.execute(SELECT COUNT(*) FROM orders) # 现在安全了第二种用缓冲游标。适合你只想取一部分、又不想手动清空cur cnn.cursor(bufferedTrue) cur.execute(SELECT id FROM users) first cur.fetchone() # 取一行就走 cur.execute(SELECT COUNT(*) FROM orders) # 不报错第三种换一个新 cursor。适合逻辑上两条查询本来就独立cur1 cnn.cursor() cur1.execute(SELECT id FROM users) first cur1.fetchone() cur1.close() # 关掉结果集随之释放 cur2 cnn.cursor() cur2.execute(SELECT COUNT(*) FROM orders)三种方式里我个人最常用第二种改动最小语义也清楚——「这条查询我就是要随手取几行」。但要注意如果同一个 cursor 上连续执行多条 SELECT缓冲游标每次execute会覆盖上一次的结果集所以别指望在缓冲游标上「先取一半、执行别的、再回来取另一半」。还有一个容易忽略的点callproc调用存储过程、或者一次execute里用分号拼了多条语句时可能产生多个结果集。这时要用cur.nextset()逐个切换直到返回Nonecur cnn.cursor(bufferedTrue) cur.execute(CALL some_procedure()) while True: if cur.with_rows: print(cur.fetchall()) if not cur.nextset(): breakwith_rows判断当前结果集是不是查询结果nextset()负责跳到下一个。漏掉这个循环同样会留下未读结果集。4. 验证请求确认报错真的消失了改完之后别急着说「好了」跑一个验证脚本把「报错场景」和「修复场景」放在一起对比。import mysql.connector def build_conn(bufferedFalse): return mysql.connector.connect( host127.0.0.1, userroot, passwordyour_password, databasetest_db, charsetutf8mb4, bufferedbuffered, ) # 场景 A非缓冲 只取一行 复用 cursor预期报错 try: cnn build_conn(bufferedFalse) cur cnn.cursor() cur.execute(SELECT id FROM users) cur.fetchone() cur.execute(SELECT COUNT(*) FROM orders) print(A: 没报错说明数据太少或行为不同) except mysql.connector.Error as e: print(A 预期报错:, e) finally: cnn.close() # 场景 B缓冲游标 同样操作预期通过 cnn build_conn(bufferedTrue) cur cnn.cursor() cur.execute(SELECT id FROM users) print(B 取一行:, cur.fetchone()) cur.execute(SELECT COUNT(*) FROM orders) print(B 第二条查询:, cur.fetchone()) cnn.close()预期输出A 预期报错: Unread result found B 取一行: (1,) B 第二条查询: (42,)如果场景 A 没报错通常是因为users表只有一行fetchone()恰好把结果集读完了。往表里多插几行再试就能稳定复现。场景 B 通过说明缓冲游标这条路是通的。再补一个「读干净」的验证确认非缓冲游标也能安全复用cnn build_conn(bufferedFalse) cur cnn.cursor() cur.execute(SELECT id FROM users) rows cur.fetchall() # 读干净 print(行数:, len(rows)) cur.execute(SELECT COUNT(*) FROM orders) print(复用成功:, cur.fetchone()) cnn.close()两个验证都过了基本可以确定你的修复方向是对的。接下来把它套回真实业务代码重点检查所有「execute 之后没有 fetch 完就复用 cursor」的位置。5. 本篇常见错排查报错信息是mysql connection not available而不是Unread result found。这是外层异常根因还是未读结果集。把except里的异常类型放宽到mysql.connector.Error并把e打出来就能看到真话。别被外层信息带偏。加了bufferedTrue还是报错。检查你是不是在连接上开了 buffered但某个 cursor 又显式传了bufferedFalse覆盖掉。另外executemany和存储过程的多结果集缓冲游标也不一定全帮你兜住该nextset()还是要写。数据量大时程序变卡、内存飙升。这就是 buffered 的副作用。把连接级别的buffered去掉只在小结果集的 cursor 上开大查询用非缓冲游标配合fetchmany(size)分批处理cur cnn.cursor(bufferedFalse) cur.execute(SELECT id, payload FROM big_table) while True: batch cur.fetchmany(1000) if not batch: break for row in batch: process(row)循环里反复execute导致连接被占满。每次execute前确认上一条结果集已处理完或者干脆每条查询用独立 cursor 并close()。连接池场景下未读结果集还会让连接无法归还表现为「连接数只增不减」。事务里报错后连接状态混乱。未读结果集叠加未提交事务容易让后续操作全部失败。出错分支里记得cnn.rollback()并把相关 cursorclose()掉再决定是重连还是继续。注意不要为了图省事在全局连接上无脑开bufferedTrue然后到处复用。它解决的是「小结果集随手取」的问题不是「大查询」的万能药。分场景选游标类型才是长期稳定的写法。6. 把连接配置和 cursor 习惯固定下来回到最开始那个场景重装环境后代码突然报错其实不是环境变了而是数据量或执行路径变了把原本就存在的「未读结果集」问题暴露了出来。buffered参数能快速止血但真正让代码稳的是固定的 cursor 使用习惯。我的建议是连接配置里写清楚charset、connection_timeout、autocommitbuffered默认不开每个查询函数内部自己创建 cursor、自己负责读完或关闭跨函数的连接复用要格外小心宁可多建一个 cursor也别让一个 cursor 带着半截结果集到处跑。把这些约定落到代码里Unread result found基本就不会再找上门。如果你在接入或排障过程中需要对照参数和接口细节可以到 TaoToken 的接入文档里查连接与鉴权部分配合 API Keys 页面把 key 管好想先验证模型或 SQL 辅助逻辑用模型对话跑一遍最小样例长期写代码、跑 Agent 的话Coding Plan 会更省心。地址都在下面按需取用。官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPIhttps://taotoken.net/api模型对话https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewriteCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteClaudeCodeAnthropichttps://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_codeutm_campaignrewrite
返回列表