百科 / 错误代码 / Class 23 完整性约束冲突
23503 foreign_key_violation
外键约束冲突
ERROR 已实测 详解 实测通过
- 条件名
foreign_key_violation- 宏名称
ERRCODE_FOREIGN_KEY_VIOLATION- 启用版本
- 7.4
- 状态
- 活跃
版本覆盖
速览
23503 是 PostgreSQL 类别 23 integrity_constraint_violation 中的 foreign_key_violation 条件。子表插入或更新找不到匹配的父键,或者普通父表键操作触发外键规则,都可能产生它。在 PostgreSQL 18.6 中,父表 RESTRICT 分支改用 23001,因此应先按操作和完整诊断判断 SQLSTATE。
最有用的诊断字段是 SQLSTATE(C)、主报文(M)、detail(D),以及服务器提供时的 schema、table 和 constraint 字段。在 psycopg 中,这些字段位于 exc.sqlstate 和 exc.diag。重试前应保存完整诊断,因为当服务器可以展示键值时,detail 会指出缺少或仍被引用的键。
对于立即检查的外键约束,错误由违反约束的语句报告;延迟约束可以让语句先完成,并在 COMMIT 时报告 23503。自动提交下,立即失败的语句结束后连接可以执行下一条命令;显式事务中的立即失败会让事务变为 INERROR,必须执行 ROLLBACK 或回滚到 savepoint 后才能发送无关命令。代表性案例用真实的 parents/children 外键、有效父行和新的子行写入验证了修复。
案例 fk_insert_missing_parent 使用真实的 parents/children 外键,在 PostgreSQL 18.6 和隔离的 PostgreSQL 10.21 目标上均通过。run ID 和逐案例断言保存在公开证据 JSON中。
含义与触发路径
子表上的外键指向父表的主键或合适的唯一键。PostgreSQL 会在子表 INSERT、改变键值的 UPDATE 时检查这种关系;父表键被更新或删除时,也会检查反向引用。约束是立即检查还是延迟检查,决定了错误出现的具体时点。
列的匹配规则同样重要。默认的 MATCH SIMPLE 下,只要引用键中有任意列为 NULL,该行就不必匹配父表。MATCH FULL 允许全为 NULL 的键,或全部非 NULL 且能匹配父键的键,但会拒绝 NULL 与非 NULL 混合的键。代表性案例只有一个 NOT NULL 列,因此有意没有演示这两种 NULL 规则。
对于父表 DELETE 或改变键值的 UPDATE,ON DELETE/UPDATE NO ACTION 可以在约束可延迟时等到约束检查时点;RESTRICT 要求立即检查且不能延迟。在固定的 PostgreSQL 18.6 源码中,RESTRICT 分支使用 23001(restrict_violation)及专用报文,而不是本页案例中的 23503。动作选择是关系契约的一部分;父表操作不能按“缺少父键”的子表插入来推断 SQLSTATE。
普通子行路径在 ri_triggers.c 中使用 insert or update on table "%s" violates foreign key constraint "%s" 模板。可选 detail 为 Key (%s)=(%s) is not present in table "%s".;服务器还会通过协议的 table 和 constraint 字段附加子表及约束身份。普通的父键删除或更新分支可使用另一套 23503 主报文;固定版本的 RESTRICT 分支例外地使用 23001。本页运行证据只覆盖缺少父行的子表写入,不覆盖父表动作。
23503 只说明关系检查失败,不直接给出业务修复。缺少父行可能是写入顺序错误、标识符错误、另一个事务尚未提交,或需要级联策略的有意删除。选择修复前应检查语句和约束定义。
报文与诊断
代表性操作创建父表和子表,不插入键 99 对应的父行,然后插入子行。下面的可执行摘录与 runner 使用同一触发和恢复顺序;实际运行时由隔离 harness 为名称加上模式限定。
CREATE TABLE parents(id integer PRIMARY KEY);
CREATE TABLE children(
id integer PRIMARY KEY,
parent_id integer NOT NULL,
CONSTRAINT children_parent_fk FOREIGN KEY (parent_id) REFERENCES parents(id)
);
BEGIN;
INSERT INTO children VALUES (1, 99);
-- 服务器报告 23503,事务此时为 INERROR。
ROLLBACK;
BEGIN;
INSERT INTO parents VALUES (99);
INSERT INTO children VALUES (1, 99);
COMMIT;
PostgreSQL 18.6 的自然错误为:
SQLSTATE: 23503
severity: ERROR
message_primary: insert or update on table "children" violates foreign key constraint "children_parent_fk"
message_detail: Key (parent_id)=(99) is not present in table "parents".
schema_name: c23503_fk_insert_missing_parent
table_name: children
constraint_name: children_parent_fk
source: ri_triggers.c / ri_ReportViolation / line 2783
PostgreSQL 10.21 的主报文和 detail 相同;对应源码行为位于第 3266 行。detail 取决于服务器是否有权限描述键值,可能不存在。不要把本地化的英文报文当作协议契约:应按 23503 分支,再读取结构化字段和操作上下文。
报文模板
源码里的格式串,不是某一次运行的输出。%s 之类是占位符,实际报文会填入对象名与取值。适用范围一栏是核验时留下的原始英文记录,未经翻译。
insert or update on table "%s" violates foreign key constraint "%s"
Key (%s)=(%s) is not present in table "%s".
来源:src/backend/utils/adt/ri_triggers.c(lines 2761-2809) @ REL_18_6
适用范围:The detail is conditional; parent delete/update paths use different primary and detail templates.
update or delete on table "%s" violates foreign key constraint "%s" on table "%s"
Key (%s)=(%s) is still referenced from table "%s".
来源:src/backend/utils/adt/ri_triggers.c(lines 2761-2809) @ REL_18_6
适用范围:This is the ordinary parent-key update/delete branch; the fixed RESTRICT branch uses SQLSTATE 23001 and different wording.
诊断
先记录失败语句、SQLSTATE、严重级别、主报文、detail、hint、服务器版本和事务状态。显式事务要分别记录错误后(INERROR)以及恢复后(IDLE 或 INTRANS)的状态。后续的 25P02 表示客户端在事务已经失败时发送了命令;它是后续状态,不能替代 23503。
使用 pg_constraint 和 pg_get_constraintdef() 检查命名约束及其引用关系。结合适用的隔离级别检查尝试写入的键和父表。如果父行由另一个事务创建,应确认写入和提交顺序是否符合设计;固定等待并不能证明父行已经可见。
对于父键删除或更新,检查引用行以及 ON DELETE、ON UPDATE 声明的动作。延迟外键可能允许违规语句暂时成功,而在 COMMIT 时才报告 23503。日志和重试逻辑应保留这一时点。
处理与修复
选择符合关系语义的修复:
- 像代表性案例一样,先创建或选择目标父行,再重试子行写入。
- 如果子标识符过期或格式错误,修正子行标识符;不要关闭约束来掩盖数据错误。
- 删除父行时,只有业务规则允许时才采用声明的级联、置空或限制动作;否则应先更新或归档引用行。
- 如果父行由另一个事务写入,应采用能建立预期顺序和隔离级别的事务设计。回滚后重新读取,再决定是否重放旧的子行请求。
显式事务失败后,ROLLBACK 会丢弃其中的待提交工作并让连接回到 IDLE。当子行操作可选时,可以使用 savepoint 保留之前的工作。真正的修复应包括父行的实际读取、提交后的子行以及提交后查询;只执行 ROLLBACK 或打开新连接不能证明关系已经修好。
可复现案例
在一次性实例上执行过的场景。其中 1 个附有可执行 SQL,正文相应小节里给出。
fk_insert_missing_parent PG 10 / 18 有 SQL
前置条件
- A parent table and a child table with a NOT DEFERRABLE foreign key
- The referenced key is absent
触发
Insert a child row whose parent key does not exist.
断言
- SQLSTATE is 23503
- The foreign-key constraint and child table are identified
- A valid parent and child row can be committed after rollback
处置
Create or select the intended parent before inserting the child; do not disable the constraint to hide the data error.
清理
Drop the case schema with an owner connection.
版本与边界
目录在 PostgreSQL 7.4 的锁定定义中已观察到 23503,并持续到 8.4.22 的 pre-9.0 定义;随后在列出的所有正式快照直到 PostgreSQL 18.6 以及 PostgreSQL 19 Beta 3 预览中存在。这是 definition_only 的存在边界,不是确切实现引入版本或运行时使用断言。扫描范围内没有记录该条件的定义变化。
代表性案例在 PostgreSQL 18.6 和 10.21 上通过。两个版本的源码行号不同;本页采用 SQLSTATE、约束身份和外键诊断形状作为兼容边界。延迟时点、权限、级联动作以及并发创建父行是独立维度,本次立即约束案例不覆盖它们。
来源
- 核验材料 doc/src/sgml/ddl.sgml
- 核验材料 doc/src/sgml/protocol.sgml
- 核验材料 src/backend/utils/errcodes.txt
- 核验材料 src/backend/utils/adt/ri_triggers.c 第 2761–2809 行
证据
断言
每条断言都写明了是怎么核实的,以及它不覆盖什么。这一层是核验时留下的原始英文记录,照原样呈现,未经翻译。
-
23503 is the foreign_key_violation condition in Class 23 integrity_constraint_violation.
-
The ordinary missing-parent path reports insert or update on table "%s" violates foreign key constraint "%s", conditionally adds Key (%s)=(%s) is not present in table "%s"., and attaches the child table and constraint.
-
NO ACTION parent checks use the ordinary foreign-key violation branch and can be deferred when the constraint is deferrable; the fixed PostgreSQL 18.6 RESTRICT branch uses ERRCODE_RESTRICT_VIOLATION (23001) with a RESTRICT-specific message and cannot be deferred.
-
A failed child write leaves an explicit transaction INERROR until rollback; a valid parent and child can then be committed.
运行记录
| 目标 | 服务器版本 | 结果 | 覆盖案例 |
|---|---|---|---|
| latest | 18.6 (Homebrew) | passed | fk_insert_missing_parent |
| pg10 | 10.21 (Debian 10.21-1.pgdg90+1) | passed | fk_insert_missing_parent |
| latest | 18.6 (Homebrew) | passed | fk_insert_missing_parent |
| pg10 | 10.21 (Debian 10.21-1.pgdg90+1) | passed | fk_insert_missing_parent |
| latest | 18.6 (Homebrew) | passed | fk_insert_missing_parent |
| pg10 | 10.21 (Debian 10.21-1.pgdg90+1) | passed | fk_insert_missing_parent |
| latest | 18.6 (Homebrew) | passed | fk_insert_missing_parent |
| pg10 | 10.21 (Debian 10.21-1.pgdg90+1) | passed | fk_insert_missing_parent |
同类错误代码
Class 23 完整性约束冲突 下的其他成员。