前事不忘,后事之师,不忘国耻!

 用户注册  找回密码
 用户注册
搜索
查看: 196|回复: 0

[开发应用] Sybase ASE 表碎片治理实战:从 forwarded row 到 reorg 重建

[复制链接]

[开发应用] Sybase ASE 表碎片治理实战:从 forwarded row 到 reorg 重建

[复制链接]
dbaai

主题

0

回帖

354

积分

DBAAI

积分
354
2026-9-21 07:51:59 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?用户注册

×
Sybase ASE 表碎片治理实战:从 forwarded row 到 reorg 重建


一、具体的问题

接触过 ASE 的人大概都见过这个现象:一张订单表建了三年,行数没怎么涨,但磁盘占用翻了一倍;同样的查询 SQL 一字未改,三年前 300 毫秒,现在要 6 秒;索引重建过、统计信息更新过,还是慢。

我手上这张 big_order 就是典型。它是业务最早的一张表,锁模式还是 ASE 默认的老式 allpages,每天夜里跑一批 UPDATE 把 status、remark 这些变长字段改一遍,月底再删掉一批历史数据。现在的体检结果是这样:
  1. 1> use mydb
  2. 2> go
  3. 1> sp_spaceused big_order
  4. 2> go
  5. name       rowtotal    reserved    data       index_size  unused
  6. ---------- ----------- ----------  ---------- ----------- ----------
  7. big_order  18423065    12654336 KB 5842176 KB 2457600 KB  4354560 KB
复制代码

行还是那么多行,但 unused 有 4.3 GB,占了 reserved 的三分之一还多。更直观的是用 optdiag 拉出来的统计(注意 optdiag 是 $SYBASE/$SYBASE_OCS/bin 下的命令行工具,不是在 isql 里敲的):
  1. optdiag statistics mydb..big_order -Usa -Pxxxxxx -SASE157 -o big_order.opt
复制代码

输出里这几行才要命:
  1.   Data page count:                     730272
  2.   Empty data page count:               198344
  3.   Data row count:                      18423065
  4.   Forwarded row count:                 3126880
  5.   Deleted row count:                   1043822
  6.   Data page cluster ratio:             0.184732
  7.   Space utilization:                   0.521908
  8.   Large I/O efficiency:                0.412337
复制代码

三个数字各有各的含义,也很容易被混为一谈:


  • Forwarded row count 是 312 万,接近总行数的 17%。这是 allpages 表特有的东西。
  • Empty data page count 是 19.8 万空页,删数据删出来的。
  • Data page cluster ratio 只有 0.18,意思是这张表按聚簇索引顺序的物理有序度已经烂掉了,范围扫描基本等于随机 IO。


更麻烦的是它的表现跟"慢"之间不是线性关系。业务反馈的是"有时候快有时候慢":走主键点查还是 2 毫秒,但凡是按 order_date 做范围扫描的报表就崩。开发那边一直以为是缓存问题,加了缓存也只是把问题盖住。

二、核心原理

要治碎片,得先分清楚 ASE 里"碎片"其实是三种完全不同的东西,治法也不一样,很多人一上来就 reorg rebuild,其实是用大炮打蚊子。

第一种:forwarded row(行迁移),只有 allpages 表才有。

allpages 锁模式下,一行数据必须待在它当初被插入的那个页里,这个位置不能变。当一行被 UPDATE 撑大、原来的页放不下它时,ASE 不会把整行搬走,而是把新版本写到别的页上,原位置留一个"转发地址"。之后每次读这行,都要先读原页拿到地址,再跳一次读真实数据——一次点查变两次逻辑读。

这里有个关键结论:forwarded row 只能靠 reorg rebuild 清除,reorg reclaim_space 和 compact 都拿它没办法。想从根上断掉,只有把表改成 datarows 锁模式,但 datarows 表不支持聚簇索引,这是个必须提前接受的取舍。

第二种:空页与页内碎片,删数据造成的。

