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 约束的值没有对应的父记录。
在常见的表结构下,执行过程如下:
- 临时表中找到了待写入的 BLOB。
- Oracle 尝试向
ACTUATION_IMAGES插入记录。 - 记录中的外键值在父表中不存在,或者对当前会话不可见。
- 整条图像插入语句失败。
如果应用随后执行 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,本例就表示对应信息记录在插入图像时不存在或不可见,所以图像记录未能写入。
备注:内容仅供参考。
