chain_cnt是Oracle中判断行迁移和行链接的最直接指标,需通过DBMS_STATS.GATHER_TABLE_STATS收集统计信息后才准确;若chain_cnt/num_rows>5%,且avg_row_len远小于db_block_size,则大概率为行迁移,主因是PCTFREE设置过小,调高至20~30并重建表可根治。

查 chain_cnt 是最直接的指标
Oracle 把行迁移和行链接都记在 user_tables.chain_cnt 字段里,它不区分到底是迁移还是链接,只统计“访问时需要跨块读取”的行数。这个值不是实时更新的,必须先收集统计信息才能反映真实情况。
-
DBMS_STATS.GATHER_TABLE_STATS必须执行,否则chain_cnt可能为 0 或陈旧 - 如果表有大量
UPDATE(尤其变长字段如VARCHAR2扩容),chain_cnt上升快;纯INSERT一般只触发行链接,但大行 + 小块也会立刻出现 -
chain_cnt > 0且占比超过 5%(即chain_cnt / num_rows > 0.05)就该介入,别等查询明显变慢
确认是不是行迁移而不是行链接
行链接是插入时单行太大、一个块塞不下,数据被拆到同一 extent 的多个块;行迁移是 UPDATE 后行变大、原块没空间,整行被搬走,原位置只剩指针。两者性能影响类似,但成因和应对方式不同。
- 看
avg_row_len和db_block_size:如果平均行长接近或超过块大小(比如avg_row_len > 7000且块是 8K),大概率是行链接;如果avg_row_len很小(如 200),但chain_cnt高,基本就是行迁移 - 查
v$segment_statistics中physical reads和table fetch continued row的比值:后者高说明每次读都在跳转,符合迁移特征 - 用
ANALYZE TABLE ... LIST CHAINED ROWS把问题行导出到chained_rows表,再查这些行的原始ROWID对应块是否已空——如果是,就是迁移;如果块还满,更倾向链接
shrink space 对行迁移无效,别白忙
ALTER TABLE ... SHRINK SPACE 只能压缩高水位线(HWM)以下的空闲空间,把分散的数据往前提,但它不会把已被迁移出去的行拉回来。那些指针还在原块里,chain_cnt 不会下降。
- 启用行移动(
ENABLE ROW MOVEMENT)是 shrink 前提,但它本身不修复迁移,只是允许 shrink 过程中移动行 - shrink 后
chain_cnt不变,甚至可能略增(因数据重排触发新迁移) - 真正清除行迁移,得重建:要么
CREATE TABLE AS SELECT+DROP+RENAME,要么ALTER TABLE MOVE(注意索引会失效)
别漏掉 PCTFREE 这个根因参数
很多团队查到 chain_cnt 高就急着重建表,但没动 PCTFREE,过几周又打回原形。行迁移本质是块内没预留足够更新空间。
- 默认
PCTFREE是 10,对频繁更新的表远远不够;建议设为 20~30,尤其含VARCHAR2(4000)这类字段的表 - 改
PCTFREE不影响现有数据,只对后续 INSERT/UPDATE 生效;已有迁移行不会自动修复 - 重建表时带上
PCTFREE 25,比单纯重建多一层防护
DBMS_STATS.GATHER_TABLE_STATS,再查 user_tables 看 chain_cnt,结合 avg_row_len 和业务 DML 类型判断迁移还是链接,最后决定是调参、收缩,还是重建。最容易被跳过的环节,是忘记检查 PCTFREE 是否合理——它不报错,也不进告警,但会让所有后续操作事倍功半。


















