news 2026/9/27 17:55:16

解决mysql驱动连接MariaDB,rs.next()游标报错java.sql.SQLException:The statement (1) has no open cursor——TaoToken

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
解决mysql驱动连接MariaDB,rs.next()游标报错java.sql.SQLException:The statement (1) has no open cursor——TaoToken

1. 问题现场:rs.next() 为什么突然说游标没打开

你写了一段再普通不过的 JDBC 查询代码,executeQuery()返回了ResultSet,结果第一行rs.next()就炸了:

java.sql.SQLException: The statement (1) has no open cursor

这句话的迷惑点在于:语句明明执行了,ResultSet对象也拿到了,为什么游标是「没打开」的状态?更奇怪的是,同一套代码连 MySQL 一切正常,换成 MariaDB 就翻车。这个异常在 Java 应用用 mysql 驱动连 MariaDB 的场景里出现频率相当高,尤其是查询里带子查询、UNION、或者你手动设置过fetchSize的时候。

先把结论摆出来:这个报错的核心不是「连接断了」,而是结果集游标在服务端被提前关闭或从未真正打开。MariaDB 和 MySQL 虽然协议高度兼容,但在流式结果集(Streaming ResultSet)的处理策略上有差异。当你用 mysql 的com.mysql.cj.jdbc.Driver去连 MariaDB,驱动会按 MySQL 的默认行为走,而 MariaDB 服务端在某些查询形态下会提前释放内部游标,客户端再去next()就撞上了「no open cursor」。

适合谁看:正在用 Spring Boot / MyBatis / 原生 JDBC 连 MariaDB,却沿用 mysql 驱动或 mysql 风格 URL 的 Java 开发者;以及被这个异常卡住、搜到一堆「换驱动」但不知道怎么平滑迁移的人。下面我会给出可复制的 JDBC URL、驱动类名、连接池配置,以及一个最小复现用例,帮你逐项定位根因。

2. 前置准备:TaoToken 与驱动环境确认

在动手改代码之前,先把「模型/接口调试」和「数据库驱动」两条线分开。排查这类 JDBC 异常时,我习惯用一个稳定的接口调试入口来验证 SQL 逻辑本身没问题,避免把「SQL 写错」和「驱动游标问题」混在一起。TaoToken 的模型对话入口可以用来快速验证一段 SQL 的语义、让模型帮你解释报错栈,地址是:

  • 模型对话:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

注意,TaoToken 在这里的角色是「帮你分析和验证」,不是数据库代理,也不是让你把生产库直连上去。数据库连接始终走你自己的 JDBC 配置。

环境侧你需要确认三件事:

第一,你用的驱动到底是哪个。打开pom.xml或build.gradle,看依赖坐标。如果是mysql:mysql-connector-java或com.mysql:mysql-connector-j,那就是 mysql 驱动连 MariaDB,这正是本篇的场景。如果是org.mariadb.jdbc:mariadb-java-client,那属于另一条路线。

第二,驱动版本。mysql 驱动 8.x 和 5.x 行为差异很大,8.x 默认useSSL、时区、allowPublicKeyRetrieval等参数都会影响连接。用mvn dependency:tree | grep mysql或gradle dependencies确认实际生效版本。

第三,MariaDB 服务端版本。SELECT VERSION();一条就够。10.2 到 11.x 之间,流式结果集和子查询游标的处理有细微差别,报错形态也会不同。

提示:不要一上来就换驱动。先用最小复现用例确认是「驱动行为」还是「SQL 形态」触发的,否则换完驱动可能只是把问题藏起来。

3. 可复制配置:JDBC URL、驱动类名与连接池骨架

这一节是全文最该抄的部分。先给原生 JDBC 的最小配置,再给连接池和框架配置骨架。

3.1 JDBC URL 与驱动类名

用 mysql 驱动连 MariaDB 时,URL 前缀仍然是jdbc:mysql://,但建议显式加上几个关键参数,把流式行为控制住:

