
我将为您深入解析Oracle优化器中两个相互关联且极其重要的高级特性:绑定变量窥探(Bind Peeking) 与 自适应游标共享(Adaptive Cursor Sharing, ACS)。理解它们是解决SQL性能不稳定问题的关键。
第一部分:官方定义与核心作用
一、绑定变量窥探 (Bind Peeking)
- 官方定义:在SQL语句的硬解析(Hard Parse) 过程中,优化器会查看(Peek)绑定变量(Bind Variable)的实际值,并将该值用于基数(Cardinality)估算,从而生成一个执行计划。该计划会被后续所有使用不同绑定变量值的执行所共享。
- 核心作用:解决“一刀切”问题。允许优化器在硬解析时利用具体的值来生成一个更优的计划,而不是对所有变量值都使用默认的猜测(例如,对数字字段假设均匀分布)。
二、自适应游标共享 (Adaptive Cursor Sharing, ACS)
- 官方定义:一种优化器特性,它允许一条SQL语句拥有多个不同执行计划的子游标(Child Cursor),每个子游标针对不同范围的绑定变量值进行了优化。Oracle通过监控每个执行计划的性能(rows processed)来自动决定是否应该生成一个新的计划。
- 核心作用:解决绑定变量窥探的“计划固化”缺陷。它使优化器能够适应数据分布不均匀(Data Skew)的列,为不同的绑定变量值选择不同的最优执行计划,从而提供更灵活的优化策略。
通俗比喻:
想象一个医生(优化器)给“胸口疼痛”(SQL语句)的病人开药(执行计划)。
- 没有绑定变量:每个病人都详细描述症状(Literal Value),医生为每个人定制药方。好处:精准。坏处:医生累死(库缓存中充斥大量硬解析,效率极低)。
- 只有绑定变量,没有窥探:所有病人都只说“我胸口疼”(Bind Variable)。医生开一种“万能止痛药”(通用执行计划)。坏处:对于心脏病患者(选择性高的值)和消化不良者(选择性低的值),药效都不理想。
- 绑定变量窥探:第一个病人来说“我胸口疼”,医生问他到底多疼(Peek),他说“像压了块大石头”(值=‘心脏病’)。医生开了“强力救心丸”(基于该值生成的计划)。后续所有胸口疼的病人,不管啥原因,都得到这种药。风险:下一个病人如果是“吃撑了”(值=‘消化不良’),用救心丸不仅无效,还可能有害(性能更差)。
- 自适应游标共享(ACS):医生变聪明了。他给第一个“心脏病”病人开了救心丸,并记下疗效很好(监控统计信息)。来了一个“消化不良”的病人,他一看症状不同,就开了“健胃消食片”(生成新的子游标和计划),也记下疗效。从此,他学会了对不同症状用不同的药,并且能根据新病人的描述快速决定用哪种药(自适应选择游标)。
第二部分:深入底层原理与管理机制
一、绑定变量窥探的详细过程
- 触发时机:仅在硬解析时发生。
- 过程:
- 用户提交带绑定变量的SQL:
SELECT * FROM orders WHERE status = :status_val; - 首次执行,传入
:status_val => 'SHIPPED'(已发货,假设有100万行)。 - 进行硬解析时,优化器“窥探”到
status_val的值是'SHIPPED'。 - 优化器查询
status列上直方图(Histogram)统计信息,发现'SHIPPED'值的数据量非常大。 - 基于“返回大量数据”的假设,优化器决定使用全表扫描(Full Table Scan) 计划,因为索引扫描再回表成本太高。
- 用户提交带绑定变量的SQL:
- 结果:生成的游标(包含全表扫描计划)被存入库缓存(Library Cache)。后续所有执行
SELECT * FROM orders WHERE status = :status_val的会话,无论:status_val传什么值,都共享这个全表扫描计划。
这就是“计划固化”问题:一个基于特定值生成的计划,可能对其他值是最优的。
二、自适应游标共享 (ACS) 的详细过程
ACS是绑定变量窥探的增强机制,其核心在于游标共享不再是简单的“是”或“否”,而是“在什么条件下共享”。
-
核心概念:绑定敏感游标(Bind-Sensitive Cursor)
- 当一个游标被标记为绑定敏感,意味着Oracle意识到其执行计划可能会因绑定变量值的不同而不同。
- 标记条件:该列存在直方图统计信息,且优化器认为不同的值可能导致不同的基数估算。
-
核心概念:绑定感知游标(Bind-Aware Cursor)
- 当一个绑定敏感游标被执行多次后,如果Oracle通过监控实际返回的行数,确认其性能确实因变量值不同而有显著差异,它就会将该游标升级为绑定感知。
- 一旦游标是绑定感知的,它就具备了为不同值生成不同子游标(即不同执行计划)的资格。
-
ACS的工作流程(串联举例):
- 初始状态:用户执行
SELECT * FROM sales WHERE channel = :ch。- 首次执行,传入
:ch => 'DIRECT'(直销,数据量少)。优化器窥探该值,生成一个索引范围扫描计划(游标C1)。Oracle将其标记为绑定敏感。
- 首次执行,传入
- 监控执行:后续又用
'DIRECT'执行了几次,每次返回行数都很少(例如10行)。游标C1的监控数据确认了这一点。 - 新值触发评估:现在,用户传入一个新值
:ch => 'ONLINE'(在线销售,数据量巨大)。- Oracle使用原有的游标C1(索引扫描计划)来执行。
- 但ACS机制会监控本次执行的实际行数(比如返回了10万行)。
- Oracle将监控到的巨大差异(10行 vs 10万行)与游标C1中存储的原始基数估算值进行比较。
- 生成新游标:Oracle判定原有计划对新值不是最优的,于是触发一次硬解析。这次硬解析会窥探新值
'ONLINE',并生成一个全表扫描的新计划。这个新计划被存储为一个新的子游标C2。原始游标C1被升级为绑定感知。 - 自适应选择:从此以后:
- 当传入
:ch => 'DIRECT'时,Oracle自动选择子游标C1(索引扫描)。 - 当传入
:ch => 'ONLINE'时,Oracle自动选择子游标C2(全表扫描)。
- 当传入
- 初始状态:用户执行
-
底层数据结构:
V$SQL_CS_SELECTIVITY:存储了不同绑定值的选择率范围。V$SQL_CS_STATISTICS:存储了每个子游标执行时的监控信息(实际返回行数等),这是Oracle做决策的依据。
第四部分:争用、等待事件与排查解决
ACS本身的目的是解决性能问题,但它可能引入新的开销。
1. 硬解析开销增加
- 场景:在ACS作用下,对于同一个SQL语句,可能会产生多个子游标。每次生成新的子游标都是一次硬解析。如果系统中有大量绑定变量值多且分布不均匀的SQL,ACS会导致硬解析数量上升。
- 等待事件:
library cache: mutex X/latch: library cache。硬解析需要获取相关Latch/Mutex来管理库缓存结构,高并发下的硬解析会导致对这些串行化结构的争用。 - 排查SQL:
-- 查看SQL是否有多个子游标,且是否为绑定感知 SELECT SQL_ID, CHILD_NUMBER, IS_BIND_SENSITIVE, IS_BIND_AWARE, IS_SHAREABLE, PLAN_HASH_VALUE FROM V$SQL WHERE SQL_TEXT LIKE '%YOUR_SQL_TEXT%'; -- 查看每个子游标对应的绑定变量值和执行计划 SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR('&sql_id', &child_number)); - 解决方案:
- 优化SQL:重写SQL,使其对不同的值都能使用一个稳定的计划(但这通常很难)。
- 使用SQL Profile或Baseline:使用SQL Tuning Advisor或SPM(SQL Plan Management)来固定一个通常表现良好的计划,避免ACS产生多个计划。
- 消除直方图:如果ACS带来的问题大于收益,可以考虑删除相关列的直方图统计信息。但这是一种“因噎废食”的做法,需谨慎评估。
DBMS_STATS.DELETE_COLUMN_STATS。
2. 子游标过多,库缓存浪费
- 场景:一条SQL语句可能产生数十甚至上百个子游标,消耗大量的库缓存空间(Shared Pool)。
- 影响:可能导致库缓存老化(Aging Out)加速,引发其它SQL的硬解析。
- 排查SQL:
-- 查找子游标数量最多的SQL SELECT SQL_ID, COUNT(*) CHILD_COUNT FROM V$SQL GROUP BY SQL_ID HAVING COUNT(*) > 10 ORDER BY COUNT(*) DESC; - 解决方案:同上,使用SPM或Profile来稳定执行计划。或者定期刷新共享池(
ALTER SYSTEM FLUSH SHARED_POOL,非常粗暴,不推荐用于生产环境)。
第五部分:常用监控与诊断SQL
-
确认ACS是否生效:
SELECT SQL_ID, CHILD_NUMBER, IS_BIND_SENSITIVE "BS", IS_BIND_AWARE "BA", IS_SHAREABLE "SH", PLAN_HASH_VALUE FROM V$SQL WHERE SQL_ID = '&your_sql_id' ORDER BY CHILD_NUMBER;- 如果
IS_BIND_SENSITIVE和IS_BIND_AWARE为Y,且CHILD_NUMBER有多个,说明ACS正在为该SQL工作。
- 如果
-
查看每个子游标的执行统计信息:
SELECT SQL_ID, CHILD_NUMBER, EXECUTIONS, BUFFER_GETS, ROWS_PROCESSED FROM V$SQL WHERE SQL_ID = '&your_sql_id' ORDER BY CHILD_NUMBER;- 比较不同子游标的
ROWS_PROCESSED,可以看到它们处理的数据量差异。
- 比较不同子游标的
-
查看ACS的决策依据(选择性范围):
SELECT * FROM V$SQL_CS_SELECTIVITY WHERE SQL_ID = '&your_sql_id';
总结
官方总结:绑定变量窥探和自适应游标共享是Oracle优化器处理绑定变量与数据倾斜矛盾的核心技术。绑定变量窥探在硬解析时利用具体值生成计划,但会导致计划固化。自适应游标共享通过监控执行性能,允许一条SQL拥有多个绑定感知的子游标,为不同的值选择不同的最优计划,从而解决了固化问题。其副作用是可能增加硬解析和库缓存消耗。
通俗总结:
- 绑定变量窥探:解决了“盲猜”的问题,但犯了“以第一印象评判所有人”的错误。
- 自适应游标共享:它是一个有学习能力的系统。它意识到“以第一印象评判所有人”是错误的,于是开始记录每个人的不同特点(监控统计),并为不同类型的人建立不同的档案(子游标),以后就能根据新人的特点快速选择最合适的档案来应对。
DBA的职责是当好这个系统的“监理”:欣赏其灵活性(为不均匀数据提供优化),同时警惕其“多动症”(过多的子游标和硬解析)。通过监控V$SQL和使用了ACS的SQL,并使用SPM等工具在必要时进行干预,才能让这套先进的机制真正为数据库性能服务,而不是带来新的麻烦。
欢迎关注我的公众号《IT小Chen》

被折叠的 条评论
为什么被折叠?



