1. 这个报错到底卡在哪一步
TDSQL 是腾讯云推出的分布式数据库产品,兼容 MySQL 协议,很多团队在迁移或新建业务时都会选它。但当你用 JDBC 驱动连上 TDSQL 的 Proxy 接入层后,可能会在日志里看到这样一行:
Proxy ERROR: CURSOR_TYPE_READ_ONLY is not support yet这个报错的意思是:客户端向 Proxy 请求了一个「只读游标」类型的结果集,但 TDSQL 的 Proxy 层目前还不支持这种游标模式。它不是一个网络问题,也不是权限问题,而是驱动和代理之间的协议兼容性缺口。
谁最容易踩到这个坑?三类人:一是从原生 MySQL 迁移到 TDSQL、沿用了旧连接串的开发者;二是用了 ORM 框架(比如 MyBatis、Hibernate)默认开启了流式查询或游标 fetch 的项目;三是在数据同步、大批量导出场景里手动设置了useCursorFetch=true的同学。这些场景的共同点是:客户端主动要求「服务端游标」,而 TDSQL Proxy 只认普通结果集。
我试过在一个 Spring Boot 项目里复现这个问题,连接串里带了useCursorFetch=true,一执行分页查询就报这个错。下面把定位思路和修正配置一步步拆开讲,你可以直接照着改。
2. 动手前先把 TaoToken 的接入信息准备好
在改连接配置之前,如果你还需要一个稳定的模型对话或编码辅助环境来对照排查,可以先把 TaoToken 的入口配好。它在这里的角色是:帮你快速验证 SQL 片段、生成连接参数对照、或者让 coding agent 直接读你的报错日志给出修改建议。
TaoToken 官网入口:https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=
API 基础地址(不带 UTM,直接用于代码里):https://taotoken.net/api
如果你只是想让模型帮你分析这段报错,用模型对话页最省事:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
如果你打算长期用 coding agent 来改 JDBC 配置、跑回归,建议走 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
需要生成 API Key 时在这里操作:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
接入文档在:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
Claude Code 用户走这个入口:https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite
这些链接先收好,后面排查到驱动参数时,你可以直接把报错和连接串丢给模型对话,让它帮你比对差异。
3. 可复制的连接配置修正片段
核心思路只有一句话:把客户端主动要求服务端游标的参数关掉,让结果集走普通模式。
3.1 JDBC 连接串修正
原始有问题的连接串通常长这样:
jdbc:mysql://tdsql-proxy-host:3306/your_db?useCursorFetch=true&useServerPrepStmts=true修正后:
jdbc:mysql://tdsql-proxy-host:3306/your_db?useCursorFetch=false&useServerPrepStmts=false&serverTimezone=GMT%2B8关键点三个:
useCursorFetch=false:这是直接触发CURSOR_TYPE_READ_ONLY的开关,必须关。useServerPrepStmts=false:服务端预处理语句在某些 Proxy 版本下也会联动游标行为,一并关掉更稳。serverTimezone=GMT%2B8:TDSQL 环境里时区不显式指定容易出别的乱子,顺手补上。
3.2 代码里动态移除游标参数
如果你用的是连接池(HikariCP、Druid),参数可能写在配置类里。参考下面这段逻辑,在数据源初始化时把游标相关属性摘掉:
if ("mysql".equalsIgnoreCase(db.getPluginId())) { dbMeta.getAttributes().remove("EXTRA_OPTION_MYSQL.useCursorFetch"); if (!db.getAttributes().containsKey("EXTRA_OPTION_MYSQL.serverTimezone")) { dbMeta.getAttributes().put("EXTRA_OPTION_MYSQL.serverTimezone", "GMT+8"); } }这段代码的作用是:识别到 MySQL 系插件时,强制移除useCursorFetch扩展属性,并兜底设置时区。它适合放在数据源元信息构建阶段,避免参数从上层透传下来。
3.3 MyBatis 流式查询的替代写法
如果你原本用ResultHandler或Cursor<T>做流式查询,改成普通分页:
<select id="selectLargeData" resultType="YourEntity"> SELECT * FROM your_table WHERE id > #{lastId} ORDER BY id LIMIT #{pageSize} </select>用「游标分页」代替「服务端游标」,既绕开了 Proxy 限制,又不会一次性把全表拉进内存。
3.4 参数对照表
| 参数 | 报错时取值 | 修正后取值 | 作用 |
|---|---|---|---|
| useCursorFetch | true | false | 关闭服务端游标请求 |
| useServerPrepStmts | true | false | 关闭服务端预处理 |
| serverTimezone | 未设置 | GMT+8 | 固定时区 |
| defaultFetchSize | 1000 | 不设置 | 避免触发 fetch 模式 |
注意:不同 TDSQL 版本对 Proxy 的支持范围有差异,改完参数后务必在测试环境跑一遍完整查询链路,不要直接上生产。
4. 验证请求是否真的修好了
改完配置不能只看「没报错」,要做三步验证。
4.1 最小复现脚本
先用一个最简单的 JDBC 程序确认:
String url = "jdbc:mysql://tdsql-proxy-host:3306/test_db?useCursorFetch=false&useServerPrepStmts=false&serverTimezone=GMT%2B8"; try (Connection conn = DriverManager.getConnection(url, "user", "password"); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT id, name FROM test_table LIMIT 10")) { while (rs.next()) { System.out.println(rs.getLong("id") + " -> " + rs.getString("name")); } }如果这段跑通且日志里不再出现CURSOR_TYPE_READ_ONLY,说明连接层已经对了。
4.2 打开驱动日志确认游标类型
在连接串里临时加上:
&logger=com.mysql.cj.log.StandardLogger&profileSQL=true观察输出里结果集的 fetch 模式。修正后应该看到普通结果集,而不是READ_ONLY游标。
4.3 在业务查询上回归
拿你实际报错的那条 SQL,在测试环境用修正后的数据源跑一遍。重点看三个指标:查询是否返回完整数据、分页是否正常、连接池有没有因为参数变更出现连接泄漏。
提示:如果验证时还报别的 Proxy 错误,把完整日志贴到模型对话里,让它帮你比对参数差异,比逐行翻文档快得多。
5. 本篇常见错排查
5.1 改了连接串还是报同样的错
大概率是参数没生效。检查顺序:连接池配置类是否覆盖了连接串参数 → 是否有多个数据源指向同一个库 → 环境变量里是否还有旧的useCursorFetch=true。用SHOW VARIABLES看不到客户端参数,得从应用侧打印实际生效的 URL。
5.2 报错消失但查询结果变少
这是把useCursorFetch关掉后,某些 ORM 的流式逻辑失效导致的。检查你的 Mapper 是否依赖Cursor返回值,改成普通List接收,或者用LIMIT分页。
5.3 时区相关报错跟着出现
serverTimezone写成GMT+8在 URL 里需要转义成GMT%2B8,否则+会被解析成空格。这个坑很隐蔽,报错信息通常和时区无关,但连接就是建不起来。
5.4 其他驱动版本差异
MySQL Connector/J 8.x 和 5.x 对游标参数的处理不同。8.x 里useCursorFetch默认 false,但如果你从旧项目复制了连接串,可能带着 true。建议统一升级到 8.0.28 以上,并在连接串里显式写死修正后的参数。
5.5 Proxy 版本兼容性确认
如果所有参数都改对了还是报错,联系 TDSQL 运维确认 Proxy 版本。部分早期版本对CURSOR_TYPE_READ_ONLY的支持是硬缺失,只能靠客户端规避,没有服务端开关。
6. 后续接入与工具分流
排查完这个报错,如果你想把「报错日志 → 参数修正 → 回归验证」这条链路固化下来,可以按用途选入口:
需要生成和管理 API Key 来跑自动化验证脚本,走 API Keys 页:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
需要对照完整接入参数和示例,走接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
只是想让模型帮你分析某段 JDBC 配置或报错堆栈,用模型对话最快:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
打算长期用 coding agent 维护数据源配置、跑回归测试,选 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite
最后留一个我踩过的坑:改完useCursorFetch后别忘了同步改连接池的connectionInitSql,有些池子会在建连时执行初始化语句,如果那里还带着游标相关设置,等于白改。把连接串、池配置、ORM 三层都过一遍,这个报错基本就绝迹了。