☰
Druid 连接池配置踩坑记:YashanDB 报 YAS-04003 打开游标数过多怎么排查
2026/9/26 14:35:20 网站建设 项目流程

1. 从一次线上告警说起:YAS-04003 到底在说什么

YAS-04003 这个报错,字面意思是「打开游标数过多」,完整提示通常是YAS-04003: maximum number of open cursors is 1000。它不是一个连接数问题,也不是内存问题,而是数据库层面在告诉你:当前这个会话(或者整个实例)里,还没被关闭的游标数量已经顶到了OPEN_CURSORS参数设定的上限。

游标这个概念,你可以把它理解成数据库执行一条 SQL 时开的一个「临时窗口」。每执行一次查询或更新,数据库内部就会打开一个游标来承载结果集或执行上下文,语句执行完、结果集被消费完、Statement 被关闭之后,这个游标才会释放。如果应用层一直不关,或者连接池帮你「缓存」着不关,游标就会越积越多,直到撞上 1000 这个默认天花板。

这个报错最容易出现在 Java 应用 + 连接池 + 国产数据库的组合里,尤其是 Druid 搭配 YashanDB 的场景。原因很典型:Druid 有一套 PreparedStatement 缓存机制,本意是减少 SQL 预编译开销、提升性能,但它和 YashanDB 对游标的计数方式一旦没对齐,就会把「缓存」变成「泄漏」。我见过不少团队第一反应是去调大OPEN_CURSORS,结果只是把爆炸时间往后推,根因还在。

这篇就按排查路径来:先确认现象和影响面,再讲清楚 Druid 侧哪些参数在作祟,然后给出可复制的配置片段、YashanDB 侧的游标查询语句、复现与验证步骤,最后把常见误判点列出来。适合正在用 Druid + YashanDB、被这个报错卡住的后端和 DBA 同学跟做。

2. 前置准备:确认版本、拿到连接信息与 TaoToken 接入

在动手改配置之前,先把环境信息固定下来,不然排查过程会来回摇摆。你需要确认三件事:Druid 的版本、YashanDB 的版本、以及当前OPEN_CURSORS的实际取值。Druid 不同版本对share-prepared-statements和pool-prepared-statements的处理细节有差异,YashanDB 不同版本对游标的统计口径也可能不同。

如果你在本地或测试环境复现,建议直接用一套独立的连接配置,别在生产上试。连接信息(host、port、service name、账号密码)从你的配置中心或 DBA 那里拿。YashanDB 的 JDBC 驱动 jar 要放对位置,驱动类名和 URL 格式按官方文档来,别照搬 MySQL 的写法。

这里顺带说一个提效的点:排查这类问题时,经常需要临时跑一些诊断 SQL、对比不同参数下的行为,或者让 AI 帮你读一段 Druid 配置和报错日志。我平时会用 TaoToken 把模型对话和 API 调用统一起来,省得在多个平台之间切。它的模型对话入口在https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc,API Key 在https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys管理。API 基址是https://taotoken.net/api,注意这个地址不带 UTM 参数,配置客户端时直接用这个。

注意:TaoToken 在这里的角色是帮你做诊断辅助和日志分析,不是替代你的数据库客户端,也不是让你把生产库连接信息丢进去。诊断 SQL 还是在你自己的 YashanDB 会话里跑。

3. 可复制配置:Druid 侧到底哪几个参数在制造游标

先把「肇事参数」摆出来。Druid 里和 PreparedStatement 缓存相关的核心配置有三个:

参数作用危险点
share-prepared-statements是否在连接之间共享缓存的 PreparedStatement开启后同一 SQL 的 Statement 会被复用并长期持有
pool-prepared-statements是否开启 PreparedStatement 池化开启后 Statement 不随连接关闭而释放
max-open-prepared-statements每个连接最多缓存多少个 PreparedStatement设成正数会限制缓存量,设成 -1 表示不限制

问题就出在这三个参数同时开启、且max-open-prepared-statements设了一个正数(比如 100)的时候。Druid 会把每个连接上执行过的 PreparedStatement 缓存起来,YashanDB 则把每一个未关闭的 Statement 都算作一个活动游标。连接池里有 N 个连接,每个连接缓存 M 个 Statement,游标数就是 N×M 量级,很容易冲到 1000。

下面是一份「先止血」的配置片段,把缓存关掉,让游标随语句执行完就释放:

spring: datasource: druid: url: jdbc:yashandb://127.0.0.1:1688/yashan username: app_user password: your_password driver-class-name: com.yashandb.jdbc.Driver initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 # 关键三行:关闭 PreparedStatement 缓存 share-prepared-statements: false pool-prepared-statements: false max-open-prepared-statements: -1 validation-query: SELECT 1 FROM DUAL test-while-idle: true test-on-borrow: false test-on-return: false keep-alive: true

如果你用的是druid.properties或纯 Java 配置,对应写法是:

druid.share-prepared-statements=false druid.pool-prepared-statements=false druid.max-open-prepared-statements=-1
DruidDataSource ds = new DruidDataSource(); ds.setUrl("jdbc:yashandb://127.0.0.1:1688/yashan"); ds.setUsername("app_user"); ds.setPassword("your_password"); ds.setSharePreparedStatements(false); ds.setPoolPreparedStatements(false); ds.setMaxOpenPreparedStatements(-1);

