PAYATHON 2026

Oracle 19c 插入数据时出现 ORA-02291

排障赵师傅

结论

ORA-02291 说明在向子表 SV.ACTUATION_IMAGES 插入记录时,外键约束 SV.FK_ACT_IMAGE 找不到对应的父表键值。

这条语句已经执行失败,目标表中没有写入该图像记录。该错误针对外键关系,与 BLOB 图像内容是否损坏无关。插入记录中的某个外键值在父表中找不到对应记录。

按名称推测,FK_ACT_IMAGE 可能约束 ACTUATION_IMAGES.ACTUATION_ID,但约束名不足以确认具体关系,需要查询数据字典,确定关联的列和父表。

查出外键对应的父表和字段

使用有数据字典查询权限的账号执行:

SELECT
    c.owner           AS child_owner,
    c.table_name      AS child_table,
    cc.column_name    AS child_column,
    p.owner           AS parent_owner,
    p.table_name      AS parent_table,
    pc.column_name    AS parent_column,
    cc.position
FROM all_constraints c
JOIN all_cons_columns cc
  ON cc.owner = c.owner
 AND cc.constraint_name = c.constraint_name
JOIN all_constraints p
  ON p.owner = c.r_owner
 AND p.constraint_name = c.r_constraint_name
JOIN all_cons_columns pc
  ON pc.owner = p.owner
 AND pc.constraint_name = p.constraint_name
 AND pc.position = cc.position
WHERE c.owner = 'SV'
  AND c.constraint_name = 'FK_ACT_IMAGE'
  AND c.constraint_type = 'R'
ORDER BY cc.position;

查询结果中的字段含义如下:

  • CHILD_TABLE、CHILD_COLUMN:本次插入需要检查的子表及字段。
  • PARENT_TABLE、PARENT_COLUMN:该字段引用的父表及父键字段。
  • POSITION:复合外键中各字段的对应顺序。

如果当前账号就是 SV,也可以查询 USER_CONSTRAINTS 和 USER_CONS_COLUMNS。使用 ALL_* 视图则更方便同时查看父表和子表的 owner。

检查缺少的具体父键

假设查询结果显示以下关系:

ACTUATION_IMAGES.ACTUATION_ID
    -> ACTUATIONS.ACTUATION_ID

可以用下面的语句检查父键:

SELECT *
FROM sv.actuations
WHERE actuation_id = 14549011115595030000007290;

如果查询没有返回记录,原因就很明确了:准备写入 ACTUATION_IMAGES.ACTUATION_ID 的值是 14549011115595030000007290,而父表中没有相同的 ACTUATION_ID。

处理时应先写入父表,再写入图像子表:

INSERT INTO sv.actuations (
    actuation_id
    -- 其他必要字段
)
VALUES (
    14549011115595030000007290
    -- 其他字段值
);

INSERT INTO sv.actuation_images (
    actuation_id,
    seq_nbr,
    image,
    image_type
)
SELECT
    14549011115595030000007290,
    NULL,
    blob_data,
    'zip'
FROM sv.blob_temp
WHERE blob_id = 14549011115595030000008647;

COMMIT;

实际的父表名称和必填字段,应以数据字典查询结果及表结构为准。

这次失败的是对应信息还是图像

从报错语句来看,失败发生在向 ACTUATION_IMAGES 插入图像记录时。Oracle 保证单条 SQL 语句的原子性,因此这条 INSERT 失败后,不会留下不完整的图像记录。

该语句使用了 INSERT ... SELECT:

SELECT 14549011115595030000007290,
       NULL,
       blob_data,
       'zip'
FROM blob_temp
WHERE blob_id = 14549011115595030000008647

如果 BLOB_TEMP 中找不到该 blob_id,这个 SELECT 会返回零行,结果通常只是插入零行,不会触发 ORA-02291。既然出现了外键错误,说明语句至少尝试插入了一行,其中受 FK_ACT_IMAGE 约束的值没有对应的父记录。

在常见的表结构下,执行过程如下:

  1. 临时表中找到了待写入的 BLOB。
  2. Oracle 尝试向 ACTUATION_IMAGES 插入记录。
  3. 记录中的外键值在父表中不存在,或者对当前会话不可见。
  4. 整条图像插入语句失败。

如果应用随后执行 ROLLBACK,同一事务中先前插入的对应信息也可能被回滚。因此,事后在 SQL Developer 中同时查不到对应信息和图像,不能据此认定 BLOB 最初就不存在。

为什么问题只是偶尔发生

间歇性出现这类错误时,需要重点检查事务和会话,不能只比较事务结束后的数据。

父记录尚未提交

如果父记录和图像分别通过两个数据库连接写入,连接 B 看不到连接 A 尚未提交的数据:

连接 A:INSERT 父记录,但尚未 COMMIT
连接 B:INSERT 图像子记录,触发 ORA-02291

如果两次插入使用同一个连接和同一个事务,后执行的语句可以看到本事务中刚插入但尚未提交的父记录。

写入顺序存在竞争

异步任务、并行队列或不同线程可能偶尔先处理图像,后处理对应信息。程序需要在父记录插入成功后,再执行子表插入。

父记录插入本身已经失败

应用可能只记录了图像插入异常,遗漏了此前发生的父表插入异常。日志中应保留每条 SQL 的执行结果、受影响行数、连接或事务标识,以及完整的异常链。

父记录被回滚或删除

父记录可能已经插入,之后又因其他错误被回滚。也可能是清理程序删除了父记录,而图像写入任务仍在使用旧标识。

大整数在应用层被改变

示例中的 ID 有 26 位。Oracle NUMBER 可以精确保存该整数,但如果应用先将它存入 IEEE 754 双精度浮点类型,数值在发送给 Oracle 前就可能已经被舍入。

需要检查应用的参数绑定类型,避免使用 Double 表示这类标识。可根据程序语言改用高精度十进制类型、整数类型或字符串,同时确认数据库列的数据类型和精度与之匹配。

还应核对临时图像数据

可以通过以下语句检查 SQL Developer 当前会话能否看到源数据:

SELECT blob_id,
       DBMS_LOB.GETLENGTH(blob_data) AS blob_length
FROM sv.blob_temp
WHERE blob_id = 14549011115595030000008647;

如果 BLOB_TEMP 是全局临时表,数据可能只对创建它的会话可见。SQL Developer 中查不到数据,不代表应用执行插入时也查不到。排查时应记录应用会话中的查询结果或受影响行数。

对 NULL 的判断

示例中的 SEQ_NBR 是 NULL。如果 FK_ACT_IMAGE 是定义在 SEQ_NBR 上的单列外键,NULL 通常不会触发父键查找,一般也不会导致 ORA-02291。

最终仍要以数据字典的查询结果为准,因为该约束可能是复合外键,也可能实际约束了其他字段。

建议的修复方向

先用 ALL_CONSTRAINTS 和 ALL_CONS_COLUMNS 确认 FK_ACT_IMAGE 的父子列映射,再根据实际父键逐项检查:

  • 父记录是否存在。
  • 父记录是否在插入图像前成功写入。
  • 两次写入是否使用同一个连接和事务。
  • 不同连接之间是否缺少 COMMIT。
  • 异步任务是否改变了父子表的写入顺序。
  • 应用是否使用浮点类型传递长 ID。
  • 事务失败后的统一回滚是否撤销了此前写入的父记录。

如果查询结果确认 ACTUATION_IMAGES.ACTUATION_ID 引用了某张信息表的 ACTUATION_ID,本例就表示对应信息记录在插入图像时不存在或不可见,所以图像记录未能写入。

备注:内容仅供参考。