SQL ShardingSphere 的 SQL 解析缓存与 hint 强制路由结合使用

3次阅读

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

SQL ShardingSphere 的 SQL 解析缓存与 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 的 connectionInitSqlmybatis@SelectProvider 延迟生成 SQL,hint 可能还没来得及设就进了解析流程
  • ShardingSphere 的 SQLParseEngine 缓存 key 包含 databaseType(如 mysqlpostgresql),跨数据库类型部署时,同一 SQL 字符串在不同库类型实例中会生成不同缓存项,但 hint 行为不会自动适配方言差异
  • 当使用 ShardingSphere-Proxy 时,HintManager 在客户端无效,必须用注释形式;此时唯一稳妥做法是让 Proxy 层关闭 SQL 解析缓存(props.sql-parse-cache-enabled=false),或接受小概率路由偏差

事情说清了就结束

text=ZqhQzanResources