# mysql 驱动连 MariaDB 的推荐 URL jdbc.url=jdbc:mysql://127.0.0.1:3306/your_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&useCursorFetch=true&defaultFetchSize=1000 jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.username=your_user jdbc.password=your_pass

关键参数逐个说:

useCursorFetch=true让驱动使用服务端游标,而不是把整个结果集一次性拉到客户端。defaultFetchSize=1000给一个合理的批量大小,避免Integer.MIN_VALUE那种极端流式设置。serverTimezone不设的话,8.x 驱动经常在连接阶段就报时区错。

如果你确实需要流式读取大结果集,才用下面这种写法,并且要清楚它的代价:

// 仅在需要流式读取超大结果集时使用 PreparedStatement pst = connection.prepareStatement( realSql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY ); pst.setFetchSize(Integer.MIN_VALUE); // mysql 驱动的流式开关

Integer.MIN_VALUE是 mysql 驱动特有的「逐行流式」信号。问题在于,MariaDB 服务端在遇到子查询时可能提前关闭内部游标,而客户端还以为流式游标开着,于是rs.next()抛no open cursor。所以除非你明确要流式,否则别随手加这一行。

3.2 连接池骨架(HikariCP)

# application.yml spring: datasource: url: jdbc:mysql://127.0.0.1:3306/your_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&useCursorFetch=true&defaultFetchSize=1000 driver-class-name: com.mysql.cj.jdbc.Driver username: your_user password: your_pass hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 connection-test-query: SELECT 1

connection-test-query用SELECT 1就够,别用复杂查询,否则每次借连接都跑一遍子查询,反而容易触发游标问题。

3.3 如果你用 MariaDB 官方驱动

换驱动是根治方案之一,配置骨架如下:

spring: datasource: url: jdbc:mariadb://127.0.0.1:3306/your_db?useUnicode=true&characterEncoding=utf8&useServerPrepStmts=true driver-class-name: org.mariadb.jdbc.Driver username: your_user password: your_pass

注意 URL 前缀从jdbc:mysql://变成jdbc:mariadb://,驱动类名也换掉。MariaDB 驱动对自家服务端的游标处理更贴合,no open cursor基本不会出现。但迁移前要确认你的 ORM 框架、分页插件、连接池都兼容。

3.4 工具侧配置骨架

如果你用某些数据库客户端或 AI 辅助工具,配置里也会涉及连接串。以settings.json和config.toml为例:

{ "database": { "driver": "com.mysql.cj.jdbc.Driver", "url": "jdbc:mysql://127.0.0.1:3306/your_db?useSSL=false&serverTimezone=Asia/Shanghai&useCursorFetch=true&defaultFetchSize=1000", "user": "your_user", "poolSize": 5 } }
[database] driver = "com.mysql.cj.jdbc.Driver" url = "jdbc:mysql://127.0.0.1:3306/your_db?useSSL=false&serverTimezone=Asia/Shanghai&useCursorFetch=true&defaultFetchSize=1000" user = "your_user" pool_size = 5

这些骨架的重点都是:别用Integer.MIN_VALUE流式,改用useCursorFetch+ 合理defaultFetchSize。

4. 最小复现与逐项验证:定位游标未打开的根因

光看配置不够,得能复现。下面是一个最小复现用例,专门触发子查询 + 流式游标的组合。

4.1 最小复现用例

