预编译与绑定与多语句与管道

共 19 题
📑 题目列表 19 题
#
★★★

1. 服务端预编译与客户端预编译的差异,MySQL 默认客户端预编译?

服务端预编译与客户端预编译的差异是什么?为什么 MySQL 默认采用客户端预编译?

  • 服务端预编译与客户端预编译的区别
  • MySQL 默认客户端预编译的原因
  • 预编译在连接池下的生命周期

客户端预编译(客户端模拟预编译)指驱动在客户端对参数做转义处理并拼入 SQL 文本,以普通文本协议(COM_QUERY)整体发送给服务器执行,服务器不做预解析与计划缓存;服务端预编译则通过 COM_STMT_PREPARE 让服务器预先解析并缓存执行计划,之后用 COM_STMT_EXECUTE 传参数执行,可复用解析结果与执行计划。MySQL 默认采用客户端预编译,是因为服务端预编译语句的缓存与连接绑定,连接断开、复用或 DEALLOCATE 后缓存会失效,而客户端预编译不依赖服务器端缓存,兼容性更好,连接池复用下更稳定。

关键是区分"参数分离"与"执行计划缓存"两个层面。客户端预编译只做参数处理,服务端预编译才真正复用计划;MySQL 默认客户端预编译主要是为了规避连接池复用导致服务端缓存失效的问题,需要时通过 useServerPrepStmts 显式开启服务端预编译。

#
★★★

2. 绑定变量(Bind Variables)的执行计划缓存影响?

绑定变量(Bind Variables)对执行计划缓存有何影响?为什么不使用绑定变量会导致硬解析费用上升?

  • 绑定变量与执行计划缓存
  • 硬解析与软解析
  • 绑定变量对优化器选择的影响

使用绑定变量时,SQL 文本保持稳定(参数以占位符 ? 表示),数据库可以按 SQL 文本哈希后缓存执行计划,后续相同结构不同参数的语句直接命中缓存,称为软解析,可显著降低解析与计划生成开销。若不使用绑定变量,每次拼接不同字面值都会生成不同的 SQL 文本,导致缓存无法命中,每次都要完整解析、权限检查并重新生成执行计划,即硬解析,在高并发下会严重消耗 CPU 与锁资源。绑定变量的代价是优化器无法根据具体参数值做针对性的优化,例如无法对特定值做索引的选择性判断,可能不如字面量优化准确。

核心是"计划缓存按 SQL 文本匹配"。绑定变量保证文本稳定以命中缓存,换取通用性;字面量虽利于特定值优化但破坏缓存命中。需在复用率与优化精度之间权衡,遇到数据倾斜时可用直方图或 hint 辅助。

#
★★★

3. 预编译语句(PreparedStatement)的优势,防 SQL 注入、执行计划复用?

预编译语句(PreparedStatement)的优势有哪些?它是如何防 SQL 注入并复用执行计划的?

  • PreparedStatement 防 SQL 注入原理
  • 执行计划复用
  • 与字符串拼接 SQL 的对比

PreparedStatement 的优势在于:一是防 SQL 注入,SQL 与参数在协议层分离,参数作为数据传给服务器,不会被当作 SQL 语句解析,因此用户输入无法改变 SQL 结构,从根本上杜绝了拼接注入;二是执行计划复用,同一 SQL 结构仅需解析一次,后续只用新的参数执行,减少解析与计划生成开销;三是类型安全与性能,参数以正确的类型传输,避免类型转换与隐式转换。与之相对,字符串拼接 SQL 既存在注入风险,又因每次 SQL 文本不同而无法复用计划。

防注入的关键是"参数与 SQL 分离、参数不被当作 SQL 解析"。执行计划复用依赖 SQL 文本稳定。两者共同构成 PreparedStatement 的核心价值,但前提是参数必须通过占位符传递,不能把参数拼进 SQL 文本。

#
★★★

4. 多语句执行(Multi-Statements)的安全风险,SQL 注入?

多语句执行(Multi-Statements)存在哪些安全风险?为什么它常与 SQL 注入攻击关联?

  • 多语句执行机制
  • 多语句与注入攻击
  • 安全防护措施

多语句执行允许一条请求中拼接多条以分号分隔的 SQL 语句(如 MySQL 的 allowMultiQueries、SELECT 后追加 UPDATE/DELETE)。其安全风险在于扩大了注入攻击面:若应用允许多语句并在拼接处缺乏防护,攻击者可在合法查询后追加 DELETE、UPDATE、DROP 等危险语句,从而直接操纵数据或破坏结构。此外多语句会破坏单条语句的原子性与错误处理语义,一条失败可能影响后续语句。因此默认应关闭多语句支持,参数化查询并做好输入校验与权限最小化。

