数据库-引用完整性(referential integrity)

文章目录

  • 前言
  • [练兵场 🚀🏋️🏋️🏋️](#练兵场 🚀🏋️🏋️🏋️)
      • [1. 创建两张表](#1. 创建两张表)
      • [2. 插数据,看约束效果](#2. 插数据,看约束效果)
      • [3. 试试引用违规](#3. 试试引用违规)
      • [4. CASCADE 生效](#4. CASCADE 生效)
      • [5. 换成 RESTRICT 对比](#5. 换成 RESTRICT 对比)
      • [6. SET NULL](#6. SET NULL)
      • [7. 回到你项目中的实际场景](#7. 回到你项目中的实际场景)

前言

分析这个Prisma 的sql,在AI客服💁项目删除了文档后关联文档chunk 也删除了。

sql 复制代码
ALTER TABLE "DocumentChunk" ADD CONSTRAINT "DocumentChunk_documentId_fkey" 
FOREIGN KEY ("documentId") REFERENCES "Document"("id") ON DELETE CASCADE;

拆开来讲。


这不是 Prisma 特有的,是 SQL 标准。几乎所有主流关系数据库都支持:

数据库 是否支持 ON DELETE CASCADE
PostgreSQL
MySQL / MariaDB
SQLite
SQL Server
Oracle
DuckDB

写法几乎一模一样:

sql 复制代码
-- PostgreSQL / MySQL / SQLite / SQL Server / Oracle
FOREIGN KEY (documentId) REFERENCES Document(id) ON DELETE CASCADE

实际上 Prisma 的 @relation(onDelete: Cascade) 最终生成的也是上面这句 SQL。我这里直接在迁移里写原生 SQL,是因为 vector(1536) 是 pgvector 扩展类型,Prisma schema 里标了 Unsupported,没法走它自己的关系声明,所以手动在 migration 文件里加上去了。

REFERENCES

声明"这个字段的值,必须是另一个表的某个字段里已经存在的值"

sql 复制代码
FOREIGN KEY ("documentId") REFERENCES "Document"("id")

意思是:DocumentChunk.documentId 里填的值,必须在 Document.id 里找得到。如果你试图插入一个不存在的 documentId,数据库直接报错拒绝。

这叫 引用完整性(referential integrity)------保证数据不会指向一个不存在的东西。


ON DELETE ...

当被引用的行(Document)被删除时,关联的行(DocumentChunk)怎么办?有 4 种选项:

选项 行为 例子
CASCADE 跟着删 删文档 → 分块自动全删
SET NULL 把外键字段设成 NULL 删文档 → 分块还在,但 documentId 变 NULL
SET DEFAULT 设成默认值 删文档 → documentId 变成 DEFAULT 指定的值
RESTRICT / NO ACTION 禁止删除 删文档 → 数据库报错:"还有分块引用着它,不让删"

实际场景对比

复制代码
文档 → 删了
     ├── CASCADE    → 分块也删         ✅ 知识库删除
     ├── SET NULL   → 分块变孤儿记录   ⚠️ 极少用
     └── RESTRICT   → 删不了文档       ✅ 订单明细场景
                                                       
订单 → 想删
     └─ 还有订单明细引用它
        → RESTRICT  → "先删明细,再删订单"(防止误删)

所以 ON DELETE CASCADE 的选择逻辑是: 子记录离开父记录没有独立存在的意义 → 父删子也删。DocumentChunk 就是这种场景。

练兵场 🚀🏋️🏋️🏋️

1. 创建两张表

sql 复制代码
CREATE TABLE author (
  id   SERIAL PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE post (
  id        SERIAL PRIMARY KEY,
  title     TEXT NOT NULL,
  author_id INTEGER REFERENCES author(id) ON DELETE CASCADE
);

注意:REFERENCES 的完整写法是 FOREIGN KEY (author_id) REFERENCES author(id),字段在同一行时可以简写。


2. 插数据,看约束效果

sql 复制代码
INSERT INTO author (name) VALUES ('Alice'), ('Bob');
-- 2 行

INSERT INTO post (title, author_id) VALUES ('文章A', 1), ('文章B', 1), ('文章C', 2);
-- 3 行

现在查一下:

sql 复制代码
SELECT p.title, a.name 
FROM post p 
JOIN author a ON a.id = p.author_id;

结果应该是:

title name
文章A Alice
文章B Alice
文章C Bob

3. 试试引用违规

sql 复制代码
INSERT INTO post (title, author_id) VALUES ('文章D', 999);

报错: insert or update on table "post" violates foreign key constraint。因为 author_id=999 不存在。数据库在保护你------不会让你插入一个指向不存在作者的帖子。


4. CASCADE 生效

sql 复制代码
DELETE FROM author WHERE id = 1;
-- 删掉了 Alice

不用写 DELETE FROM post WHERE author_id = 1,PostgreSQL 自动把 Alice 的两篇文章也删了。

验证:

sql 复制代码
SELECT * FROM author;  -- 只剩 Bob
SELECT * FROM post;    -- 只剩 文章C(Bob 的)

5. 换成 RESTRICT 对比

sql 复制代码
-- 重建,换成 RESTRICT
DROP TABLE post, author;

CREATE TABLE author (
  id   SERIAL PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE post (
  id        SERIAL PRIMARY KEY,
  title     TEXT NOT NULL,
  author_id INTEGER REFERENCES author(id) ON DELETE RESTRICT
);

INSERT INTO author (name) VALUES ('Alice');
INSERT INTO post (title, author_id) VALUES ('文章A', 1);

现在试试删:

sql 复制代码
DELETE FROM author WHERE id = 1;

报错: 还有文章引用作者,不让删。你得先删文章,再删作者:

sql 复制代码
DELETE FROM post WHERE author_id = 1;
DELETE FROM author WHERE id = 1;  -- 这次可以

这就是 RESTRICT 的场景------订单明细还在,不许删订单。


6. SET NULL

sql 复制代码
CREATE TABLE post (
  id        SERIAL PRIMARY KEY,
  title     TEXT NOT NULL,
  author_id INTEGER REFERENCES author(id) ON DELETE SET NULL
);

-- 删掉作者 Alice
DELETE FROM author WHERE id = 1;

-- 文章还在,但 author_id 变成了 NULL
SELECT * FROM post;
id title author_id
1 文章A null

7. 回到你项目中的实际场景

sql 复制代码
ALTER TABLE "DocumentChunk" 
ADD CONSTRAINT "DocumentChunk_documentId_fkey" 
FOREIGN KEY ("documentId") 
REFERENCES "Document"("id") 
ON DELETE CASCADE;

整个链路的流程:

js 复制代码
你点删除 → fetch /api/documents?id=xxx,DELETE 方法
         → prisma.document.delete({ where: { id: xxx } })
         → Prisma 生成 DELETE FROM "Document" WHERE id = 'xxx'
         → PostgreSQL 执行 DELETE
         → 碰到 ON DELETE CASCADE,自动补一条:
           DELETE FROM "DocumentChunk" WHERE "documentId" = 'xxx'
         → 文档 + 所有分块,一行代码都不用写,全部清干净

相关推荐
不可求~7 小时前
多模型路由怎么落地:把任务分流写进项目的最小实现
java·前端·数据库
Lumistory8 小时前
城市建筑照明的落地与长效运营观察
大数据·数据库·中间件·光照贴图
围炉聊科技10 小时前
长期免费的数据库方案——零成本基建系列
数据库
AC赳赳老秦11 小时前
公开图片 OCR 数据提取:OpenClaw 合规采集公开信息图,识别文字与表格并转化为结构化数据
java·大数据·前端·数据库·python·php·openclaw
梁辰兴12 小时前
软件工程:数据结构设计
数据库·软件工程·设计原则·设计方法·梁辰兴·数据库结构设计·数据库结构类型
AC赳赳老秦13 小时前
风控岗应用:OpenClaw 采集公开司法与经营异常数据,自动生成企业风险评估报告
大数据·c语言·数据库·人工智能·python·php·openclaw
灯澜忆梦13 小时前
【MySQL18】进阶篇 | MySQL管理
数据库·mysql
zcmodeltech14 小时前
智能制造教学实训沙盘模型多系统协同控制系统设计:基于STM32与Modbus RTU的工业机器人-智慧工厂-数字孪生全场景联动方案
数据库·stm32·嵌入式硬件·机器人·制造·多分类
AI多Agent协作实战派14 小时前
AI多Agent协作系统实战(四十):AI说“没有错误“,系统判了“测试失败“——一个正则的误判
数据库·人工智能
隔窗听雨眠15 小时前
OceanBase接入DeepSeek:数据库与AI的深度融合如何改写企业数据规则
数据库·人工智能·oceanbase