执行计划缓存失效是存储过程变慢的主因,表现为首次快、后续慢或耗时波动,根源于统计信息过期、结构变更未刷新、参数嗅探失准或连接级缓存隔离;应通过查询缓存状态验证,优先精准驱逐或启用共享缓存,而非盲目清空。

执行计划缓存失效导致存储过程变慢
存储过程第一次执行快、后续变慢,或相同参数下耗时波动剧烈,大概率是执行计划缓存异常——不是SQL写得差,而是数据库没复用已编译的计划。常见诱因包括:统计信息过期、表结构变更后未触发计划刷新、参数嗅探失准(尤其在SQL Server或Oracle中)、或连接级缓存隔离(如金仓KES默认每连接独占一份缓存)。
验证方式很简单:SELECT * FROM sys.m_sql_plan_cache WHERE state = 'invalid'(SAP HANA),或SELECT * FROM pg_stat_statements WHERE query LIKE '%your_procedure_name%'(PostgreSQL/金仓),重点看calls和total_time是否呈非线性增长,同时plans字段是否频繁重编译。
强制复用执行计划的实操手段
绕过优化器误判、锁定稳定计划,比反复调优SQL更直接有效:
- 在支持语句级Hint的数据库(如Oracle、OceanBase)中,对关键查询显式指定
/*+ USE_PLAN_HASH(123456789) */,前提是已通过DBA_HIST_SQL_PLAN或v$plan_cache确认该hash对应高效路径 - MySQL 8.0+ 可启用
query_cache_type=0并配合PREPARE/EXECUTE复用预编译句柄,避免每次CALL都走解析流程 - 金仓KES V9需主动开启共享缓存:
ALTER SYSTEM SET shared_preload_libraries = 'plan_cache_sharing';再重启,否则200个连接跑同一存储过程会生成200份缓存,内存爆炸 - SQL Server中用
OPTION (RECOMPILE)反而适得其反——它禁用缓存;真正要的是OPTION (OPTIMIZE FOR (@param = 'known_value'))来规避参数嗅探陷阱
缓存污染与清理的边界控制
盲目清空缓存(如DBCC FREEPROCCACHE或ALTER SYSTEM FLUSH PLAN CACHE)可能引发雪崩——所有存储过程首次执行都要硬解析。更稳妥的做法是精准驱逐:
只清理特定对象:DBCC FREEPROCCACHE (plan_handle)(SQL Server),或sys.purge_plan_cache('procedure_name')(SAP HANA);PostgreSQL/金仓则优先用pg_stat_statements_reset()重置统计,而非暴力清缓存。
注意:金仓KES中shared_buffers调得再大,若未启用共享执行计划缓存,每个连接仍会把计划拷贝进私有内存区——这正是压测时RSS飙升到48GB的根因,不是SQL本身内存泄漏。
参数化不足引发的缓存分裂
存储过程中拼接字符串构造动态SQL(如CONCAT('SELECT * FROM t WHERE id = ', p_id)),会导致每种p_id值生成独立执行计划,缓存碎片化。必须改用参数化查询:
MySQL:SET @sql = 'SELECT * FROM t WHERE id = ?'; PREPARE stmt FROM @sql; EXECUTE stmt USING p_id;
Oracle:EXECUTE IMMEDIATE 'SELECT * FROM t WHERE id = :1' USING p_id;
关键点在于:占位符?或:1必须原样出现在SQL文本中,不能被变量替换掉——否则优化器无法识别为同一模板。
缓存异常的本质不是“计划没生成”,而是“生成了太多不该生成的计划”。定位时先看缓存状态,再看参数传递方式,最后才动SQL逻辑。最容易被忽略的是数据库默认的缓存隔离策略——它不声不响吃掉你80%的内存,却从不在错误日志里报错。