多语句本身不是漏洞,但放大了注入后果。单独的参数化查询无法防御多语句注入,因为攻击面在于允许拼接多条语句;关闭多语句是减少攻击面的关键防护手段。

#
★★★

5. 预编译语句的执行计划何时失效(表结构变更、统计更新)与数据库端 plan cache 管理

预编译语句的执行计划何时失效?表结构变更、统计更新如何影响计划,数据库端 plan cache 如何管理?

  • 执行计划失效条件
  • 表结构变更与统计更新
  • plan cache 管理策略

预编译语句的执行计划会在以下情况失效:表结构变更(增删列、改索引、重建表)、统计信息更新导致基数估计变化、SQL 文本变化、缓存溢出或手工清理(如 DEALLOCATE 释放预编译语句)。失效后数据库会重新解析并生成新计划。数据库端 plan cache 通常以 SQL 文本为键缓存计划,并设置容量上限、LRU 淘汰与失效机制;MySQL 8.0 针对预编译语句有独立的缓存,而优化器在检测到统计变化或表结构变化时会自动使计划失效并重新生成。

计划失效的核心是"基础信息变化导致计划可能不再最优"。管理 plan cache 要平衡命中率与计划新鲜度,避免因统计长期不更新或用旧计划导致性能劣化,必要时用 ANALYZE、EXPLAIN 与手动清理干预。

#
★★

6. 批量 INSERT 多值(Multi-VALUES INSERT)的性能?

批量 INSERT 多值(Multi-VALUES INSERT)相比单条 INSERT 有哪些性能优势?其适用边界是什么?

  • Multi-VALUES INSERT 的机制
  • 减少网络往返与日志开销
  • 适用边界与限制

Multi-VALUES INSERT 通过一条 INSERT 语句携带多组值(INSERT INTO t VALUES (...),(...),...),比逐条 INSERT 减少了网络往返次数、SQL 解析次数与日志刷盘次数,从而显著提升批量写入吞吐。同时在 MySQL 中还可借助 rewriteBatchedStatements 将 JDBC 的 addBatch 自动改写为多值 INSERT。其适用边界在于:单条语句过大(超过 max_allowed_packet)时需分批,事务过大时锁与 redo 日志压力增加,且多值 INSERT 不利于按行精确的错误处理与部分失败回滚。

性能提升源于"减少往返与解析",本质是把多次小操作合并为一次大操作。但需权衡单条语句大小上限、事务规模与错误处理粒度,据此确定分批大小。

#
★★

7. 服务端预编译语句在连接池/PgBouncer 复用下的生命周期,为什么需要显式 DEALLOCATE 或按连接缓存?MySQL 的 useServerPrepStmts 如何控制?

服务端预编译语句在连接池/PgBouncer 复用下如何管理生命周期?为什么需要显式 DEALLOCATE 或按连接缓存?MySQL 的 useServerPrepStmts 如何控制?

  • 服务端预编译与连接的绑定
  • 连接池复用下的缓存管理
  • DEALLOCATE 与 useServerPrepStmts

服务端预编译语句(PREPARE 生成的语句)与创建它的连接绑定,缓存保存在连接对应的会话语境中。连接池复用连接时,若池中连接被多个应用流程共享,预编译缓存可能残留或错乱,因此需要显式 DEALLOCATE 释放,或按连接缓存(连接池中每个连接维护自己的准备语句缓存)以保证一致性与避免资源泄漏。MySQL 的 useServerPrepStmts=true 时驱动会调用服务端 PREPARE,配合 cachePrepStmts 缓存已准备的语句;PgBouncer 的事务级/语句级池模式下,连接在事务间切换会导致预编译语句的会话状态丢失,需配合 DEALLOCATE 或使用包级模式。

核心是"预编译语句的会话绑定性与连接复用之间的冲突"。管理策略是连接内置缓存、生命周期与连接一致,断连时自动释放,跨连接复用需显式 DEALLOCATE。useServerPrepStmts 决定是否启用服务端预编译,cachePrepStmts 决定缓存多少个。

#
★★

8. 协议层批量写入,executeBatch 与多值 INSERT 在 MySQL 文本协议/二进制协议下分别如何减少网络往返与解析开销?

协议层批量写入中,executeBatch 与多值 INSERT 在 MySQL 文本协议/二进制协议下如何减少网络往返与解析开销?

  • executeBatch 协议层机制
  • 文本协议与二进制协议
  • 网络往返与解析开销

