dbaai 发表于 7 天前

Sybase ASE 内存与过程缓存治理实战:从 701 报错和 71% 命中率到命名缓存拆分

一、具体的问题

一套 ASE 15.7 业务库,白天接交易、晚上跑报表和批量结算,两拨负载挤在同一实例里。问题攒了两个月,攒成三条:


[*]每天 02:10 左右的结算批处理零星报错,错误是 701,severity 17:



Error: 701, Severity: 17, State: 12
There is not enough procedure cache to run this procedure, trigger, or SQL batch.
Retry later, or ask your SA to reconfigure Adaptive Server with more procedure cache.


重跑往往能过,运维加了自动重试,这条错误被"消化"掉,两个月没人管。


[*]白天报表那批 SQL 一上来,同一张订单表上的主键查询 P95 从 30ms 抖到 1.8 秒。
[*]监控上物理读尖峰、io busy 峰值 92%,而 CPU 一直很闲。


运维的第一反应很标准:加内存。max memory 32G 提到 64G,重启,现象一模一样:物理读没降,P95 照抖,701 每天来。

真正的发现在 sp_cacheconfig 的输出上:


Cache Name            Status    Type      Config Size   Run Size
default data cache    Active    Default   0.00 Mb       8.00 Mb


Config Size 是 0.00 Mb。这台机器的 default data cache 从上线到现在从来没有被显式配置过,一直在用系统给的默认小值。max memory 翻了一倍,数据缓存一个字节都没多——ASE 不会因为你把仓库租大了,就自动帮你把货架摆出来。

过程缓存那边,procedure cache size 配的是 102400 页(约 200MB);批量窗口里 Max_Used 顶到配置值,Reuse_cnt 累计两万六千多——不是偶尔紧张,是长期满负荷复用。

所以问题不是内存不够,是三件事叠在一起:数据缓存没配、只有单页 IO 池导致范围扫描把缓冲冲干净、过程缓存按"感觉"给的 200MB 而不是按编译峰值定的。加内存解决不了其中任何一个。

二、核心原理

ASE 的内存是分层的:操作系统共享内存由 max memory 划定上限,之下分给 default data cache、各命名缓存、过程缓存、语句缓存、worker process、锁与连接结构。所有缓存配置值之和(total logical memory)必须小于 max memory,否则实例起不来。sp_helpcache 的 Memory Available 就是"还能划出多少"。

default data cache 必须显式给绝对值。 它不会自动跟随 max memory。不写 sp_cacheconfig "default data cache", "24G",它就一直是默认小值。这是 ASE 上最常见的一种"配了等于没配"。

改尺寸要看两列。 Config Size 是配置文件里的值,Run Size 是当前运行值。改尺寸要重启才生效,两列不一致就是改了没重启。

wash size 决定脏页什么时候被赶出去。 默认按池的 20% 算(池小于 300MB),超过 300MB 则按 300MB 对应缓冲数折算。太小则脏页积压到临界才集中写盘,checkpoint 期间 IO 打满;太大则可用缓冲变少。

大 IO 池决定范围扫描的效率。 只有逻辑页大小的池(2K 或 4K),所有 IO 都单页往返,预读能力等于没有。建 16K 池,逻辑页 2K 的服务器一次 IO 就是连续 8 页。

建池在线生效,但不一定到位。 sp_poolconfig 运行时能执行,可它只能搬空闲缓冲(没被占用、没有未写盘脏页的),所以常提示 requested 与 actual 不一致,得重启后才到位。

命名缓存是隔离,不是加速。 把热表和热索引绑到独立缓存,本质是把它们从"和大表扫描共用一个缓冲池"里捞出来。绑定粒度可到表,也可到索引,在线生效。

过程缓存不只是存存储过程文本。 它还装查询计划、编译与优化工作区、部分执行期排序区;开了语句缓存,缓存语句也吃这块。所以需求跟"并发编译峰值"走,而不是跟"存了多少存储过程"走。ASE 15 之后计划普遍更大,从 12.5 迁上来通常要给到原来的 2 到 6 倍。

