马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?用户注册
×
Sybase ASE 统计信息治理实战:从计划退化到 update statistics 与 optdiag 巡检
一、具体的问题
先说一个很多 ASE 老系统都会遇到的怪现象:某条查询过去几周都是毫秒级返回,业务没改代码、表也没怎么涨数据,某天突然变成十几秒;重启服务或者重建索引之后又恢复正常,过阵子又犯。还有一个更典型的:同一张表,查询条件里换个日期参数,计划就从索引 seek 掉到全表扫描,快慢差出两个数量级。
我自己在一个订单系统上就处理过:orders 表两千多万行,按 cust_id + order_date 查询,平时 200ms 以内,某个周一之后 P99 直接飙到 8 秒,应用层超时报错堆积。排查发现表数据其实只涨了 3%,罪魁祸首是统计信息——上一轮 update statistics 是七个月前跑的,这七个月里订单状态分布严重偏移(历史订单归档导致"已完成"占比从 20% 涨到 85%),优化器拿着陈旧的直方图估算行数,把一个明明可以走复合索引的查询估成了"返回 40% 的表",直接选了全表扫描。
这类问题的麻烦在于它不报错、不告警,只有业务方感受到慢。ASE 和有些数据库不一样,不会自动重建列的直方图,统计信息全靠 DBA 自己排程维护。很多从 MySQL、SQL Server 转过来管 ASE 的人,恰恰容易漏掉这一环。
二、核心原理
ASE 的基于成本的优化器做决策,依赖三类统计信息,全部存在系统目录里:
| 统计类型 | 内容 | 作用 | | 列直方图(histogram) | 列值的分桶分布,每个桶记录上下界、频次、权重 | 估算谓词选择性 | | 密度(density) | 列(或列组合)的平均重复率 | 估算等值匹配的返回行数 | | 表/索引物理统计 | 行数、页数、每页平均行数、页链信息 | 估算 IO 成本 |
这些统计不是实时的。当你对表做大量 DML 之后,优化器手里的直方图和真实数据分布就脱节了——这就是"计划退化"的根源:查询没变,变的是优化器对数据分布的认知。
配套的治理命令有四个,职责要分清:
- update statistics 表 (列):给指定列建/更新直方图,最常用,只动列统计,不重建索引;
- update index statistics 表:更新索引全部键列(含复合索引的非前导列)的统计;
- update all statistics 表:列 + 索引键全量更新,开销最大,只适合维护窗口;
- delete statistics 表:删除列统计,用于清理无用的历史统计。
另外一个实用特性是采样:update statistics ... with sampling = N percent,大表不必全扫,采 10%~20% 通常足够优化器看清分布。更新统计信息后,ASE 会自动把引用该表的缓存计划作废、下次执行时重新编译,所以改完通常立刻见效,个别情况再配合 sp_recompile 补一刀。
三、实例参考(动手步骤)
步骤 0:确认现状基线
先别急着动手,把慢查询的执行计划和 IO 基线抓下来,否则治理完没法对比:
- -- isql 会话内
- 1> set showplan on
- 2> go
- 1> set statistics io on
- 2> go
- 1> set statistics time on
- 2> go
- 1> select count(*) from pubdb..orders
- where cust_id = 12345 and order_date >= '2026-09-01'
- 2> go
复制代码
记录 showplan 输出里的表访问方式(TABLE SCAN 还是索引名)、logical reads 和耗时,这就是你的"治理前"证据。
步骤 1:用 optdiag 还原统计信息现场
optdiag 是 ASE 自带的统计信息导出工具,把 orders 表的统计完整倒出来看:
- optdiag statistics pubdb..orders -Usa -P'密码' -SSYB_ASE \
- -o /tmp/orders_optdiag.txt
复制代码
打开输出重点看两处:
- Last update of column statistics —— 列统计的上次更新时间。如果比业务高峰期早了好几个月,基本可以定性;
- 直方图桶的分布 —— 对照实际数据分布,看关键列(如 order_status、cust_id)的桶是不是还反映现状。七个月前"已完成"订单只占 20%,直方图里最多的桶还停留在"处理中",这就是计划退化的直接证据。
步骤 2:更新统计信息
针对慢查询涉及的列精准补统计,先不动重型命令:
- -- 1) 慢查询谓词涉及的列,各建直方图,步数从默认 20 提到 100
- 1> update statistics pubdb..orders (cust_id) using steps 100
- 2> go
- 1> update statistics pubdb..orders (order_date) using steps 100
- 2> go
- -- 2) 复合索引的完整键列统计(含非前导列)
- 1> update index statistics pubdb..orders
- 2> go
- -- 3) 大表可用采样降低开销(15.x 支持)
- 1> update statistics pubdb..orders (order_status)
- with sampling = 20 percent
- 2> go
复制代码
命令执行完 ASE 会自动作废相关缓存计划;如果存储过程行为还怪,补一句 sp_recompile pubdb..orders 强制引用它的过程下次重编译。
步骤 3:验证计划恢复
重跑步骤 0 的同一查询,对比三项指标:
- 治理前:TABLE SCAN,logical reads 1,842,000,duration 8.2s
- 治理后:IX_orders_cust_date,logical reads 12,400,duration 0.19s
复制代码
showplan 里访问方式从全表扫描回到索引扫描、logical reads 掉两个数量级,才算真的修复。没恢复就回去检查直方图步数是否够细、密度是否合理。
步骤 4:固化巡检,别再靠人肉
把 update statistics 排成每周维护作业,用 crontab 挂 isql 脚本:
- # /opt/dba/scripts/update_stats.sh
- #!/bin/sh
- isql -Usa -P"密码" -SSYB_ASE -i /opt/dba/sql/update_stats.sql \
- -o /opt/dba/log/stats_$(date +%Y%m%d).log
复制代码
update_stats.sql 内容按"大表采样、小表全量、热点表高频"分级:
- -- 热点大表:采样 20%,每周
- update statistics pubdb..orders (cust_id, order_date, order_status)
- with sampling = 20 percent
- go
- -- 中小表:全量,每周
- update statistics pubdb..customers
- go
复制代码
crontab 里每周日凌晨 2 点跑,业务低峰,单表串行执行避免抢 IO。
治理前后对比
| 指标 | 治理前 | 治理后 | | 慢查询 P99 | 8.2s | 0.19s | | logical reads | 184 万页 | 1.24 万页 | | 应用超时报错 | 40+ 次/天 | 0 | | 统计信息最长滞后 | 7 个月 | ≤7 天 | | 计划退化复发 | 每月 2~3 次 | 0(3 个月内) | | 治理投入 | — | 每周 15 分钟维护作业 |
四、实操检查清单
- [ ] 给每台 ASE 实例建档:核心大表清单 + 各表上次 update statistics 时间;
- [ ] 用 optdiag 每月导出一次核心表统计,归档留底,方便事后追溯分布变化;
- [ ] 慢查询排查顺序固定:先 set showplan/statistics io 抓计划 → 再 optdiag 看统计新旧 → 最后才考虑加索引;
- [ ] 直方图步数按列基数分级:低基数列默认即可,高基数列提到 100~200;
- [ ] 大表统一用 with sampling = 10~20 percent,避免维护窗口超时;
- [ ] update statistics 之后必跑一次核心 SQL 回归,确认 showplan 符合预期;
- [ ] 存储过程异常时用 sp_recompile 强制重编译,不要盲目重启服务;
- [ ] 每周维护作业的日志落盘并巡检,作业失败要有告警;
- [ ] delete statistics 只用于清理确认无用的列统计,删前先 optdiag 备份;
- [ ] 变更大表数据(批量导入、归档、大量 delete)之后,主动补一次相关列的 update statistics,不等周作业。
几个容易踩的坑
- 一慢就重建索引。reorg rebuild 顺带更新部分物理统计,看起来"治好了",但列直方图还是旧的,过几周必复发。先 update statistics,再考虑 reorg。
- update all statistics 当日常手段。全量更新在千万级大表上能跑几十分钟还抢锁,放业务时间就是自己制造事故。日常用列级精准更新,全量只留维护窗口。
- 只更新前导列。复合索引的非前导列也有独立查询场景,update statistics 表 只覆盖索引前导列,记得配 update index statistics 表。
- 采样比例一刀切。分布极度倾斜的列(比如 95% 的行是同一个状态值)采样 10% 可能漏掉尾部分布,这类列要么全量、要么把采样提到 30% 以上。
- 改完统计不验证。统计更新成功不代表计划一定变好,个别情况优化器仍选旧路径,必须用 showplan + statistics io 回归确认,形成闭环。
统计信息治理是 ASE 上性价比最高的优化手段:不花钱、不改代码、不动结构,一条命令换回两个数量级的性能。关键是把它从"救火动作"变成"每周例行",问题自然消失在发生之前。 |