executeBatch 在协议层可一次发送多条参数或语句,减少往返;配合多值 INSERT 时,一条语句携带多组数据进一步压缩往返与解析次数。文本协议下数据以文本编码传输,参数需要转义且解析开销较大;二进制协议(COM_STMT_EXECUTE)下参数以二进制类型编码传输,无需转义,服务器按类型直接解析,减少编解码与解析开销。rewriteBatchedStatements 可将批量 executeBatch 改写为多值 INSERT,在文本协议下减少语句数,在二进制协议下优化参数绑定。

关键在于"字节级打包 vs 逐条往返"。二进制协议按类型编码省去转义与转换,多值 INSERT 合并语句减少解析与日志,二者结合最大化批量写入效率。

#
★★

9. MySQL 文本协议与二进制预处理协议(COM_STMT_PREPARE/EXECUTE)的差异?二进制协议对日期/浮点/二进制字段的编解码有何优势?

MySQL 文本协议与二进制预处理协议(COM_STMT_PREPARE/EXECUTE)有哪些差异?二进制协议对日期/浮点/二进制字段的编解码有何优势?

  • 文本协议与二进制协议
  • COM_STMT_PREPARE/EXECUTE
  • 日期/浮点/二进制字段编解码

文本协议下 SQL 与结果以文本字符串传输,参数需转义、结果需按文本解析,类型转换开销大;二进制协议(COM_STMT_PREPARE 准备、COM_STMT_EXECUTE 执行)下参数与结果按各自类型编码为二进制,无需转义,服务器按类型直接解析。对日期、浮点、二进制字段,二进制协议直接传输原生二进制表示,避免文本与二进制之间的来回转换,精度不受文本表示丢失,且二进制大对象(BLOB)无需转义,传输与解析更高效,同时减少类型安全风险。

二进制协议的核心优势是"按类型原生编码、免转义、免文本转换"。对时间戳、浮点、BLOB 等类型尤其明显,既省开销又保精度。

#
★★

10. 管道化(Pipelining)的应用,MySQL 8.0、PostgreSQL 14?

管道化(Pipelining)在 MySQL 8.0 与 PostgreSQL 14 中如何应用?它能带来哪些收益?

  • 管道化机制
  • MySQL 8.0 与 PostgreSQL 14 的实现
  • 往返延迟与吞吐收益

管道化(Pipelining)允许客户端在未等待前一条语句响应的情况下连续发送多条请求,从而减少网络往返(RTT)导致的延迟,提高吞吐。PostgreSQL 14 引入管道查询模式(pipeline mode),客户端可批量发送多个查询而无需等待每条结束;MySQL 在多语句与批量请求中也存在类似机制。管道化对高 RTT 网络、批量操作收益显著,但需注意错误处理:若一条语句失败,后续语句如何处理取决于管道语义,且与事务边界、结果集消费顺序需谨慎配合。

管道化本质是"以流水线方式重叠往返延迟"。收益来自减少等待,但错误定位与事务边界会变复杂,是工程上权衡的关键点。

#
★★

11. MySQL JDBC 的 cachePrepStmts 参数与性能?

MySQL JDBC 的 cachePrepStmts 参数如何影响性能?它与 useServerPrepStmts 的关系是什么?

  • cachePrepStmts 参数
  • 预编译语句缓存
  • 与 useServerPrepStmts 的关系

cachePrepStmts=true 时,MySQL JDBC 驱动会在连接内缓存已创建的 PreparedStatement 对象,避免对相同 SQL 重复调用 PREPARE 与解析开销,从而提升性能。cachePrepStmts 与 useServerPrepStmts 配合:useServerPrepStmts=true 启用服务端预编译,cachePrepStmts 缓存这些预编译语句(含服务器端准备语句),避免重复的网络往返准备;若 useServerPrepStmts=false,cachePrepStmts 也能缓存客户端预编译对象,减少对象创建与解析开销。合理设置两者可显著提升高频重复 SQL 的性能。

cachePrepStmts 控制"按 SQL 缓存预编译对象",useServerPrepStmts 控制"是否走服务端预编译"。两者正交但配合使用,才能既缓存对象又复用服务器端计划。

#
★★

12. 预编译语句的批量执行(addBatch)与性能?

预编译语句的批量执行(addBatch)如何实现?它对性能的影响是什么?

  • addBatch 与 executeBatch
  • 批量执行减少往返
  • 性能提升与注意事项

PreparedStatement 的 addBatch 将多条参数集加入批处理,executeBatch 一次性提交执行,协议层可一次发送多条参数,减少网络往返与解析次数,从而提升批量写入性能。其性能收益与批大小、驱动是否改写为多值 INSERT(rewriteBatchedStatements)相关。注意事项:批大小过大会导致内存与锁压力,过大事务影响回滚粒度;部分数据库对 batch 的原子性、错误返回处理不同,需按返回值逐条处理错误。

