rowid 是 oracle 中指向行物理位置的二进制伪列,非字符串;直接拼接 .tostring() 会导致 ora-01410 错误,必须用 oracledbtype.rowid 参数化传递,并避免跨事务或缓存复用。

ROWID不是字符串,别直接拼进DELETE语句
最常见的错误是:查出 ROWID 后调用 .ToString(),再拼成 "DELETE FROM t WHERE ROWID = '" + rid.ToString() + "'"。这会触发 Oracle 的校验和验证失败,报 ORA-01410: invalid rowid——哪怕字符串看起来“格式正确”。因为 ROWID 在 ODP.NET 中本质是二进制结构,Base64 编码只是传输表现,中间任意空格、换行、大小写变化或截断都会让校验和失效。
正确做法是全程保持原生类型流转:
- 查询时用
OracleDataReader读取ROWID列,结果是OracleString类型,不调.ToString() - 删除时用参数化:新建
OracleParameter("rid", OracleDbType.RowId),把刚读到的OracleString实例直接赋给Value - SQL 里写死
WHERE ROWID = :rid,绝不用字符串插值
ODP.NET 中 OracleDbType.RowId 必须显式指定
即使你传的是 OracleString,如果没设 OracleParameter.OracleDbType = OracleDbType.RowId,ODP.NET 可能按默认规则推断为 VarChar,导致底层仍走字符串解析路径,一样触发 ORA-01410。
示例关键片段:
cmd.CommandText = "DELETE FROM emp WHERE ROWID = :rid";
var ridParam = new OracleParameter("rid", OracleDbType.RowId);
ridParam.Value = reader.GetOracleString(1); // 假设第2列是ROWID
cmd.Parameters.Add(ridParam);
注意:如果从 DataTable 或 ORM(如 Dapper)拿到的是 string,必须包一层:new OracleString(ridString) 再传入,否则类型丢失。
ROWID 生命周期短,别缓存超过单次事务
ROWID 指向的是“数据文件号_块号_行号”这个物理地址。一旦发生行迁移(UPDATE 导致行变长且原块放不下)、表移动(ALTER TABLE ... MOVE)、分区拆分或高水位重置,ROWID 就失效。
所以:
- 不要把
ROWID存进 Redis 或本地缓存复用 - 不要跨事务使用——查完立刻删/改,中间别 commit 再去执行
- 批量操作时,每个
ROWID都应来自本次查询结果集,不能复用上次查询的值
用 DBMS_ROWID 解析 ROWID 查看物理位置
调试或排查时,常需确认某条记录到底在哪个数据文件、哪个块、哪一行。这时不能靠肉眼解码 Base64 字符串,要用 Oracle 自带的 DBMS_ROWID 包:
SELECT ROWID, DBMS_ROWID.ROWID_RELATIVE_FNO(ROWID) AS file_no, DBMS_ROWID.ROWID_BLOCK_NUMBER(ROWID) AS block_no, DBMS_ROWID.ROWID_ROW_NUMBER(ROWID) AS row_no FROM emp WHERE ROWNUM <p>返回的 <code>file_no</code>、<code>block_no</code>、<code>row_no</code> 是真实物理坐标,可用于和 AWR、ASH 或底层存储日志对齐。注意:该函数只对堆表(heap table)有效,索引组织表(IOT)返回的是逻辑 <code>ROWID</code>,解析结果无意义。</p> <p>真正难的不是怎么写 DELETE,而是怎么确保拿到的 <code>ROWID</code> 还有效、传得对、用得准——它是个物理指针,不是业务主键,容错率极低。</p>