import java.sql.*; public class CursorRepro { public static void main(String[] args) throws Exception { String url = "jdbc:mysql://127.0.0.1:3306/test_db" + "?useSSL=false&serverTimezone=Asia/Shanghai"; try (Connection conn = DriverManager.getConnection(url, "user", "pass")) { String sql = "SELECT id, name FROM (SELECT id, name FROM users WHERE id > ?) t ORDER BY id"; PreparedStatement pst = conn.prepareStatement( sql, ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY ); pst.setFetchSize(Integer.MIN_VALUE); // 关键:流式开关 pst.setInt(1, 0); ResultSet rs = pst.executeQuery(); while (rs.next()) { // 这里可能抛 no open cursor System.out.println(rs.getInt("id") + " -> " + rs.getString("name")); } } } }

跑这段代码,如果 MariaDB 服务端在子查询上提前关游标,rs.next()就会抛The statement (1) has no open cursor。注意(1)是语句编号,不是行号,别被误导。

4.2 逐项验证动作

按顺序做,每步只改一个变量:

第一步,去掉pst.setFetchSize(Integer.MIN_VALUE),再跑。如果不报错了,说明就是流式设置触发的。这是最常见的根因。

第二步,保留流式设置,但把子查询改成单表查询SELECT id, name FROM users WHERE id > ?。如果不报错,说明是「流式 + 子查询」的组合问题,MariaDB 对子查询的游标生命周期管理更激进。

第三步,把 URL 换成useCursorFetch=true&defaultFetchSize=1000,去掉代码里的setFetchSize(Integer.MIN_VALUE)。这是推荐的生产写法,验证是否稳定。

第四步,换 MariaDB 官方驱动,URL 前缀改jdbc:mariadb://,重复第一步。如果彻底不报,说明驱动层面对接更匹配。

第五步,检查连接池是否在查询中途回收了连接。把max-lifetime调大,或者临时关掉连接池用裸连接跑,排除池化干扰。

注意:no open cursor有时是「连接被关闭」的次生现象。如果连接池在rs.next()循环期间因为超时回收了连接,也会报类似错误。所以第五步别跳过。

4.3 验证成功的标志

改完后,你应该看到:查询正常返回所有行,rs.next()循环走完不抛异常;用EXPLAIN看执行计划,子查询被正常展开;连接池日志里没有频繁的connection reset。如果用了useCursorFetch,可以通过SHOW STATUS LIKE 'Com_stmt_fetch'观察服务端游标拉取次数,确认走的是游标而非全量拉取。

5. 本篇常见错排查清单

把踩过的坑列成表,对照着查:

现象可能原因处理动作
rs.next()首行就抛 no open cursor用了setFetchSize(Integer.MIN_VALUE)且 SQL 含子查询去掉流式设置,改用useCursorFetch
查询前几行正常,中途抛错连接池回收连接或服务端游标超时调大max-lifetime,检查net_write_timeout
换 MariaDB 驱动后报类找不到依赖没引入或版本冲突加mariadb-java-client依赖,排除旧 mysql 驱动
URL 里useSSL=true连不上MariaDB 未配 SSL测试环境设useSSL=false,生产配证书
时区报错The server time zone value8.x 驱动默认时区不匹配URL 加serverTimezone=Asia/Shanghai
分页插件报游标错分页 SQL 被改写成子查询 + 流式关闭分页插件的流式选项,或换驱动

几个容易忽略的点:allowPublicKeyRetrieval=true在 mysql 8.x 连 MariaDB 时经常需要,否则认证阶段就失败;useServerPrepStmts在 MariaDB 驱动下建议开启,能减少游标问题;MyBatis 的fetchSize设置如果配成Integer.MIN_VALUE,同样会触发这个异常,去mybatis-config.xml里检查。

如果排查过程中需要让模型帮你读异常栈、解释某段 SQL 的执行计划,可以用模型对话入口贴报错和 SQL,让它逐行分析:

  • 模型对话:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite

长期做 Java 后端编码、需要反复调试 JDBC 和 SQL 的话,Coding Plan 更适合持续使用:

  • Coding Plan:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite

6. 接入与排障入口

回到操作层面,给你一条清晰的路径。先确认驱动和 URL,再跑最小复现用例,然后按第 4 节的五步逐项验证。绝大多数情况下,把setFetchSize(Integer.MIN_VALUE)换成useCursorFetch=true&defaultFetchSize=1000就能解决no open cursor。如果业务允许,迁移到 MariaDB 官方驱动是更彻底的方案。

需要生成或管理 API Key 做接口调试的,走这里:

  • API Keys:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
  • 接入文档:https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite

最后留一个我实际踩过的坑:有次排查了半天,最后发现是连接池的connection-test-query配了一条带子查询的 SQL,每次借连接都触发一次游标问题,日志里却只显示业务查询报错。把测试查询改成SELECT 1之后,异常直接消失。所以排查时别只盯着业务 SQL,连接池的健康检查语句、分页插件的改写 SQL、ORM 自动生成的语句,都要看一眼。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/27 17:55:05

专门做包包的网站避坑指南:保姆级建站教程

专门做包包的网站避坑指南:保姆级建站教程 改个需求建站公司拖一周,这种噩梦谁懂?上周刚接了个客户,做高定女包的,之前找外包做的专门做包包的网站,改个颜色方案要等三天,上线后页面加载还得五秒,手机上看图还模糊。客户急得跳脚,问我有没有靠谱的 保姆级建站教程…

作者头像 李华
网站建设 2026/9/27 17:54:06

搞懂seo专员岗位职责要多少钱?避坑指南与真实报价

搞懂seo专员岗位职责要多少钱?避坑指南与真实报价 网站后台突然弹出一串乱码,或者打开首页全是博彩广告,这时候你第一反应往往是“我的网站被黑了”。很多老板第一句话问的不是“怎么修”,而是“修这个要多少钱”。这其实是个误区,网站挂马往往只是表象,背后可能是代码漏洞未修补,或者是服务器权限配置过宽,甚至…

作者头像 李华
网站建设 2026/9/27 17:53:46

网站被黑挂马别慌:自己做的网站不备案不能访问吗?修复成本多少钱

网站被黑挂马别慌:自己做的网站不备案不能访问吗?修复成本多少钱 昨晚两点,老张急得给我打电话,声音都在抖。他说公司官网首页突然变了,弹出一个乱七八糟的赌博广告,点进去全是黄赌毒链接。他第一反应不是找技术,而是问我:“这网站被黑挂马不知道怎么办?修一次多少钱?”…

作者头像 李华
网站建设 2026/9/27 17:53:22

企业网站的基本内容以及营销功能免费工具推荐

搞懂企业网站基本内容及营销功能速查手册 备案流程一头雾水,卡住了整个项目上线进度?别慌,这不只是你一个人的困境。很多甲方对接人在盯着开发进度的同时,对 企业网站的基本内容以及营销功能 缺乏系统认知,导致验收时才发现“能看但不好用”,或者SEO效果差强人意。 今天这份 速查手册…

作者头像 李华
网站建设 2026/9/27 17:53:12

浙江省网站建设别踩坑 5个注意事项保排名

浙江省网站建设别踩坑 5个注意事项保排名 做网站最怕啥?不是代码写不完,而是花了几万块做出来的站,打开一看跟十年前的政府内网似的,丑得没法看。很多浙江的老板,特别是做外贸或者本地服务的,第一反应就是找个模板套一下,觉得能省事儿。结果呢?模板网站太丑不够用,客户进来三秒钟就跑了,百度也搜不到你。…

作者头像 李华
网站建设 2026/9/27 17:53:05

做的公司网站怎么没了?3个案例拆解重建成本与哪家好

做的公司网站怎么没了?3个案例拆解重建成本与哪家好 备案流程一头雾水,导致网站突然下线?选建站公司哪家好成了救命稻草。最近帮一位成都做机械设备的老板复盘,他花8000块做的官网,刚上线三个月,因为ICP备案信息核验失败被直接封停,客户全断了。这背后不是运气差,而是对“做的公司网站怎么没了”这个核心风…

作者头像 李华