编程 Connector/J 预处理没走服务端:useServerPrepStmts 默认 false,缓存上限 25/256 怎么改

2026-11-02 00:03:53

Connector/J 预处理没走服务端:useServerPrepStmts 默认 false,缓存上限 25/256 怎么改

三个现象同时出现

general_log 里干干净净,只有 Query select 42、Query select 43,没有 Prepare,也没有 Execute。DBA 顺手查了一下 Com_stmt_prepare,计数没动;performance_schema.prepared_statements_instances 里也是空的,结论很直接:库里根本没有预处理语句。

再去连接串里翻,useServerPrepStmts 明明加上了。问题出在写法上:文档写得很清楚,在 URL 里只写属性名而不给值,什么都不会设置。只加 useServerPrepStmts 不会启用服务端预处理,必须写成 useServerPrepStmts=true。官方属性表就在 dev.mysql.com/doc/connector-j 里,默认值一项一项列着。

另一种情况更隐蔽:参数写对了,但 URL 里还挂着 useCursorFetch=false,或者 emulateUnsupportedPstmts 在背后悄悄回退,现象和上面一模一样。

两条路径的分工

Connector/J 同时实现客户端模拟和服务端两套预处理,默认走客户端,原因是早期 MySQL 不支持或有实现问题。要开启服务端,得显式设置 useServerPrepStmts=true。

两条路径在 general_log 里的差别很直观:

  • 客户端模拟:参数在本地被拼进 SQL,只发一整条 COM_QUERY,日志里是 Query select 42、Query select 43。
  • 服务端预处理:先发 Prepare select ?,再发 Execute select 42、Execute select 43。

四个开关的默认值与判断依据

属性默认值生效版本管什么
useServerPrepStmtsfalse3.1.0是否真正在服务端 Prepare
cachePrepStmtsfalse3.0.10是否缓存解析/适用性检查/服务端语句本身
prepStmtCacheSize253.0.10缓存条目数上限
prepStmtCacheSqlLimit2563.0.10可缓存的 SQL 长度上限

相关的一组默认值也一并记下:useCursorFetch=false、defaultFetchSize=0、rewriteBatchedStatements=false、queryInfoCacheFactory=com.mysql.cj.PerConnectionLRUFactory(8.0.29 之前叫 parseInfoCacheFactory)。

互斥与依赖关系

  • cachePrepStmts=false 时,prepStmtCacheSize 和 prepStmtCacheSqlLimit 设了也不起作用;只有打开 cachePrepStmts,后两个参数才有意义。HikariCP 的 wiki 也重复了这一点。
  • cachePrepStmts 缓存的并不是一样东西:客户端模拟预处理语句的解析阶段、服务端预处理的“适用性检查”结果,以及服务端预处理语句本身。那条适用性检查要多一次网络往返,缓存之后才可以省掉。
  • useCursorFetch=true 且 defaultFetchSize>0,或者语句上调了 setFetchSize(n>0) 时,才会用游标结果集;此时 useServerPrepStmts 会被自动设为 true,因为游标只对服务端预处理语句可用。也就是说,useCursorFetch=true 会覆盖 useServerPrepStmts=false。
  • emulateUnsupportedPstmts=true 是默认值。服务端无法新建预处理语句时——可能是 max_prepared_stmt_count 到顶,也可能被整体禁用——Connector/J 默认静默回退到客户端模拟,不报错。所以“开了服务端预处理却看不到 Prepare”,先查这个开关。
  • 大参数改回普通参数有个坑:用 setBinaryStream、setAsciiStream、setCharacterStream、setBlob、setClob 设过参数后,数据要到 execute() 时才发送,流在那之后已经关闭。要把该参数改回非大参数并重新执行,必须先 clearParameters() 再重新设置所有参数。

验证步骤

  1. 打开 general_log,看语句是以 Query 出现,还是以 Prepare / Execute 成对出现。
  2. 查 Com_stmt_prepare、Com_stmt_close 状态变量,确认服务端确实收到了 Prepare。
  3. 查 performance_schema.prepared_statements_instances,看当前存在的服务端语句。
  4. 核对 URL 里每个属性都带了 =值,尤其是 useServerPrepStmts=true 这种。

取舍

维基百科 Prepare (SQL) 词条把边界说得很清楚:客户端模拟对只执行一次的查询能减少往返;服务端预处理对多次执行更快;两者在防注入上同样有效;预处理无法在编译时完成全部优化,因为最佳计划可能依赖具体参数值。

HikariCP 给的建议是:prepStmtCacheSize 默认 25 太保守,建议 250–500;prepStmtCacheSqlLimit 默认 256 对 Hibernate 生成的 SQL 太短,建议 2048;cachePrepStmts 默认 false,不打开这个开关,上面两个参数无效;并建议 useServerPrepStmts=true。

阿里云 AnalyticDB 文档列了几种语言只开客户端插值的写法:JDBC 用 useServerPrepStmts=false、MariaDB 相同、Go 用 interpolateParams=true、PDO 用 ATTR_EMULATE_PREPARES=true;同时提示 useCursorFetch=true 会覆盖 useServerPrepStmts=false。

其余几个预处理相关默认值:useStreamLengthsInPrepStmts=true、processEscapeCodesForPrepStmts=true、autoClosePStmtStreams=false、generateSimpleParameterMetadata=false。

站内 #8259 讲的是 PDO 驱动层的模拟预处理(emulate prepares、LIMIT '10' 报 1064、PARAM_INT、同名命名参数 HY093),#8184 讲的是引擎侧 max_prepared_stmt_count=16382 的配额与消耗分型;这里是 Connector/J 在 Java/JDBC 侧的四个开关。

复制全文 生成海报 MySQL preparedstatement JDBC Connector J Java

推荐文章

程序员茄子在线接单