preparedstatement缓存是jdbc驱动在客户端对已编译sql执行计划的复用机制,缓存对象为封装sql模板、参数元信息及statement id的预编译查询对象(如cachedquery),非查询结果或preparedstatement实例本身。

PreparedStatement 缓存不是数据库端的“查询结果缓存”,而是 JDBC 驱动在客户端(Java 应用侧)对已编译的 SQL 执行计划做的复用机制。它的核心目标是避免重复解析、编译相同结构的 SQL,从而降低数据库压力、提升执行效率。
缓存对象是什么?
缓存的不是结果,也不是 PreparedStatement 实例本身,而是驱动内部封装的预编译查询对象(如 PostgreSQL 的 CachedQuery,MySQL Connector/J 中的 ServerPreparedStatement 或客户端缓存条目)。它包含:SQL 模板的解析树、参数元信息、执行计划(部分场景下)、以及服务端分配的 statement ID(启用服务端预编译时)。
缓存发生在哪一层?
分两种典型路径:
-
客户端缓存:JDBC 驱动(如 MySQL 的
ClientPreparedStatement)在内存中保存已处理过的 SQL 模板,下次遇到完全一致的 SQL 字符串,直接复用内部状态;需显式开启cachePrepStmts=true -
服务端预编译 + 客户端缓存协同:启用
useServerPrepStmts=true后,SQL 第一次发送给数据库做 COM_STMT_PREPARE,服务端返回 statement ID;客户端再把该 ID 和对应 SQL 模板一起缓存起来,后续复用 ID 执行,跳过再次 prepare 步骤
什么情况下缓存会失效或不命中?
缓存依赖“SQL 字符串完全一致”和“参数类型/顺序稳定”。以下操作会导致新缓存项生成或旧项被挤出:
- SQL 中拼接表名、列名、ORDER BY 子句(如
"SELECT * FROM " + table + " WHERE id = ?") - IN 子句动态拼占位符(
"id IN (?, ?, ?)"vs"id IN (?, ?, ?, ?)"被视为不同语句) - 同一逻辑反复调用
setString(1, x)和setObject(1, x, Types.VARCHAR),驱动可能判为不同类型 - 缓存容量超限(如
prepStmtCacheSize=250但实际有 300 种高频 SQL 模板)
缓存需要你主动管理吗?
不需要手动“清空”,但必须规范使用:
- 每次用完
PreparedStatement必须调用close()——否则驱动无法回收对应缓存资源,尤其服务端预编译句柄会长期占用 - 连接池(如 HikariCP)不自动缓存 PreparedStatement,它只提供可复用的 Connection;缓存行为由 JDBC URL 参数或数据源配置驱动(如
cachePrepStmts=true&prepStmtCacheSize=250) - MyBatis 等框架的一级/二级缓存是另一层机制,与 JDBC 驱动的 PreparedStatement 缓存无关
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











