确认行迁移需先查v$sysstat中'table fetch continued row'值是否持续上升,再用analyze table list chained rows定位;dbms_stats无法检测。

行迁移怎么确认存在
别急着修,先看是不是真有行迁移。直接查 v$sysstat 最快:SELECT name, value FROM v$sysstat WHERE name = 'table fetch continued row'。这个值持续上升,基本就是行迁移在拖慢查询。再配合 ANALYZE TABLE your_table_name LIST CHAINED ROWS,前提是已运行过 utlchn1.sql 创建了 chained_rows 表。注意:用 DBMS_STATS 收集统计信息是检测不出行迁移的。
为什么不能只用 ALTER TABLE ... MOVE
MOVE 确实能清掉行迁移,但它会失效所有索引——包括本地索引和全局索引,而且不自动重建。执行完必须手动跑一遍 ALTER INDEX idx_name REBUILD,否则后续查询可能走全表扫描或报 ORA-01502。更麻烦的是,如果表上有外键约束、物化视图日志或正在被 GoldenGate 同步,MOVE 会中断依赖链,得提前协调。它也不动 PCTFREE,旧碎片空间还在,新插入又可能快速复现迁移。
安全修复的四步闭环操作
真正稳妥的做法是“删—调—插”闭环,不破坏依赖也不锁太久:
- 创建临时表存迁移行:
CREATE TABLE tmp_mig AS SELECT * FROM your_table WHERE rowid IN (SELECT head_rowid FROM chained_rows WHERE table_name = 'YOUR_TABLE_NAME') - 删除原表中这些行:
DELETE FROM your_table WHERE rowid IN (SELECT head_rowid FROM chained_rows WHERE table_name = 'YOUR_TABLE_NAME') - 调高
PCTFREE预留更新空间:ALTER TABLE your_table PCTFREE 20(根据字段膨胀程度选 15–30) - 把临时表数据插回去:
INSERT /*+ APPEND */ INTO your_table SELECT * FROM tmp_mig,最后DROP TABLE tmp_mig
这过程保留原 ROWID、不碰索引、不影响外键,但要注意:如果表有触发器,INSERT 会触发;如果用了 APPEND,得确保没开行级触发器或审计。
修复后还要防复发
修完不是终点。行迁移本质是 UPDATE 导致行变长 + 块空间不足。所以得盯两件事:PCTFREE 是否长期够用,以及哪些字段常被更新且长度波动大(比如 VARCHAR2(4000) 字段反复写不同长度内容)。对这类列,要么加检查约束限制长度,要么考虑用压缩表(COMPRESS FOR OLTP),或者干脆拆出大字段到单独表里。另外,table fetch continued row 这个指标要加入日常巡检,别等慢了才想起查。











