preparedstatement在java端通过jdbc驱动决定路径:useserverprepstmts=true时发送com_stmt_prepare至mysql服务端解析并缓存执行计划,后续execute复用;false时则客户端拼接sql走com_query,无计划复用且安全性弱。

Java 的 PreparedStatement 和 MySQL 的执行计划缓存是两个不同层级的机制,它们协同工作但职责分明:前者在 JDBC 驱动层做参数安全与复用准备,后者在 MySQL 服务端决定是否复用已编译的查询路径。MySQL 8.0 已彻底移除查询缓存(Query Cache),但它仍会缓存**执行计划**——不过这个“缓存”不是靠 SQL 文本匹配,而是依赖于预处理语句(COM_STMT_PREPARE)的生命周期管理。
PreparedStatement 在 Java 端怎么工作
调用 connection.prepareStatement(sql) 时,JDBC 驱动根据配置决定走哪条路径:
-
服务器端预处理(推荐且默认开启):驱动向 MySQL 发送
COM_STMT_PREPARE命令,把带?的 SQL 模板传过去;MySQL 解析、校验语法、生成执行计划并保存,返回一个唯一statement_id;后续execute()只需传参和该 ID,跳过解析和优化阶段。 -
客户端模拟(
useServerPrepStmts=false):驱动在 Java 内存里把参数转义后拼成完整 SQL 字符串(如'Alice''; DROP TABLE users;--'),再以普通COM_QUERY方式发送;每次执行都重新解析、优化,无计划复用,且无法防御所有注入变种。
关键点:只有服务器端预处理才能真正复用 MySQL 内部的执行计划;客户端模拟只是字符串拼接,不触发服务端任何缓存逻辑。
MySQL 怎么缓存执行计划
MySQL 不像 Oracle 那样有全局共享的“执行计划缓存池”,它的执行计划复用绑定在预处理语句对象的生命周期上:
- 每个通过
COM_STMT_PREPARE创建的语句,在 MySQL 内部对应一个Prepared_statement对象,其中包含已生成的执行计划(含索引选择、JOIN 顺序等)。 - 只要该语句未被
COM_STMT_CLOSE或连接断开销毁,后续用同一statement_id执行(COM_STMT_EXECUTE)就直接复用原计划,不重新优化。 - 计划不会跨连接共享,也不会持久化到磁盘;它属于单个连接的内存结构,重启连接即失效。
注意:这和已被删除的 Query Cache 完全无关——Query Cache 缓存的是“结果集”,而这里复用的是“执行路径”。即使表数据变了,只要语句模板不变,执行计划依然复用(可能查到新数据,也可能因统计信息滞后选错索引)。
为什么 MySQL 8.0 删了 Query Cache 却保留预处理计划复用
根本原因在于设计目标不同:
- Query Cache 要求 SQL 文本完全一致 + 表未更新,高并发写入下极易失效,锁竞争严重,实际收益远低于开销。
- 预处理计划复用由应用主动控制(prepare → execute × N → close),不依赖表变更监听,无全局锁,复用确定性强,对 OLTP 场景更友好。
- 执行计划本身轻量(不含数据),复用成本低;而结果集缓存需大量内存+强一致性维护,代价过高。
所以现代最佳实践是:启用 useServerPrepStmts=true,配合连接池的 PreparedStatementCache(缓存 statement_id 映射),让 prepare 开销也摊薄——这样从 Java 到 MySQL,整条链路都实现了结构复用与参数隔离。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











