shardingsphere 的 sqlparseengine 缓存忽略 hint 路由信息,因缓存 key 仅基于原始 sql 字符串,不包含 /+ shardingsphere hint / 注释内容,导致 hint 修改路由目标时仍复用旧解析结果。

ShardingSphere 的 SQLParseEngine 缓存会忽略 hint 路由信息
ShardingSphere 在首次解析 SQL 时会将 SQLStatement 对象缓存进 SQLParseEngine 的 LRUCache(默认容量 2000),后续相同 SQL 文本直接复用。但这个缓存只基于原始 SQL 字符串,不感知 /*+ ShardingSphere hint */ 这类注释内容 —— 即使 hint 改变了实际路由目标,缓存仍返回旧的、未绑定 hint 的 SQLStatement。
常见错误现象:/*+ ShardingSphere: sharding_hint_table=order_2024 */ SELECT * FROM t_order 偶尔路由到错误分片,尤其在高并发下复现率升高;日志里出现 HintManager.setDatabaseShardingValue 生效但查询没走对应库。
- 根本原因:hint 注释在 SQL 解析阶段被
SQLParseEngine当作普通注释丢弃,不参与 AST 构建,因此无法影响缓存 key - 使用场景:适用于需要动态切换分片(如灰度查老表、数据迁移验证)但又依赖高频复用 SQL 解析结果的业务
- 参数差异:
sql-show: true日志里能看到 hint 被打印出来,但这只是日志层处理,不影响解析缓存逻辑
绕过解析缓存的两种实操方式
不能关缓存(性能暴跌),也不能靠「改 SQL 文本」硬绕(破坏可读性、增加慢 SQL 风险),得在 hint 机制内做文章。
- 用
HintManager+ 真实参数化 SQL:把 hint 操作从注释移到 Java 代码里,例如HintManager.getInstance().addDatabaseShardingValue("t_order", "order_2024"),这样 SQL 文本不变,但路由决策在执行前由HintManager注入,完全绕过解析缓存干扰 - 强制刷新单条 SQL 缓存:调用
SQLParseEngine.getCache().invalidate(new CacheKey(sql, databaseType)),适用于已知某条带 hint 的 SQL 必须立即生效(比如运维脚本),但别在循环里调,会拖慢吞吐 - 兼容性注意:ShardingSphere 5.3.2+ 对
HintManager的线程局部变量清理更严格,用完必须close(),否则可能污染后续请求
HintManager 和 SQL 注释 hint 的行为差异
两者底层都走 HintManager,但触发时机和作用域不同,直接影响是否受解析缓存制约。
- SQL 注释形式(
/*+ ... */):在 SQL 解析阶段被提取,但提取动作发生在缓存命中之后 —— 即先查缓存,再补 hint,所以缓存错,hint 就白加 - Java API 形式(
HintManager.getInstance()...):在ShardingSphereJDBCExecutor执行前注入,此时 SQL 已解析完毕,路由引擎直接读取HintManager的当前值,和缓存无关 - 性能影响:API 方式多一次
ThreadLocal.get(),但相比重新解析 SQL,开销可忽略;而注释方式看似“写 SQL 就行”,实则隐含缓存失效风险,长期看反而更重
生产环境容易漏掉的三个细节
不是所有 hint 场景都一样,这几个点不检查,上线后问题很难定位。
-
HintManager必须在DataSource获取连接前设置,如果用了 HikariCP 的connectionInitSql或 MyBatis 的@SelectProvider延迟生成 SQL,hint 可能还没来得及设就进了解析流程 - ShardingSphere 的
SQLParseEngine缓存 key 包含databaseType(如MySQL或PostgreSQL),跨数据库类型部署时,同一 SQL 字符串在不同库类型实例中会生成不同缓存项,但 hint 行为不会自动适配方言差异 - 当使用
ShardingSphere-Proxy时,HintManager在客户端无效,必须用注释形式;此时唯一稳妥做法是让 Proxy 层关闭 SQL 解析缓存(props.sql-parse-cache-enabled=false),或接受小概率路由偏差











