在DB2的嵌入式SQL与CLI、JDBC等接口体系中,PREPARE与EXECUTE是一对配合使用的语句,用来将SQL的编译阶段与执行阶段分离。PREPARE负责把一条SQL模板编译成应用计划并赋予其名称,EXECUTE则基于该计划传入具体参数完成运行。这种机制特别适合那些结构固定、仅条件值变化的场景,例如按不同用户编号查询订单、循环写入日志等。

PREPARE的底层原理与编译开销剖析
当DB2收到一条SQL文本时,会经历词法分析、语法检查、语义校验、优化器生成访问计划几个步骤。如果是动态拼串直接执行,每一次提交都要重复这套流程,称为硬解析。PREPARE的作用是在第一次把SQL模板交给数据库时完成上述全部工作,数据库把生成的包或段保存在应用内存或数据库目录里,返回一个语句句柄或名称。之后无论执行多少次,都不再做硬解析,只做变量绑定和计划复用。
从系统视图看,PREPARE产生的计划质量直接决定后续EXECUTE的效率。如果模板里含有主变量(parameter marker),优化器会按典型分布预估,而不是按某一次的具体值。这意味着对于倾斜数据,可能不如静态绑定值精准,但换来的是稳定且极低的单位执行成本。在并发较高、语句重复率大的系统里,减少硬解析能明显降低CPU和系统目录锁竞争。
需要注意的是,PREPARE本身也有代价。若一条语句只执行一次,先PREPARE再EXECUTE反而多了一次往返。因此是否使用预编译,要看执行频次与语句复杂度。简单低频语句可直接EXECUTE IMMEDIATE,复杂或循环语句才值得拆分。
EXECUTE参数绑定与不同接口写法对比
在嵌入式SQL中,PREPARE把语句存为代号,EXECUTE通过USING子句绑定宿主变量。下面是一段嵌入式SQL示例,展示先编译后执行并循环传参的过程:
EXEC SQL PREPARE stmt1 FROM :sql_text; EXEC SQL DECLARE cur1 CURSOR FOR stmt1; EXEC SQL OPEN cur1 USING :emp_id; EXEC SQL FETCH cur1 INTO :name, :dept; EXEC SQL CLOSE cur1;
在JDBC里,对应概念是PreparedStatement。虽然名字不带PREPARE,但底层同样先发PREPARE包再EXECUTE。与嵌入式不同,JDBC用问号做占位符,由setXxx方法绑定,避免字符串拼接,也顺带解决了特殊字符与注入问题。CLI层则显式调用SQLPrepare和SQLExecute,适合C/C++程序精确控制。
三种方式本质一致,但错误用法很常见。有人把参数值直接拼进模板再PREPARE,等于每次都换模板,数据库无法复用计划,PREPARE退化为普通执行。正确做法是用占位符,把变化的值通过绑定传入,保持SQL文本恒定。
事务边界与计划缓存的生命周期管理
PREPARE出的语句计划并不是永久有效。在嵌入式程序中,它通常存活到程序结束或显式释放;在CLI/JDBC连接池环境里,连接归还后计划可能随连接清空。如果应用频繁建连断连,预编译优势会被削弱,因为每次新连接都要重新PREPARE。使用长连接或连接池并开启语句缓存,才能让EXECUTE真正吃到复用红利。
事务提交方式也影响预编译收益。自动提交开启时,每条EXECUTE可能伴随日志刷盘,此时PREPARE省下的解析时间占比相对变小;批处理关闭自动提交、攒批EXECUTE后再COMMIT,能把解析节省放大为整体吞吐提升。另外,当表结构发生ALTER或统计信息大幅更新,旧计划可能失效,DB2会在必要时自动重编译,应用一般无需干预,但核心路径建议监控SQL0666等告警。
综合来看,把PREPARE放在初始化或首次进入热点逻辑时执行,EXECUTE放在循环或请求处理中,配合稳定连接与合理事务边界,是发挥DB2预编译价值的关键。盲目预编译或错误拼参,都会让机制形同虚设。