改完这三行,重启应用,观察一段时间。如果 YAS-04003 不再出现,基本可以确认根因就在缓存策略上。但先别急着收工,还要去 YashanDB 侧确认游标确实降下来了,而不是被别的因素掩盖。

4. 验证请求:在 YashanDB 侧查游标、复现与确认修复

配置改完只是第一步,真正要看到的是数据库侧的游标数量变化。YashanDB 提供了几个视图可以查当前打开的游标。最直接的一条是:

SELECT b.sql_text FROM v$open_cursor a, v$sql b WHERE a.sql_id = b.sql_id;

这条语句会把当前所有打开游标对应的 SQL 文本列出来。如果同一段 SQL 反复出现很多次,说明这些 Statement 没有被释放,缓存或泄漏就在这条 SQL 上。你可以再加一层聚合,看哪类 SQL 占用的游标最多:

SELECT b.sql_text, COUNT(*) AS cursor_cnt FROM v$open_cursor a, v$sql b WHERE a.sql_id = b.sql_id GROUP BY b.sql_text ORDER BY cursor_cnt DESC;

想定位到具体是哪个会话在堆积游标,把v$session关联进来:

SELECT s.sid, s.serial#, s.username, s.program, COUNT(*) AS cursor_cnt FROM v$open_cursor a JOIN v$session s ON a.sid = s.sid GROUP BY s.sid, s.serial#, s.username, s.program ORDER BY cursor_cnt DESC;

复现步骤可以这样设计:在测试环境把 Druid 的三个缓存参数按「肇事配置」打开,max-open-prepared-statements设成 100,然后用压测工具或循环脚本对同一个接口打几百次请求,每次请求执行同一条带参数的查询。跑几分钟后,去查上面的游标聚合语句,你会看到那条 SQL 的cursor_cnt持续上涨。接着把配置改成关闭缓存,重启,再压同样的量,游标数会稳定在一个低位,不再单调增长。

修复确认的标准不是「报错消失」,而是「游标数在压测期间保持平稳」。如果只是报错消失但游标数还在缓慢爬升,说明还有别的 Statement 没关,比如代码里手动创建的Statement或ResultSet没有在finally里关闭。

5. 本篇常见错排查:别把锅全甩给连接池

排查 YAS-04003 时,有几个高频误判点,我按踩坑顺序列一下。

第一个坑是只调大OPEN_CURSORS。把 1000 改成 5000,报错确实会晚点出现,但游标泄漏的本质没解决,只是把定时炸弹的引信拉长了。而且OPEN_CURSORS调太大,会占用更多会话内存,得不偿失。正确顺序是先查游标来源,再决定要不要动这个参数。

第二个坑是以为关了pool-prepared-statements就万事大吉。如果代码里用了 MyBatis 或 JPA,框架自己也可能维护 Statement 缓存,或者在某些写法下不释放ResultSet。这时候要结合v$open_cursor的结果去看,到底是连接池缓存的,还是业务代码泄漏的。区分方法:连接池缓存通常表现为同一 SQL 在多个连接上各有一份;代码泄漏通常表现为某个会话的游标数异常高。

第三个坑是忽略share-prepared-statements和pool-prepared-statements的联动。这两个参数不是独立的,share-prepared-statements为 true 时,Druid 会在连接之间共享 Statement,行为更复杂。排查阶段建议两个都关,确认问题消失后,如果确实需要性能优化,再单独评估是否开启其中一个,并配合max-open-prepared-statements设一个合理上限。

第四个坑是没看连接池的实际连接数。max-active设成 50,每个连接缓存 20 个 Statement,理论上限就是 1000,刚好撞线。所以排查时要把max-active和缓存参数一起看,算一下最坏情况下的游标上限。

第五个坑是诊断 SQL 本身写错。v$open_cursor和v$sql的关联字段在不同版本里可能有差异,如果查出来是空结果,先确认视图名和字段名,别急着下结论说「没有游标泄漏」。

如果你在分析这些视图输出或者读 Druid 日志时想让 AI 帮忙梳理,可以用 TaoToken 的模型对话把日志片段贴进去问,入口还是https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=model_chat。长期做编码和 Agent 类任务的,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=coding_plan。

6. 接入与排障入口:把诊断流程固化下来

这套排查流程跑通之后,建议把它固化成团队的操作手册:出现 YAS-04003 时,第一步查v$open_cursor聚合,第二步核对 Druid 三个缓存参数,第三步算max-active × max-open-prepared-statements的理论上限,第四步再决定是改配置还是改代码。

需要查 API Key 和接入方式的,走https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=api_keys;接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=doc;控制台在https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=console。API 基址统一用https://taotoken.net/api,不带 UTM。

最后补一句实操经验:改完 Druid 配置后,别只看应用日志里报错没了就收工,一定要在压测期间盯着v$open_cursor的计数曲线。曲线平稳,才算真的修好;曲线还在涨,就回到第 5 节逐个排除。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询