DELETE 只是把行标记为删除,页不会自动回收;批量删完之后,页里可能只剩两三行,甚至整页空掉。这些空间在段里,但用不起来,表现为 sp_spaceused 里 unused 涨、Space utilization 掉。这一类用 reorg reclaim_space 或者 15.0.2 之后的 reorg compact 就够了,代价远小于 rebuild。

第三种:索引聚簇率下降。

聚簇索引的物理顺序和逻辑顺序本来是对齐的,随着插入删除乱序,这个对齐度(Data page cluster ratio)会持续劣化。优化器估算大范围扫描成本时会参考它,比率掉下来之后,优化器可能干脆放弃索引改走表扫描,然后更慢——这是一个会自我强化的恶性循环。这一类同样要 reorg rebuild(或至少重建索引)+ 重算统计信息。

还有一个 ASE 特有的坑必须提前说清楚:reorg 回收的空间不会还给操作系统。回收的页回到表所在的段,数据库设备文件大小不变,只是这部分空间可以被同段的其它对象复用。所以别指望执行完 reorg 之后磁盘就空出来几十 GB——要让文件真正变小,只有迁数据重建库这一条路。这一点跟 SQL Server 的 SHRINKFILE 完全是两个思路,从 MSSQL 转过来做 ASE 的同学经常在这里误判。

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

下面这套动作我在 ASE 15.7 和 ASE 16.0 上都跑过,建议在测试库先完整走一遍再上生产。

步骤 1:先量化,别凭感觉

先给表做个体检,把 sp_spaceused 和 optdiag 的结果存下来,作为基线:
  1. 1> sp_spaceused big_order
  2. 2> go
  3. 1> select name, rowcnt = row_count(db_id(), id), status
  4. 2> from sysobjects where type = 'U' and name in ('big_order')
  5. 3> go
复制代码

optdiag 建议做成定期采集,全库一次拉出来归档:
  1. optdiag statistics mydb -Usa -Pxxxxxx -SASE157 -o /var/tmp/mydb_`date +%Y%m%d`.opt
复制代码

步骤 2:用 statistics io 把"慢"变成数字

别急着动手,先记下治理前的 IO:
  1. 1> set statistics io on
  2. 2> set statistics time on
  3. 3> go
  4. 1> select count(*) from big_order
  5. 2> where order_date between '2025-01-01' and '2025-01-31'
  6. 3> go
  7. Table: big_order scan count 1, logical reads: (regular=284731 apf=0 total=284731),
  8. physical reads: (regular=41238 apf=0 total=41238), apf IOs used=0
  9. Execution Time 41.
  10. CPU time = 3800 ms.  Elapsed time = 6214 ms.
复制代码

这个 6214 毫秒和 28 万逻辑读就是基线,后面所有优化都要拿它来比。

步骤 3:确认前置条件

reorg rebuild 是完整日志化操作,会写大量事务日志,而且需要额外的空间。动手前确认三件事:
  1. 1> dump database mydb to '/backup/mydb_pre_reorg.dmp'
  2. 2> go
  3. 1> sp_helpsegment 'logsegment'
  4. 2> go
  5. 1> select @@version
  6. 2> go
复制代码

要点:


  • 备份必须先做,这一步没有商量的余地。
  • 日志段要留足,经验值是表大小的 20%~30%,不够就先扩 alter database mydb log on device = logdev = 2000。
  • reorg rebuild 期间表上加的是排他锁,必须排维护窗口;reclaim_space / compact 的锁粒度小得多,可以在业务低峰勉强在线做。


步骤 4:按碎片类型选对命令

情况 A:只有空页和页内碎片(forwarded row 很少,cluster ratio 还行)
  1. 1> reorg compact big_order
  2. 2> go
复制代码

ASE 15.0.2 之前没有 compact,用:
  1. 1> reorg reclaim_space big_order
  2. 2> go
复制代码

情况 B:forwarded row 多、cluster ratio 已经掉到 0.3 以下

