选择 打开 改范围 完整检索页

pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。

百科 / 错误代码 / Class 23 完整性约束冲突

23503 foreign_key_violation

外键约束冲突

ERROR 已实测 详解 实测通过

类别
Class 23 完整性约束冲突
严重等级
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.sqlstateexc.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 或改变键值的 UPDATEON DELETE/UPDATE NO ACTION 可以在约束可延迟时等到约束检查时点;RESTRICT 要求立即检查且不能延迟。在固定的 PostgreSQL 18.6 源码中,RESTRICT 分支使用 23001restrict_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"
DETAIL 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"
DETAIL 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)以及恢复后(IDLEINTRANS)的状态。后续的 25P02 表示客户端在事务已经失败时发送了命令;它是后续状态,不能替代 23503

使用 pg_constraintpg_get_constraintdef() 检查命名约束及其引用关系。结合适用的隔离级别检查尝试写入的键和父表。如果父行由另一个事务创建,应确认写入和提交顺序是否符合设计;固定等待并不能证明父行已经可见。

对于父键删除或更新,检查引用行以及 ON DELETEON 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、约束身份和外键诊断形状作为兼容边界。延迟时点、权限、级联动作以及并发创建父行是独立维度,本次立即约束案例不覆盖它们。

来源

证据

断言

每条断言都写明了是怎么核实的,以及它不覆盖什么。这一层是核验时留下的原始英文记录,照原样呈现,未经翻译。

  • 23503 is the foreign_key_violation condition in Class 23 integrity_constraint_violation.

    核实方式Read the frozen errcodes.txt class and condition rows.

    不覆盖This establishes directory identity; it does not enumerate every foreign-key trigger path.

    来源src/backend/utils/errcodes.txt(lines 235-241)

  • 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.

    核实方式Trace ri_ReportViolation in the fixed source and compare the protocol diagnostics from fk_insert_missing_parent.

    不覆盖DETAIL is conditional on diagnostic visibility and the operation; parent deletion and deferred checks use different message shapes or timing.

    来源src/backend/utils/adt/ri_triggers.c(lines 2761-2809) · doc/src/sgml/protocol.sgml(ErrorResponse fields)

  • 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.

    核实方式Trace the fixed ri_ReportViolation branches for ordinary parent references and is_restrict, then compare the foreign-key action documentation.

    不覆盖This is a source boundary for parent-key operations, not runtime evidence for the missing-parent child insert case. SQLSTATE and primary text depend on the action branch and release.

    来源src/backend/utils/adt/ri_triggers.c(lines 2761-2809) · doc/src/sgml/ddl.sgml(foreign-key constraints)

  • A failed child write leaves an explicit transaction INERROR until rollback; a valid parent and child can then be committed.

    核实方式Run the missing-parent case on isolated PostgreSQL 18.6 and 10.21 targets and assert status transitions and the post-commit row.

    不覆盖The representative case uses an immediate foreign key and does not establish deferred timing, cascading actions, or concurrent parent creation.

    来源doc/src/sgml/ddl.sgml(foreign-key constraints)

运行记录

目标服务器版本结果覆盖案例
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 完整性约束冲突 下的其他成员。