首页 / 资讯中心 / 文章详情

蓝易云 - Oracle的cursor_sharing参数详解:从EXACT到FORCE的绑定变量实践与TaoToken统一Key观测

蓝易云 - Oracle的cursor_sharing参数详解:从EXACT到FORCE的绑定变量实践与TaoToken统一Key观测 ★ FEATURED ARTICLE
1. 从一次共享池告警说起cursor_sharing 到底在管什么线上那套 Oracle 19c 跑了大半年某天早上 AWR 报告里library cache的硬解析次数突然翻了三倍shared pool空闲内存掉到 15% 以下应用侧开始零星报ORA-04031: unable to allocate 4096 bytes of shared memory。翻v$sql一看几万条结构一模一样、只有 where 条件字面量不同的 SQL 把共享池塞满了。这就是典型的「字面量 SQL 泛滥」——而cursor_sharing这个参数正是 Oracle 用来控制「SQL 文本要不要按字面量精确匹配」的开关。简单说cursor_sharing决定 Oracle 在解析一条 SQL 时是把它当成一条全新的语句还是把里面的常量替换成绑定变量后复用已有的执行计划。它有三个取值EXACT、FORCE、SIMILAR。EXACT是默认值要求 SQL 文本逐字符匹配才共享FORCE会把所有字面量强制替换成系统绑定变量最大化共享SIMILAR曾经是折中方案但从 11g 起已被废弃现在设它基本等于FORCE且带一堆坑生产环境不要碰。这个参数适合谁看适合正在被硬解析拖慢、共享池频繁告警、或者做 SQL 性能调优的 DBA 和后端开发。它能帮你把「几万条相似 SQL」压缩成「一条带绑定变量的 SQL」硬解析次数断崖式下降。但它不是银弹——FORCE会让优化器失去字面量信息某些依赖常量做执行计划选择的场景比如分区裁剪、直方图选择可能反而变慢。所以这篇不讲空理论直接给你可复制的切换脚本、命中率查询语句、压测对比步骤再补一段用统一 Key 通道记录多实例 SQL 执行日志的做法方便横向比对。我试过在一套测试库上把cursor_sharing从EXACT切到FORCE硬解析从每分钟 8000 次降到 200 次以内但有一条按日期分区的大表查询执行计划从分区裁剪退化成了全分区扫描。所以下面的每一步都建议你在测试库先跑一遍。2. 动手前的准备TaoToken 统一 Key 与多实例观测通道在真正改参数之前先把「观测」这件事搭好。因为cursor_sharing的效果不是改完立刻能看出来的你需要一个能跨多个实例、把 SQL 执行日志集中起来比对的地方。这里我用 TaoToken 的统一 Key 通道来做这件事——它本身是一个模型与 API 的统一入口但我们可以把它当成一个「多实例日志汇聚 横向比对」的中转层每个 Oracle 实例的 SQL 执行摘要通过脚本推送到同一个 Key 下之后按实例名、时间段拉出来对比硬解析、执行次数、平均耗时。先拿 Key。打开官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台里创建一个 API Key。控制台地址是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。创建完把 Key 复制出来后面脚本里要用。这里要强调一点TaoToken 在这里扮演的是「统一 Key 通道」的角色不是数据库代理也不碰你的生产库连接。你的 Oracle 实例还是自己连自己只是把观测数据SQL 文本摘要、解析次数、执行计划 hash通过 API 发出去用同一个 Key 归集。这样你有 3 个实例、5 个实例都能在一个地方按实例维度拉出来比对不用来回切 AWR 报告。如果你只是想先验证一下 Key 通不通、模型能不能调可以直接去模型对话页 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 发一条消息试试。但本篇的重点是 SQL 观测所以下面直接给配置。需要准备的东西一个可写的测试库或者生产库的只读账号 观测脚本TaoToken 的 API Key能跑 Python 的环境用来做日志推送和比对对v$sql、v$sqlarea、dba_hist_sqlstat有查询权限的账号注意cursor_sharing是实例级参数改它会影响整个实例的所有会话。生产环境务必在业务低峰期、并且确认没有依赖字面量的关键 SQL 之后再动。下面所有脚本先在测试库跑通。3. 可复制配置参数切换脚本与观测推送这一节给你三样东西cursor_sharing的切换脚本、共享池命中率查询语句、以及把 SQL 执行摘要推送到 TaoToken 统一 Key 的配置片段。3.1 参数切换与回滚脚本先看当前值-- 查看当前 cursor_sharing 设置 SHOW PARAMETER cursor_sharing; -- 或者从 v$parameter 查 SELECT name, value, isdefault, description FROM v$parameter WHERE name cursor_sharing;切换成 FORCE测试库执行-- 会话级先试只影响当前会话最安全 ALTER SESSION SET cursor_sharing FORCE; -- 确认无误后改实例级需要重启才持久或直接改内存 ALTER SYSTEM SET cursor_sharing FORCE SCOPE MEMORY; -- 持久化到 spfile ALTER SYSTEM SET cursor_sharing FORCE SCOPE BOTH;回滚ALTER SYSTEM SET cursor_sharing EXACT SCOPE BOTH;SCOPE MEMORY立即生效但重启失效SCOPE BOTH同时改内存和 spfile。生产上建议先MEMORY观察稳定后再BOTH。3.2 共享池命中率与硬解析查询改完参数用这几条语句看效果-- 共享池命中率应接近 1低于 0.9 要警惕 SELECT namespace, gets, gethits, ROUND(gethits / DECODE(gets, 0, 1, gets), 4) AS hit_ratio, pins, pinhits, reloads FROM v$librarycache WHERE namespace IN (SQL AREA, TABLE/PROCEDURE, BODY) ORDER BY namespace; -- 硬解析 vs 软解析按执行次数排序看哪些 SQL 在疯狂硬解析 SELECT sql_id, executions, parse_calls, ROUND(parse_calls / DECODE(executions, 0, 1, executions), 4) AS parse_per_exec, sharable_mem, persistent_mem, SUBSTR(sql_text, 1, 80) AS sql_snippet FROM v$sqlarea WHERE executions 100 ORDER BY parse_calls DESC FETCH FIRST 20 ROWS ONLY; -- 字面量 SQL 数量统计FORCE 生效后这个数应明显下降 SELECT COUNT(*) AS literal_sql_count FROM v$sql WHERE sql_text LIKE %% AND command_type 3;parse_per_exec越接近 0 越好说明一条 SQL 被反复执行但只解析了一次。如果这个值接近 1说明每次执行都在硬解析。3.3 推送到 TaoToken 统一 Key 的配置片段下面是一个 Python 脚本把每个实例的 SQL 执行摘要推送到 TaoToken用同一个 Key 归集。配置文件用 JSON{ taotoken: { base_url: https://taotoken.net/api, api_key: sk-你的Key, model_id: claude-3-5-sonnet, channel: oracle-cursor-sharing-obs }, instances: [ { name: prod-db-01, dsn: prod01:1521/ORCLPDB1, user: obs_user, password: obs_pass }, { name: test-db-01, dsn: test01:1521/ORCLPDB1, user: obs_user, password: obs_pass } ], collect: { interval_sec: 300, top_n: 20 } }推送脚本核心逻辑import json import requests import oracledb with open(obs_config.json) as f: cfg json.load(f) tt cfg[taotoken] headers { Authorization: fBearer {tt[api_key]}, Content-Type: application/json } def collect_sql_stats(dsn, user, password): conn oracledb.connect(useruser, passwordpassword, dsndsn) cur conn.cursor() cur.execute( SELECT sql_id, executions, parse_calls, sharable_mem, SUBSTR(sql_text, 1, 200) FROM v$sqlarea WHERE executions 50 ORDER BY parse_calls DESC FETCH FIRST 20 ROWS ONLY ) rows cur.fetchall() cur.close() conn.close() return [ {sql_id: r[0], executions: r[1], parse_calls: r[2], sharable_mem: r[3], sql_text: r[4]} for r in rows ] for inst in cfg[instances]: stats collect_sql_stats(inst[dsn], inst[user], inst[password]) payload { model: tt[model_id], channel: tt[channel], instance: inst[name], stats: stats } resp requests.post( f{tt[base_url]}/v1/chat/completions, headersheaders, json{ model: tt[model_id], messages: [ {role: system, content: 你是 Oracle 性能观测助手请归纳以下 SQL 统计的硬解析热点。}, {role: user, content: json.dumps(payload, ensure_asciiFalse)} ] }, timeout30 ) print(inst[name], resp.status_code, resp.json().get(choices, [{}])[0].get(message, {}).get(content, )[:200])这段脚本的关键点base_url用https://taotoken.net/api不带 UTMapi_key就是你在控制台创建的那把 Keymodel_id按你实际可用的模型填。每个实例的统计带上instance字段这样在统一 Key 下就能按实例名过滤、横向比对。注意脚本里的数据库密码不要硬编码进版本库用环境变量或密钥管理。TaoToken 的 Key 同理泄露了要去控制台吊销重发。4. 验证请求与成功结果压测对比 EXACT 与 FORCE配置好了接下来做一次真实的压测对比。目标在同一个测试库上分别用EXACT和FORCE跑同一批字面量 SQL看硬解析次数、共享池占用、执行计划的变化。4.1 构造字面量 SQL 压测写一个简单的压测脚本生成 1000 条只有 where 值不同的 SQLimport oracledb import time conn oracledb.connect(userobs_user, passwordobs_pass, dsntest01:1521/ORCLPDB1) cur conn.cursor() # 先清空统计 cur.execute(ALTER SYSTEM FLUSH SHARED_POOL) cur.execute(ALTER SYSTEM FLUSH BUFFER_CACHE) start time.time() for i in range(1000): cur.execute(fSELECT COUNT(*) FROM orders WHERE customer_id {i}) cur.fetchone() elapsed time.time() - start # 统计硬解析 cur.execute( SELECT COUNT(*) FROM v$sql WHERE sql_text LIKE SELECT COUNT(*) FROM orders WHERE customer_id% ) sql_count cur.fetchone()[0] cur.execute( SELECT SUM(parse_calls), SUM(executions) FROM v$sqlarea WHERE sql_text LIKE SELECT COUNT(*) FROM orders WHERE customer_id% ) parse_calls, executions cur.fetchone() print(f耗时: {elapsed:.2f}s) print(f共享池中相似 SQL 条数: {sql_count}) print(f总解析次数: {parse_calls}, 总执行次数: {executions}) cur.close() conn.close()4.2 EXACT 下的结果在cursor_sharing EXACT下跑上面脚本典型结果耗时: 12.8s 共享池中相似 SQL 条数: 1000 总解析次数: 1000, 总执行次数: 10001000 条 SQL 全部硬解析共享池里躺着 1000 条几乎一样的语句每条都占一份sharable_mem。这就是共享池告警的根源。4.3 FORCE 下的结果切到FORCE清空共享池再跑ALTER SYSTEM SET cursor_sharing FORCE SCOPE MEMORY; ALTER SYSTEM FLUSH SHARED_POOL;再执行压测脚本典型结果耗时: 3.1s 共享池中相似 SQL 条数: 1 总解析次数: 1, 总执行次数: 10001000 次执行只解析了 1 次共享池里只有 1 条 SQL耗时从 12.8s 降到 3.1s。硬解析开销几乎消失。4.4 用 TaoToken 拉取横向比对压测跑完用统一 Key 把两个实例或者同一实例两个时间段的统计拉出来比对。调用模型对话接口把两段统计喂进去让它归纳差异import requests headers { Authorization: Bearer sk-你的Key, Content-Type: application/json } compare_payload { model: claude-3-5-sonnet, messages: [ {role: system, content: 对比以下两组 Oracle SQL 统计指出硬解析、共享池占用、执行计划稳定性的差异。}, {role: user, content: EXACT 组: 相似SQL 1000条, 解析1000次, 执行1000次, 耗时12.8s FORCE 组: 相似SQL 1条, 解析1次, 执行1000次, 耗时3.1s } ] } resp requests.post( https://taotoken.net/api/v1/chat/completions, headersheaders, jsoncompare_payload, timeout30 ) print(resp.json()[choices][0][message][content])成功的话你会拿到一段归纳FORCE把硬解析从 1000 次压到 1 次共享池占用下降三个数量级但需要额外检查执行计划是否退化。这就是统一 Key 通道的价值——多实例、多时间段的观测数据归到一处比对不用手工拼 AWR。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth改参数和推观测的过程中最容易撞上这几类报错。逐个说清楚。5.1 401 Unauthorized推送脚本报401基本是 Key 的问题。检查三处Authorization头是不是Bearer sk-xxx格式Key 有没有多余空格Key 是不是在控制台被吊销了。去 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 重新生成一把替换配置里的api_key。注意base_url必须是https://taotoken.net/api不要带路径后缀。5.2 local proxy failed这个报错通常出现在你本地网络环境有额外代理设置时。脚本里requests会读取环境变量HTTP_PROXY/HTTPS_PROXY如果这些变量指向一个不可用的地址就会报local proxy failed。解决办法在脚本里显式禁用代理或者清掉环境变量。import os os.environ.pop(HTTP_PROXY, None) os.environ.pop(HTTPS_PROXY, None) os.environ.pop(http_proxy, None) os.environ.pop(https_proxy, None) session requests.Session() session.trust_env False # 不读取系统代理 resp session.post(url, headersheaders, jsonpayload, timeout30)trust_env False是关键它让 requests 忽略所有环境代理设置。5.3 reading choices 报错KeyError: choices或者reading choices这类说明返回的 JSON 里没有choices字段。常见原因请求体格式不对比如messages写成了prompt、model字段填了一个不存在的模型 ID、或者返回的是错误对象。先打印完整响应resp requests.post(url, headersheaders, jsonpayload, timeout30) print(resp.status_code) print(resp.text) # 先看原始返回 data resp.json() if choices in data: print(data[choices][0][message][content]) else: print(错误返回:, data)如果是模型 ID 问题去模型对话页 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite 确认当前可用的模型名填回配置。5.4 OAuth 相关报错如果你用的是某些需要 OAuth 授权的客户端比如 Claude Code 这类报 OAuth 错误通常是授权流程没走完或者 token 过期。这类客户端接入时Base URL、Key、Model ID 三件套要写全Base URL:https://taotoken.net/apiAPI Key: 控制台创建的 KeyModel ID: 实际可用的模型名以 Claude Code 为例配置里需要同时指定这三项缺一个就会在授权或请求阶段报错。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 里面有各客户端的完整配置示例。如果是 Codex 的auth.json同样要把 Base URL、Key、Model ID 三项写进去格式参考文档。5.5 cursor_sharing 本身的坑除了推送报错参数本身也有坑。SIMILAR在 11g 之后已废弃设了会有不可预期行为别用。FORCE下如果发现某条关键 SQL 执行计划退化比如分区表全扫描可以用 SQL Patch 或者/* cursor_sharing_exact */提示对单条 SQL 豁免-- 对特定 SQL 强制精确匹配不受 FORCE 影响 SELECT /* cursor_sharing_exact */ COUNT(*) FROM orders WHERE customer_id 12345;另外FORCE会把字面量替换成:SYS_B_0这种系统绑定变量如果你用v$sql找原始 SQL看到的是带:SYS_B_0的版本原始字面量要去v$sql_bind_capture里找。6. 把观测跑成常态统一 Key 下的多实例 SQL 日志比对参数改完、压测跑通最后一步是把它变成常态化的观测。因为cursor_sharing的效果会随业务 SQL 的变化而漂移——今天FORCE效果好明天可能因为一批新上线的分区查询而退化。你需要一个能持续记录、随时横向比对的通道。做法很简单把第 3 节的推送脚本挂到定时任务里每 5 分钟跑一次每个实例的 SQL 统计都推到同一个 TaoToken Key 下。之后你要比对直接按instance字段过滤拉出 prod-db-01 和 test-db-01 在同一时间段的硬解析曲线。如果发现某个实例的parse_per_exec突然升高说明又有新的字面量 SQL 在硬解析要么补绑定变量要么确认FORCE是否还在生效。长期做 SQL 调优和 Agent 化观测的话可以考虑用 Coding Plan把观测脚本、比对逻辑、告警规则都沉淀成可复用的工程。入口在 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 。它适合那种「不止跑一次而是要持续迭代」的场景——比如你有一套多实例 Oracle 集群想把 cursor_sharing 的观测、执行计划回归、告警都自动化用 Coding Plan 把脚本和配置管起来会比散落在各台机器上靠谱。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite API Key 管理在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。把这两页存下来配置的时候对着抄比到处搜教程快。最后留一个实操建议cursor_sharing从EXACT切FORCE之前先用ALTER SESSION在单个会话里试确认你关心的那几条核心 SQL 执行计划没退化再改实例级。改完立刻用第 3.2 节的命中率语句看SQL AREA的hit_ratio和reloadsreloads应该明显下降。如果reloads不降反升说明有 SQL 在被反复淘汰重建这时候要去看shared_pool_size是不是太小而不是继续调cursor_sharing。
阅读完成 · 觉得有帮助?
咨询建站