|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?用户注册
×
一、具体的问题
一套 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_cnt | 26,480 | 0 | | 结算批处理总时长 | 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 里查不到,就没法统计频次、没法判断治理有没有效果,只能靠"最近好像没听说"这种结论。 |
|