引用操作
引用操作决定了当您的应用程序删除或更新关联记录时,该记录会发生什么。
从 2.26.0 版本开始,您可以在 Prisma schema 中的关系字段上定义引用操作。这使您能够在 Prisma ORM 级别定义引用操作,例如级联删除和级联更新。
版本差异
- 如果您使用 3.0.1 或更高版本,则可以按照此页面上的描述使用引用操作。
- 如果您使用的版本介于 2.26.0 和 3.0.0 之间,则可以按照此页面上的描述使用引用操作,但必须启用预览功能标志
referentialActions
。 - 如果您使用 2.25.0 或更早版本,则可以在数据库中手动配置级联删除。
在以下示例中,将 onDelete: Cascade
添加到 Post
模型上的 author
字段意味着删除 User
记录也将删除所有相关的 Post
记录。
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
如果您未指定引用操作,Prisma ORM 将使用默认设置。
什么是引用操作?
引用操作是定义当您运行update
或delete
查询时,数据库如何处理被引用记录的策略。
数据库级别的引用操作
如何使用引用操作
引用操作在@relation
属性中定义,并映射到底层数据库中外键约束上的操作。如果您未指定引用操作,Prisma ORM 将退回到默认设置。
以下模型定义了 User
和 Post
之间的一对多关系,以及 Post
和 Tag
之间的多对多关系,并显式定义了引用操作
model User {
id Int @id @default(autoincrement())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
tags TagOnPosts[]
User User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
userId Int?
}
model TagOnPosts {
id Int @id @default(autoincrement())
post Post? @relation(fields: [postId], references: [id], onUpdate: Cascade, onDelete: Cascade)
tag Tag? @relation(fields: [tagId], references: [id], onUpdate: Cascade, onDelete: Cascade)
postId Int?
tagId Int?
}
model Tag {
id Int @id @default(autoincrement())
name String @unique
posts TagOnPosts[]
}
此模型显式定义了以下引用操作
- 如果您删除一个
Tag
,则在使用Cascade
引用操作的情况下,TagOnPosts
中对应的标签分配也将被删除 - 如果您删除一个
User
,由于SetNull
引用操作,通过将字段值设置为Null
,作者将从所有帖子中删除。为了允许这样做,Post
中的User
和userId
必须是可选字段。
Prisma ORM 支持以下引用操作
引用操作默认设置
如果您未指定引用操作,Prisma ORM 将使用以下默认设置
子句 | 可选关系 | 强制关系 |
---|---|---|
onDelete | SetNull (设空) | Restrict (限制) |
onUpdate | Cascade (级联) | Cascade (级联) |
例如,在以下 schema 中,所有 Post
记录都必须通过 author
关系连接到 User
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
schema 没有在强制性的 author
关系字段上显式定义引用操作,这意味着默认的 onDelete
的 Restrict
和 onUpdate
的 Cascade
引用操作将生效。
注意事项
以下注意事项适用
- 引用操作不支持隐式多对多关系。要使用引用操作,您必须定义显式多对多关系并在连接表上定义引用操作。
- 引用操作和必需/可选关系的某些组合是不兼容的。例如,在必需关系上使用
SetNull
在删除被引用记录时会导致数据库错误,因为这将违反非空约束。有关更多信息,请参阅此 GitHub issue。
引用操作的类型
下表显示了每个数据库支持哪些引用操作。
数据库 | Cascade (级联) | Restrict (限制) | NoAction (不操作) | SetNull (设空) | SetDefault (设默认值) |
---|---|---|---|---|---|
PostgreSQL | ✔️ | ✔️ | ✔️ | ✔️⌘ | ✔️ |
MySQL/MariaDB | ✔️ | ✔️ | ✔️ | ✔️ | ❌ (✔️†) |
SQLite | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
SQL Server | ✔️ | ❌‡ | ✔️ | ✔️ | ✔️ |
CockroachDB | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
MongoDB†† | ✔️ | ✔️ | ✔️ | ✔️ | ❌ |
- † 请参阅MySQL 的特殊情况。
- ⌘ 请参阅PostgreSQL 的特殊情况。
- ‡ 请参阅SQL Server 的特殊情况。
- †† MongoDB 的引用操作在 Prisma ORM 3.7.0 及更高版本中可用。
引用操作的特殊情况
引用操作是 ANSI SQL 标准的一部分。但是,在某些情况下,一些关系型数据库会偏离该标准。
MySQL/MariaDB
MySQL/MariaDB 以及底层的 InnoDB 存储引擎不支持 SetDefault
。具体行为取决于数据库版本
- 在 MySQL 8 及更高版本以及 MariaDB 10.5 及更高版本中,
SetDefault
实际上是NoAction
的别名。您可以使用SET DEFAULT
引用操作定义表,但在运行时会触发外键约束错误。 - 在 MySQL 5.6 及更高版本以及 MariaDB 10.5 之前版本中,尝试使用
SET DEFAULT
引用操作创建表定义会因语法错误而失败。
因此,当您将 mysql
设置为数据库 provider 时,Prisma ORM 会警告用户在 Prisma schema 中用其他操作替换 SetDefault
引用操作。
PostgreSQL
PostgreSQL 是 Prisma ORM 支持的唯一允许您定义引用非空字段的 SetNull
引用操作的数据库。但是,当此操作在运行时触发时,会引发外键约束错误。
因此,在(默认的)foreignKeys
关系模式下将 postgres
设置为数据库 provider 时,Prisma ORM 会警告用户将任何包含在具有 SetNull
引用操作的 @relation
属性中的字段标记为可选。对于所有其他数据库 provider,Prisma ORM 会因验证错误而拒绝 schema。
SQL Server
Restrict
不适用于 SQL Server 数据库,但您可以使用NoAction
代替。
Cascade (级联)
onDelete: Cascade
删除被引用记录将触发引用记录的删除。onUpdate: Cascade
如果依赖记录的被引用标量字段被更新,则更新关系标量字段。
示例用法
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade, onUpdate: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 Cascade 的结果
如果删除了一个 User
记录,则他们的帖子也会被删除。如果用户的 id
被更新,则相应的 authorId
也会被更新。
如何使用级联删除
Restrict (限制)
onDelete: Restrict
如果存在任何引用记录,则阻止删除。onUpdate: Restrict
阻止被引用记录的标识符被更改。
示例用法
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Restrict, onUpdate: Restrict)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 Restrict 的结果
有帖子的 User
无法删除。User
的 id
无法更改。
Restrict
操作在Microsoft SQL Server 上不可用,并会触发 schema 验证错误。相反,您可以使用NoAction
,它会产生相同的结果,并与 SQL Server 兼容。
NoAction (不操作)
NoAction
操作与 Restrict
类似,两者的区别取决于所使用的数据库
- PostgreSQL:
NoAction
允许将检查(是否存在表中被引用行)延迟到事务后期。有关更多信息,请参阅PostgreSQL 文档。 - MySQL:
NoAction
的行为与Restrict
完全相同。有关更多信息,请参阅MySQL 文档。 - SQLite: 当相关主键被修改或删除时,不执行任何操作。有关更多信息,请参阅SQLite 文档。
- SQL Server: 当被引用记录被删除或修改时,会引发错误。有关更多信息,请参阅SQL Server 文档。
- MongoDB(3.6.0 版本起预览):当记录被修改或删除时,不会对任何相关记录执行任何操作。
如果您在Prisma Client 中管理关系,而不是在数据库中使用外键,您应该了解目前 Prisma ORM 只实现了引用操作。外键还会创建约束,使得无法以违反这些约束的方式操作数据:数据库不会执行查询,而是响应一个错误。如果您在 Prisma Client 中模拟引用完整性,这些约束将不会被创建,因此如果您将引用操作设置为 NoAction
,将没有任何检查来阻止您破坏引用完整性。
示例用法
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: NoAction, onUpdate: NoAction)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 NoAction 的结果
有帖子的 User
无法删除。User
的 id
无法更改。
SetNull (设空)
-
onDelete: SetNull
引用对象的标量字段将被设置为NULL
。 -
onUpdate: SetNull
更新被引用对象的标识符时,引用对象的标量字段将被设置为NULL
。
SetNull
仅适用于可选关系。在必需关系上,由于标量字段不能为 null,将抛出运行时错误。
model Post {
id Int @id @default(autoincrement())
title String
author User? @relation(fields: [authorId], references: [id], onDelete: SetNull, onUpdate: SetNull)
authorId Int?
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 SetNull 的结果
删除 User
时,其所有撰写的帖子的 authorId
将被设置为 NULL
。
更改 User
的 id
时,其所有撰写的帖子的 authorId
将被设置为 NULL
。
SetDefault (设默认值)
-
onDelete: SetDefault
引用对象的标量字段将被设置为该字段的默认值。 -
onUpdate: SetDefault
引用对象的标量字段将被设置为该字段的默认值。
这些操作要求使用@default
为关系标量字段设置默认值。如果未为任何标量字段提供默认值,将抛出运行时错误。
model Post {
id Int @id @default(autoincrement())
title String
authorUsername String? @default("anonymous")
author User? @relation(fields: [authorUsername], references: [username], onDelete: SetDefault, onUpdate: SetDefault)
}
model User {
username String @id
posts Post[]
}
使用 SetDefault 的结果
删除 User
时,其现有帖子的 authorUsername
字段值将被设置为 'anonymous'。
更改 User
的 username
时,其现有帖子的 authorUsername
字段值将被设置为 'anonymous'。
数据库特定要求
如果您的数据模型中存在自引用关系或循环关系,则 MongoDB 和 SQL Server 对引用操作有特定要求。如果您的关系存在多个级联路径,则 SQL Server 也有特定要求。
从 2.25.0 及更早版本的升级路径
升级时有几种路径可供选择,它们会根据期望的结果产生不同的结果。
如果您当前使用 migration 工作流程,可以运行 Introspection 来检查默认设置如何在您的 schema 中体现。如果需要,您可以手动更新数据库。
您也可以选择跳过检查默认设置,直接运行 migration 来使用新的默认值更新数据库。
以下假设您已升级到 2.26.0 或更高版本并启用了预览功能标志,或者已升级到 3.0.0 或更高版本
使用 Introspection (自省)
如果您对数据库进行自省 (Introspect),数据库级别配置的引用操作将反映在您的 Prisma Schema 中。如果您一直使用 Prisma Migrate 或 prisma db push
来管理数据库 schema,这些很可能是 2.25.0 及更早版本中的默认值。
当您运行 Introspection 时,Prisma ORM 会将数据库中的所有外键与 schema 进行比较,如果 SQL 语句 ON DELETE
和 ON UPDATE
不匹配默认值,它们将在 schema 文件中显式设置。
完成自省后,您可以查看 schema 中的非默认子句。最重要的是查看 onDelete
子句,在 2.25.0 及更早版本中,其默认值为 Cascade
。
如果您正在使用 delete()
或 deleteMany()
方法,现在将执行级联删除,因为 referentialActions
预览功能移除了 Prisma Client 中先前阻止运行时级联删除的安全网。务必检查您的代码并进行相应的调整。
请确保您对 schema 中所有 onDelete: Cascade
的情况感到满意。如果不是,请选择以下任一方式:
- 修改您的 Prisma schema 并运行
db push
或dev migrate
来更改数据库
或者
- 如果您使用仅 Introspection 的工作流程,则手动更新底层数据库
以下示例将导致级联删除:如果 User
被删除,则其所有 Post
也将被删除。
一个博客 schema 示例
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Cascade)
authorId Int
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
使用 Migration (迁移)
当运行 Migration (迁移)(或 prisma db push
命令)时,新的默认设置将应用于您的数据库。
与首次运行 Introspect 不同,新的引用操作子句和属性不会由 Prisma VSCode 扩展自动添加到您的 prisma schema 中。如果您希望使用除新默认设置之外的任何设置,则必须手动添加它们。
在您的 Prisma schema 中显式定义引用操作是可选的。如果您未显式定义关系引用操作,Prisma ORM 将使用新的默认设置。
请注意,引用操作可以逐个关系添加。这意味着您可以将它们添加到单个关系中,而其余关系则通过不手动指定任何内容来保留默认设置。
检查错误
在升级到 2.26.0 并启用引用操作预览功能之前,Prisma ORM 在使用 delete()
或 deleteMany()
时会阻止记录删除,以维护引用完整性。Prisma Client 会抛出一个自定义运行时错误,错误代码为 P2014
。
升级并启用引用操作预览功能后,Prisma ORM 不再执行运行时检查。您可以改为指定自定义引用操作来维护关系间的引用完整性。
当您使用 NoAction
或 Restrict
阻止记录删除时,2.26.0 版本后的错误消息与 2.26.0 版本前不同。这是因为它们现在由数据库触发,而不是 Prisma Client。新的预期错误代码是 P2003
。
为确保您能捕获这些新错误,请相应地调整您的代码。
捕获错误的示例
以下示例使用了下方博客 schema,其中 Post
和 User
之间存在一对多关系,并在 author
字段上设置了 Restrict
引用操作。
这意味着如果一个用户有帖子,该用户(及其帖子)无法删除。
model Post {
id Int @id @default(autoincrement())
title String
author User @relation(fields: [authorId], references: [id], onDelete: Restrict)
authorId String
}
model User {
id Int @id @default(autoincrement())
posts Post[]
}
在升级并启用引用操作预览功能之前,尝试删除有帖子的用户时会收到的错误代码是 P2014
,其消息为
“您尝试进行的更改会违反 {model_a_name} 和 {model_b_name} 模型之间必需的关系 '{relation_name}'。”
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
try {
await prisma.user.delete({
where: {
id: 'some-long-id',
},
})
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2014') {
console.log(error.message)
}
}
}
}
main()
为确保您的代码检查正确的错误,请修改您的检查以查找 P2003
,它将返回消息
“外键约束在字段上失败: {field_name}”
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
try {
await prisma.user.delete({
where: {
id: 'some-long-id'
}
})
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2014') {
if (error.code === 'P2003') {
console.log(error.message)
}
}
}
}
main()