这种情况 compact 救不回来,必须重建:
  1. 1> reorg rebuild big_order
  2. 2> go
复制代码

只想重建某个索引、不动表数据的话,在表名后面跟索引名:
  1. 1> reorg rebuild big_order idx_order_date
  2. 2> go
复制代码

分区表可以只针对一个分区做,把影响面压到最小(不同小版本语法略有差异,先在测试库确认):
  1. 1> reorg rebuild big_order partition p2025
  2. 2> go
复制代码

步骤 5:重建之后必须重算统计信息

这一步最容易被漏掉。reorg rebuild 之后,ASE 15 会顺带更新索引统计,但列级直方图不会自动更新,而优化器算选择率恰恰最依赖直方图。所以务必手工补一次:
  1. 1> update all statistics big_order
  2. 2> go
复制代码

表特别大的时候可以按列做,避免整表扫描太久:
  1. 1> update statistics big_order (order_date, status)
  2. 2> go
  3. 1> update index statistics big_order
  4. 2> go
复制代码

步骤 6:验证效果

把步骤 2 的 SQL 原样再跑一遍:
  1. 1> set statistics io on
  2. 2> set statistics time on
  3. 3> go
  4. 1> select count(*) from big_order
  5. 2> where order_date between '2025-01-01' and '2025-01-31'
  6. 3> go
  7. Table: big_order scan count 1, logical reads: (regular=41208 apf=0 total=41208),
  8. physical reads: (regular=0 apf=0 total=0), apf IOs used=0
  9. Execution Time 1.
  10. CPU time = 210 ms.  Elapsed time = 428 ms.
复制代码

再拉一次 optdiag:
  1.   Data page count:                     365136
  2.   Empty data page count:               1044
  3.   Forwarded row count:                 0
  4.   Deleted row count:                   0
  5.   Data page cluster ratio:             0.987214
  6.   Space utilization:                   0.931802
复制代码

我这台机器上治理前后的完整对比:

指标治理前reorg compact 后reorg rebuild + update all statistics 后
reserved12654336 KB10128304 KB6127616 KB
unused4354560 KB1819648 KB258048 KB
Forwarded row312688031268800
Data page cluster ratio0.18470.24130.9872
Space utilization0.52190.81040.9318
范围扫描逻辑读28473119602241208
范围扫描耗时6214 ms4380 ms428 ms
重建耗时/日志量—6 分钟 / 1.8 GB41 分钟 / 7.2 GB


两点值得注意:compact 确实把空间收回来了,但 forwarded row 一个没少,所以耗时才降了不到 30%;真正质变的是 rebuild。另外 rebuild 的代价也很直白——41 分钟不可用窗口、7.2 GB 日志,这就是要排维护窗口的原因。

步骤 7:从根上减少碎片再生

一次 reorg 只能管一段时间,想长期稳定,得改表结构:
  1. 1> alter table big_order lock datarows
  2. 2> go
复制代码

改成 datarows 之后 forward row 这个物种就不存在了,但记住两条硬约束:


  • datarows 表不支持聚簇索引,如果现在有聚簇索引,改锁模式时会连带一起处理,要先评估报表扫描会不会退化。
  • alter table ... lock datarows 本质上也是一次表重建,同样要排窗口、留空间。


另外把夜里那批 UPDATE 拆成分批提交,避免长事务把日志撑爆:
  1. # 每批 5000 行,批间 sleep 1 秒
  2. for i in $(seq 1 400); do
  3.   isql -Usa -Pxxxxxx -SASE157 -Dmydb <<EOF
  4. set rowcount 5000
  5. go
  6. update big_order set status = 9 where status = 1 and order_date < '2025-01-01'
  7. go
  8. EOF
  9.   sleep 1
  10. done
复制代码

步骤 8:把巡检固化下来

