↑↓ 选择 ↵ 打开 ⌫ 改范围 完整检索页

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

百科 / SQL 状态码 / Class 23 完整性约束违反

23503 foreign_key_violation

外键约束冲突

ERROR 已实测 详解 实测通过

类别
Class 23 完整性约束违反
严重等级
ERROR
条件名
foreign_key_violation
宏名称
ERRCODE_FOREIGN_KEY_VIOLATION
最早已知存在
7.4
状态
有效

手册收录:8.1、8.2、8.3、8.4、9.0、9.1、9.2、9.3、9.4、9.5、9.6、10、11、12、13、14、15、16、17、18、19、20。此处仅表示同版本手册收录,运行证据仍以原核验构建为准。

版本覆盖

版本条依据已采样构建显示;灰色版本可能尚未采样,悬停可查看。手册链接随版本选择,源码和运行证据保持原核验构建。

速览 {#at-a-glance}

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中。

含义与触发路径 {#meaning}

子表上的外键指向父表的主键或合适的唯一键。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 只说明关系检查失败,不直接给出业务修复。缺少父行可能是写入顺序错误、标识符错误、另一个事务尚未提交,或需要级联策略的有意删除。选择修复前应检查语句和约束定义。

报文与诊断 {#messages}

代表性操作创建父表和子表,不插入键 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 分支,再读取结构化字段和操作上下文。

诊断 {#diagnosis}

先记录失败语句、SQLSTATE、严重级别、主报文、detail、hint、服务器版本和事务状态。显式事务要分别记录错误后(INERROR)以及恢复后(IDLE 或 INTRANS)的状态。后续的 25P02 表示客户端在事务已经失败时发送了命令;它是后续状态,不能替代 23503。

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

对于父键删除或更新,检查引用行以及 ON DELETE、ON UPDATE 声明的动作。延迟外键可能允许违规语句暂时成功,而在 COMMIT 时才报告 23503。日志和重试逻辑应保留这一时点。

处理与修复 {#response}

选择符合关系语义的修复:

  • 像代表性案例一样,先创建或选择目标父行,再重试子行写入。
  • 如果子标识符过期或格式错误,修正子行标识符;不要关闭约束来掩盖数据错误。
  • 删除父行时,只有业务规则允许时才采用声明的级联、置空或限制动作;否则应先更新或归档引用行。
  • 如果父行由另一个事务写入,应采用能建立预期顺序和隔离级别的事务设计。回滚后重新读取,再决定是否重放旧的子行请求。

显式事务失败后,ROLLBACK 会丢弃其中的待提交工作并让连接回到 IDLE。当子行操作可选时,可以使用 savepoint 保留之前的工作。真正的修复应包括父行的实际读取、提交后的子行以及提交后查询;只执行 ROLLBACK 或打开新连接不能证明关系已经修好。

版本与边界 {#versions}

目录在 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、约束身份和外键诊断形状作为兼容边界。延迟时点、权限、级联动作以及并发创建父行是独立维度,本次立即约束案例不覆盖它们。

相关 {#related}

23505 — unique_violation 处理唯一性不变量中的重复值。23502 — not_null_violation 处理必填列收到 NULL。40001 — serialization_failure 和 40P01 — deadlock_detected 描述可能围绕关系修复出现的并发结果。25P02 — in_failed_sql_transaction 是未处理错误之后的后续事务状态。

来源 {#sources}

本页结构化证据记录在公开证据 JSON中。源码记录固定到 PostgreSQL commit 724edf9bde9d356724ad384a2e196edc3c9f80f7;runtime 记录保留两个目标的 run ID 及结构化观察。

来源

证据

断言

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

  • 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) @ REL_18_6

  • 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) @ REL_18_6 · 来源:doc/src/sgml/protocol.sgml(ErrorResponse fields) @ REL_18_6

  • 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) @ REL_18_6 · 来源:doc/src/sgml/ddl.sgml(foreign-key constraints) @ REL_18_6

  • 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) @ REL_18_6

运行记录

目标服务器版本结果覆盖案例
latest 18.6 (Homebrew) passed 案例:fk_insert_missing_parent
pg10 10.21 (Debian 10.21-1.pgdg90+1) passed 案例:fk_insert_missing_parent
latest

This rerun proves the page's explicit BEGIN around the post-rollback repair; it does not test deferred or cascading foreign keys.

18.6 (Homebrew) passed 案例:fk_insert_missing_parent
pg10

This rerun proves the page's explicit BEGIN around the post-rollback repair; it does not test deferred or cascading foreign keys.

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

This run proves that the registry BEGIN/ROLLBACK/BEGIN/COMMIT sequence executes with autocommit=True; it covers the single-parent foreign-key case.

18.6 (Homebrew) passed 案例:fk_insert_missing_parent
pg10

This run proves that the registry BEGIN/ROLLBACK/BEGIN/COMMIT sequence executes with autocommit=True; it covers the single-parent foreign-key case.

10.21 (Debian 10.21-1.pgdg90+1) passed 案例:fk_insert_missing_parent

报文模板

源码里的格式串,不是某一次运行的输出。%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.

可复现案例

在一次性实例上执行过的场景。其中 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.

同类 SQL 状态码

状态码 条件名 宏名称 严重等级 版本
Class 23 完整性约束违反 Integrity Constraint Violation 7 个 ↗
23000 integrity_constraint_violation ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION ERROR 7.4 起已知
完整性约束冲突的类别码,应使用具体子码。 有效
23001 restrict_violation ERRCODE_RESTRICT_VIOLATION ERROR 7.4 起已知
外键 RESTRICT 动作拒绝删除仍被引用的父行。 有效
23502 not_null_violation ERRCODE_NOT_NULL_VIOLATION ERROR 7.4 起已知
向 NOT NULL 列写入了 NULL 值。 有效
23503 foreign_key_violation ERRCODE_FOREIGN_KEY_VIOLATION ERROR 7.4 起已知
外键约束冲突,子行找不到匹配的父键。 有效
23505 unique_violation ERRCODE_UNIQUE_VIOLATION ERROR 7.4 起已知
行或索引违反唯一约束,常见于重复键插入。 有效
23514 check_violation ERRCODE_CHECK_VIOLATION ERROR 7.4 起已知
CHECK 约束计算为假,写入的行不满足条件。 有效
23P01 exclusion_violation ERRCODE_EXCLUSION_VIOLATION ERROR 9.0.0 起已知
排除约束发现新行与已有行冲突,如范围重叠。 有效