addBatch 的核心是"参数打包、一次往返"。批大小与错误处理粒度是权衡重点,配合 rewriteBatchedStatements 可进一步合并为多值 INSERT。

#
★★

13. useServerPrepStmts 与 cachePrepStmts 组合下 SQL 文本是否仍带参数(? 占位符)及安全意义

useServerPrepStmts 与 cachePrepStmts 组合下,发送到服务器的 SQL 文本是否仍带参数(? 占位符)?其安全意义是什么?

  • 占位符与 SQL 文本
  • useServerPrepStmts 与 cachePrepStmts
  • 安全意义

无论是否启用 useServerPrepStmts,发送到服务器的 SQL 文本都带 ? 占位符、参数与 SQL 分离:启用服务端预编译时,PREPARE 发送带 ? 的 SQL,EXECUTE 单独发送参数绑定的值;未启用时,驱动在客户端做参数替换,但替换发生在驱动内部而非服务器解析层。cachePrepStmts 只影响预编译对象的缓存,不改变 SQL 文本是否带占位符。安全意义在于参数始终作为数据而非 SQL 结构传递,即使客户端替换,也避免了把用户输入拼进 SQL 文本导致的注入。

关键区分"占位符始终存在"与"参数替换发生在哪一层"。安全取决于参数不被当作 SQL 结构解析,而非替换发生在客户端还是服务端。cachePrepStmts 不影响占位符语义。

#
★★

14. ORM 与预编译的交互,MyBatis/JPA 的 #{} 与 ${} 如何映射到绑定变量,为什么 ${} 拼接存在注入风险,动态 SQL 如何保持计划复用?

ORM 与预编译如何交互?MyBatis/JPA 的 #{} 与 ${} 如何映射到绑定变量,为什么 ${} 拼接存在注入风险,动态 SQL 如何保持计划复用?

  • #{} 与 ${} 的区别
  • ${} 注入风险
  • 动态 SQL 与计划复用

MyBatis 中 #{} 会把参数映射为绑定变量(PreparedStatement 的 ? 占位符),由驱动以参数方式传递,安全且可复用计划;${} 则把参数值直接拼接到 SQL 文本中,用户输入若含恶意内容会改变 SQL 结构,造成注入风险,且因文本变化无法复用计划。JPA/JPQL 的命名参数或位置参数同样映射为绑定变量。动态 SQL 要保持计划复用,应尽量让 SQL 结构稳定,仅让参数通过绑定变量变化,避免因条件变化导致 SQL 文本频繁改变而无法命中计划缓存。

核心是"参数是数据还是结构"。#{} 参数作为数据,${} 参数作为结构。动态 SQL 在满足业务所需分支的前提下,把可变部分都收敛为绑定变量,才能在灵活性与计划复用之间取得平衡。

#
★★

15. 预编译语句的监控与诊断,如何观测服务端预编译的命中率、缓存条目与失效原因,COM_STMT_PREPARE 语句量异常说明什么?

如何监控与诊断预编译语句?如何观测服务端预编译的命中率、缓存条目与失效原因,COM_STMT_PREPARE 语句量异常说明什么?

  • 预编译监控指标
  • 命中率与缓存条目
  • COM_STMT_PREPARE 异常分析

服务端预编译的监控可通过数据库状态变量与性能视图观测:如 MySQL 的 Prepared_stmt_count(当前预编译语句数)、Com_stmt_prepare/Com_stmt_execute 计数,以及 general log 或 performance_schema 中的语句采样。命中率可通过相同 SQL 的 PREPARE 次数与 EXECUTE 次数之比估计,理想情况下 PREPARE 远少于 EXECUTE。COM_STMT_PREPARE 语句量异常偏大说明预编译语句未按预期复用(如 cachePrepStmts 未开启、SQL 文本频繁变化、连接池频繁重建导致缓存失效),需要排查缓存配置与 SQL 文本稳定性。

命中率是"PREPARE 次数 vs EXECUTE 次数"的比值。联系项:缓存失效原因(连接重建、SQL 变化、缓存上限)与 COM_STMT_PREPARE 偏大的现象,据此定位是配置问题还是 SQL 文本不稳定问题。

#
★★

16. 管道化(pipelining)的工程边界,MySQL 8.0/PostgreSQL 14 管道化对批处理与往返延迟的收益,与事务边界、错误处理的交互?