碎片是慢性病,靠人想起来才查一定会拖到出事。做法是每天采一次 optdiag,把关键指标落表,超过阈值就告警:
  1. #!/bin/sh
  2. # /opt/dba/frag_snapshot.sh  每天 02:00 采集
  3. optdiag statistics mydb -Usa -Pxxxxxx -SASE157 -o /var/log/optdiag/mydb_`date +%Y%m%d`.opt
  4. grep -E "Forwarded row count|Empty data page count|Data page cluster ratio|Space utilization" \
  5.      /var/log/optdiag/mydb_`date +%Y%m%d`.opt \
  6.   | awk '{ if ($0 ~ /Forwarded/ && $NF > 200000) print "WARN forwarded_row: " $0;
  7.            if ($0 ~ /cluster ratio/ && $NF < 0.30)  print "WARN cluster_ratio: " $0 }' \
  8.   | mail -s "ASE 碎片告警 mydb" dba@example.com
复制代码

想看某张表的索引到底有没有被用上(顺带揪出可以下线的冗余索引),可以查 MDA:
  1. 1> select ObjectID, IndexID, UsedCount, OptSelectCount
  2. 2> from master..monOpenObjectActivity
  3. 3> where DBID = db_id('mydb') and UsedCount > 0
  4. 4> order by UsedCount desc
  5. 5> go
  6. 1> select name from sysobjects where id = 1839201472
  7. 2> go
复制代码

前提是 MDA 表已经安装(installmontables 脚本)且登录被授予 mon_role,sp_configure 'enable monitoring' 为 1——没装 MDA 的话这个查询会直接报错,属于环境前置,不是 SQL 写错了。

四、实操检查清单


  • 动手前先 dump database 做全备,并确认备份可恢复,不要跳这一步。
  • 用 sp_spaceused + optdiag 取基线,至少记录 reserved / unused / forwarded row / cluster ratio 四项。
  • 用 set statistics io on、set statistics time on 记下代表性 SQL 的逻辑读与耗时,作为效果验收的对照。
  • 看 Forwarded row count:为 0 说明不是行迁移问题;数量大就只有 reorg rebuild 能清。
  • 看 Data page cluster ratio:低于 0.3 且业务有范围扫描,优先考虑 rebuild;高于 0.8 则不必为它单独停机。
  • 只有空页和页内碎片时选 reorg compact(15.0.2+)或 reorg reclaim_space,别一上来就 rebuild。
  • 确认日志段空间足够(经验值表大小的 20%~30%),不够先 alter database ... log on 扩容。
  • reorg rebuild 加排他锁,必须在维护窗口执行;先在不重要的表上跑一次,摸清耗时与日志增长速率。
  • rebuild 后必须补 update all statistics,否则列直方图陈旧,优化器可能给出更差的执行计划。
  • 大表可以按索引(reorg rebuild 表 索引名)或按分区执行,把影响面缩小。
  • 重建后用 dbcc checktable(big_order) 校验一遍再开放业务。
  • 复跑基线 SQL 做前后对比,把数字贴进变更记录,不要只写"已优化"。
  • 想根治 forwarded row 就改 alter table ... lock datarows,但先确认能否放弃聚簇索引。
  • 批量 UPDATE / DELETE 改成分批提交(set rowcount + 循环),避免长事务撑爆日志。
  • 明确告知业务方:reorg 回收的空间不还给文件系统,设备大小不会变,别按"能腾出磁盘"来做规划。
  • 把 optdiag 采集做成定时任务,对 forwarded row 数量、cluster ratio 设阈值告警,避免再次拖到出事。





以上命令在 ASE 15.7 / 16.0 环境实测通过,optdiag 与 isql 均来自 $SYBASE 安装目录。不同小版本的 reorg 语法细节略有差异,生产执行前请先在测试库验证。
免责申明1、欢迎访问本站,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@dbabbs.com
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

QQ|Archiver|小黑屋|DBA论坛中国 ( 鲁ICP备20017503号-2 )

GMT+8, 2026-10-20 07:52 , Processed in 0.017309 second(s), 9 queries , MemCached On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表