701 不代表物理内存不足。 它的含义是"这一次分配不到"。一条特别大的 SQL 单独编译就可能失败,同时其它小语句照跑;平时能跑的存储过程,在 schema 变更触发重编译的那次也可能突然要一大块。

一句话:加内存是给仓库扩容,配缓存是把货架分区。只扩容不分区,热页照样被冲刷。

三、实例参考(动手步骤)

步骤 0:确认现状基线

改动之前把六条命令的原样输出存下来,这是后面所有对比的基准,也是出事时唯一能自证的东西。


sp_configure "max memory"
go
sp_configure "allocate max shared mem"
go
sp_configure "procedure cache size"
go
sp_cacheconfig
go
sp_helpcache
go
sp_monitorconfig "procedure cache size"
go


判读三条:Config Size 是不是 0.00 Mb(是则从未配置);Config 与 Run 是否一致(不一致则改过未重启);Memory Available 还剩多少。案例基线:max memory 32G,default data cache 仅 8MB 级,无 16K 池、无命名缓存,procedure cache size 102400 页。

步骤 1:先量需求,再动配置

数据缓存的需求看命中率与物理读。挑报表高峰窗口采样十分钟:


sp_sysmon "00:10:00", dcache
go


重点看 Data Cache Management 段的 Cache Hits 与 Physical Reads、Buffer Wash 段的 wash 次数。命中率低于 90%、物理读随报表上涨,说明热数据被扫描冲走——这种情况才可能靠"分区"改善,而不是靠加总容量。

过程缓存的需求看编译峰值:


sp_monitorconfig "procedure cache size"
go
select * from monProcedureCacheMemoryUsage
go
select * from monProcedureCacheModuleUsage
go


Max_Used 是历史高水位,Reuse_cnt(部分版本显示 Num_Reuse)非 0 且持续增长就是确凿的短缺信号。后两张 MDA 表用来看哪个模块的编译把缓存吃掉了——案例里是报表那批拼接出来的大 SQL,单条编译要四十多兆。

步骤 2:显式配置 default data cache 与大 IO 池


-- max memory 单位是 2K 页:40GB = 40 * 1024 * 1024 / 2 = 20971520 页
sp_configure "max memory", 20971520
go
sp_configure "allocate max shared mem", 1
go

-- 显式给默认数据缓存定绝对值(不是 0.00 Mb),24GB
sp_cacheconfig "default data cache", "24G"
go

-- 划出 6GB 的 16K 池给范围扫描与排序
sp_poolconfig "default data cache", "6G", "16K"
go


执行完必须重启,再跑 sp_cacheconfig 确认 Config Size 与 Run Size 都是 24.00 Gb、池里出现 6Gb 的 16K 行。若当时提示的 actual 小于 requested,重启后重跑一次即可到位。

留内存的原则:total logical memory 别顶到 max memory 的 100%,留 10% 到 15% 给 worker process、锁结构、连接内存这类会波动的消费者,也给操作系统留余量。案例最终 max memory 40G、default data cache 24G、命名缓存 6G、过程缓存 512M。

步骤 3:把热表隔离到命名缓存


sp_cacheconfig "cache_hot", "6G"
go
sp_poolconfig "cache_hot", "1G", "16K"
go

sp_bindcache "cache_hot", "bizdb", "t_order"
go
sp_bindcache "cache_hot", "bizdb", "t_order", "ix_order_cust"
go

sp_helpcache "cache_hot"
go


容量按"热数据量 × 1.2"估,多出的 20% 给 wash 留余量。绑定在线生效,sp_helpcache 末尾会列出对象与索引——只绑表不绑索引是最常见的半成品。反过来,大表全表扫描的对象不要绑到热缓存,那等于把刚隔离好的热页重新冲一遍。

步骤 4:过程缓存按峰值定容


-- 262144 页 × 2K = 512MB
sp_configure "procedure cache size", 262144
go


这个参数是动态的,改完不用重启。增量别一步到位,按官方建议的量级走:1GB 以下每次加 128MB,1GB 到 2GB 每次加 256MB,往上类推。

调完在下一个批量窗口再跑 sp_monitorconfig,验收三条:Pct_act 峰值不再顶到 100%;Max_Used 稳定在配置值的 60% 到 75%(低了说明配多,可回收给数据缓存);Reuse_cnt 保持 0 或极小值。案例提到 512MB 后落在 68%,Reuse_cnt 归零。