管道化(pipelining)的工程边界是什么?MySQL 8.0/PostgreSQL 14 管道化对批处理与往返延迟的收益,以及与事务边界、错误处理的交互如何?

  • 管道化收益与边界
  • 批处理与往返延迟
  • 事务边界与错误处理

管道化在一个往返内发送多条请求,显著降低高 RTT 场景的批处理延迟,MySQL 8.0 与 PostgreSQL 14 均支持客户端流水线。工程边界在于:管道内语句按顺序执行,一条失败会中断后续语句,错误处理粒度变粗;管道跨越事务边界时,事务语义与结果集消费顺序需明确定义,否则易出现提交/回滚范围与预期不符;同时管道化会暂存多条结果,需合理控制批大小避免内存与缓冲压力。因此管道化适合大量独立、幂等的小操作,不适合强依赖前序结果的场景。

收益来自"并发往返",边界来自"顺序依赖与错误/事务语义"。判断是否适合管道化,取决于操作是否独立、幂等,以及错误与事务边界是否清晰。

#
★★

17. 预编译语句与分库分表中间件的兼容,ShardingSphere/MyCat 下服务端预编译如何被改写,绑定参数与路由字段的匹配如何处理?

预编译语句与分库分表中间件如何兼容?ShardingSphere/MyCat 下服务端预编译如何被改写,绑定参数与路由字段的匹配如何处理?

  • 分库分表中间件与预编译
  • 服务端预编译的改写
  • 绑定参数与路由字段匹配

分库分表中间件(ShardingSphere/MyCat)位于应用与数据库之间,会改写 SQL 以路由到不同分片。服务端预编译语句在中间件下需要额外处理:中间件需解析占位符与参数,依据路由字段(分片键)计算目标分片,并把参数绑定到改写后的 SQL。由于分片键常以参数形式传入,中间件必须能解析绑定参数值才能完成路由,因此客户端预编译(参数在客户端)更利于中间件解析;服务端预编译的 PREPARE 语句在中间件层可能被改写为多段目标分片的语句,需重新匹配参数与占位符顺序。中间件需维护预编译语句缓存与参数的映射,保证路由与执行一致。

核心是"中间件需要看到参数值才能路由"。预编译语句把参数与 SQL 分离,中间件必须解析参数以确定分片,并处理改写后占位符与参数的对应关系,这与直接拼接 SQL 的中间件有本质差异。

#

18. MySQL allowMultiQueries 与 PostgreSQL 多语句协议的差异?批量执行时各自的注入面与限制?

MySQL allowMultiQueries 与 PostgreSQL 多语句协议有哪些差异?批量执行时各自的注入面与限制是什么?

  • MySQL allowMultiQueries
  • PostgreSQL 多语句协议
  • 各自的注入面与限制

MySQL 的 allowMultiQueries 允许一条请求中带多条以分号分隔的语句,需在连接串显式开启,默认关闭;PostgreSQL 默认允许 simple query 协议一次发送多条语句(扩展协议则不允许多语句,需逐条 Parse/Bind/Execute)。两者在多语句开启时都会扩大注入面:攻击者可追加额外语句。MySQL 的 allowMultiQueries 依赖显式开关,关闭即减少注入面;PostgreSQL 的扩展查询协议天然不支持多语句,更利于防注入。批量执行时,MySQL 多语句一条语句失败会影响后续语句,PostgreSQL simple 协议同样有顺序执行与错误打断的特点。

差异在于"默认是否开启"与"协议层是否支持"。MySQL 默认关闭多语句、PostgreSQL simple 协议默认每请求多语句,但扩展协议禁止多语句从而更安全。据此判断各协议的注入面与批量执行时的错误限制。

#

19. 批量插入的 SQL 大小上限(max_allowed_packet)与分批策略

批量插入的 SQL 大小上限(max_allowed_packet)如何影响批量写入?分批策略应如何设计?

  • max_allowed_packet 上限
  • 批量插入与包大小
  • 分批策略

max_allowed_packet 是 MySQL 服务器接受的最大数据包大小,批量插入(尤其多值 INSERT)生成的 SQL 不能超过该上限,否则会被拒绝或截断。因此批量大小需按行大小与 max_allowed_packet 估算:单批 SQL 大小 = 行数 × 单行大小 + 语句开销,需确保小于上限。分批策略:根据估算的行数确定每批条数,留出安全余量;同时考虑事务规模、锁竞争与 redo 日志压力,避免单批过大导致长事务与锁持有时间增加;必要时动态调整批大小(如按行大小的实际值估算)。

核心是"单批 SQL 大小受包上限约束"。分批不仅看行数,还要换算字节大小,并在包上限、事务规模与日志压力之间取平衡,留安全余量。