ROWID会变,因其本质是含对象号、文件号、块号、行号的逻辑编码而非绝对物理地址;表重组(如MOVE、SHRINK、在线重定义)导致数据位置变更,ROWID随之失效。

ROWID 为什么会变?不是说它是物理地址吗
ROWID 确实是 Oracle 中行的物理地址,但它不是“硬盘扇区+偏移”那种绝对不变的地址,而是由 data_object_id、文件号、块号、行号组成的逻辑编码。一旦发生表重组(比如 ALTER TABLE ... MOVE、SHRINK SPACE、在线重定义、分区拆分/合并),数据块会被重新分配,行被挪到新位置,data_object_id 或块号/行号就变了——ROWID 自然失效。
常见触发场景包括:
-
ALTER TABLE t MOVE或MOVE PARTITION p1 -
ALTER TABLE t SHRINK SPACE COMPACT(尤其带CASCADE) - 使用
DBMS_REDEFINITION做在线重定义 - RAC 环境下某节点执行 DDL 导致全局
data_object_id刷新
游标里缓存的 ROWID 在重组后直接报 ORA-01410
如果应用用 PL/SQL 游标查出一批 ROWID 后暂存(比如存在集合类型变量中),再循环做 UPDATE ... WHERE ROWID = :rid,而中间表被 MOVE 过——那些缓存的 ROWID 就全变成无效值了。执行时 Oracle 检查到该 ROWID 对应的块/行不存在或对象 ID 不匹配,立刻抛 ORA-01410: invalid ROWID。
这不是游标本身坏了,而是它持有的“地址本”过期了。你用 SELECT ROWID FROM t 现查,得到的是新 ROWID;但旧游标里存的还是老地址。
为什么有时候不报错,却更新了 0 行?
这通常发生在 SQL 层面直接拼接 ROWID 字符串(非绑定变量),且表重组后旧 ROWID 恰好“格式合法但指向空块”。Oracle 不会立即校验 ROWID 是否真实可寻址,而是走正常执行计划:定位块 → 定位行槽 → 检查 ITL 或行头。若块已重用、行槽为空或头信息损坏,最终 Rows matched: 0,不报错但无效果。
典型表现:
- 第一次
UPDATE ... WHERE ROWID = 'AAABmAABAAABrAAB1'成功(ROWID 有效) - 执行
ALTER TABLE t MOVE - 第二次同样语句返回
0 rows affected,SQL%ROWCOUNT = 0 - 查当前数据:新 ROWID 已变成
AAABnAABAAABsBAC2
如何避免这类问题
核心原则:ROWID 是瞬态标识,不能跨 DDL 生命周期复用。实际开发中要主动切断依赖:
- 业务逻辑中避免长期缓存 ROWID 字符串,尤其不能写进日志、消息队列或数据库持久化字段
- PL/SQL 批量处理时,把
SELECT ROWID和UPDATE ... WHERE ROWID放在同一个事务或紧密连续的语句块中,中间不穿插 DDL - 必须支持表重组的场景(如归档、压缩),改用主键或唯一业务键做更新条件,或者用
SELECT ... FOR UPDATE NOWAIT加锁后立即更新 - 监控脚本里若用 ROWID 做巡检基准,每次执行前先
SELECT ROWID动态获取,别硬编码
最易被忽略的一点:即使没手动执行 MOVE,某些自动维护任务(如 DBMS_SPACE_ADMIN.ASSIGN_MIGRATE_TABLESPACE 或云平台后台优化)也可能悄悄触发行迁移或段重组,ROWID 同样会变。