步骤 5:把 701 纳入可统计范围

701 默认只发给客户端,errorlog 里看不到,所以频次永远是"听说":


sp_altermessage 701, 'with_log', true
go


如果加完缓存 701 依然出现,说明是单条巨无霸 SQL 的编译需求问题,抓现场:


sp_configure 'dump on conditions', 1
go
sp_shmdumpconfig 'add', 'error', 701, 1, '/sybase/dump', null, include_proc
go
sp_shmdumpconfig
go


取证完记得 sp_shmdumpconfig 'drop', 'error', 701 关掉,转储文件很大。

治理前后对比


指标治理前治理后
default data cache 配置值0.00 Mb(未配置)24 Gb
16K 大 IO 池无6 Gb
缓存命中率(报表时段)71.4%98.6%
物理读峰值4,200 次/秒310 次/秒
主键查询 P95(报表并发时)1.8 秒42 毫秒
701 报错频次37 次/天0(连续三周)
过程缓存 Max_Used顶格 100%68%
过程缓存 Reuse_cnt26,4800
结算批处理总时长3 小时 52 分2 小时 06 分
io busy 峰值92%41%


批处理时长缩了一半,很大一部分来自 16K 池——那张两千万行的结算表原来每次都是单页 IO 一点点啃。

四、实操检查清单


[*][ ] default data cache 的 Config Size 是显式绝对值,不是 0.00 Mb
[*][ ] 每个缓存 Config Size 与 Run Size 一致(不一致 = 改了没重启,此刻没生效)
[*][ ] Memory Available 不为负,total logical memory 占 max memory 的 85% 以内
[*][ ] 存在与负载匹配的大 IO 池(2K 页服务器至少一个 16K 池),尺寸与扫描量级相当
[*][ ] wash size 核过,大池的 wash 不会导致 checkpoint 期间 IO 集中
[*][ ] 热表、热索引绑到独立命名缓存,容量按热数据 × 1.2 估算
[*][ ] Cache Binding Information 里能看到索引级绑定,不止表级
[*][ ] statement cache size 若已开启,占用算进过程缓存预算(两者共用一块)
[*][ ] procedure cache size 按批量窗口的 Max_Used 定容,不是按 max memory 百分比拍脑袋
[*][ ] Reuse_cnt 长期为 0
[*][ ] sp_altermessage 701, 'with_log', true 已执行,errorlog 能统计频次
[*][ ] 每次改配置都有"重启后复核 Config/Run 双列并存档"的固定动作


几个容易踩的坑

以为加了 max memory 数据缓存就大了。 default data cache 不显式配置就一直是默认小值,加到 64G 那一刻命中率一个点都没动——本案例白折腾两个月就栽在这。

看到 Config 改了、池子也没报错,就当生效。 Config 24G / Run 8M 的组合同样常见;sp_poolconfig 在线执行只能搬空闲缓冲,requested 与 actual 不一致是常态。改尺寸必须重启,复查必须看 Run 列。

只绑表不绑索引。 热表进了新缓存,索引 IO 还在老池子里跟报表扫描抢缓冲,改善幅度只有预期的一半。

过程缓存一路加。 它和数据缓存抢同一个 max memory。见过过程缓存配到 4G 而数据缓存只有 2G 的实例,编译不报错了,查询全在等 IO。

只盯总量不看单条。 701 也可能是某条拼接出来的大 SQL 编译就要几十兆,加总容量只是降低发生概率,根子在那条 SQL 的写法(去嵌套、拆临时表、避免巨型 union)。

没给操作系统留余量。 max memory 贴到物理内存 100%、allocate max shared mem 又置 1,操作系统被挤到换页,ASE 反而整体变慢。

701 只在客户端日志里见到。 没开 with_log,errorlog 里查不到,就没法统计频次、没法判断治理有没有效果,只能靠"最近好像没听说"这种结论。
页: [1]
查看完整版本: Sybase ASE 内存与过程缓存治理实战:从 701 报错和 71% 命中率到命名缓存拆分