跳至主要内容

引用动作

引用动作决定了当你的应用程序删除或更新相关记录时,记录将发生什么。

从 2.26.0 版本开始,你可以在 Prisma 模式中的关联字段上定义引用动作。这允许你在 Prisma ORM 级别定义诸如级联删除和级联更新之类的引用动作。

信息

版本差异

  • 如果你使用 3.0.1 或更高版本,则可以使用本页所述的引用动作。
  • 如果你使用 2.26.0 到 3.0.0 之间的版本,则可以使用本页所述的引用动作,但必须启用预览功能标志 referentialActions
  • 如果你使用 2.25.0 或更早版本,则可以在数据库中手动配置级联删除。

在以下示例中,在 Post 模型的 author 字段中添加 onDelete: Cascade 表示删除 User 记录也将删除所有相关的 Post 记录。

schema.prisma
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使用默认值

危险

如果你从 2.26.0 之前的版本升级:务必检查引用动作的升级路径 部分。Prisma ORM 对引用动作的支持**移除了 Prisma Client 中防止运行时级联删除的安全网**。如果你在*未升级数据库*的情况下使用此功能,则旧的默认操作 - ON DELETE CASCADE - 将变为活动状态。这可能导致你意想不到的级联删除。

什么是引用动作?

引用动作是策略,定义了在运行updatedelete 查询时,数据库如何处理引用的记录。

数据库级别的引用动作

引用动作是外键约束的功能,旨在维护数据库中的引用完整性。

当你在 Prisma 模式中定义数据模型之间的关系时,你使用关联字段,**它们不存在于数据库中**,以及标量字段,**它们存在于数据库中**。这些外键在数据库级别连接模型。

引用完整性规定这些外键必须引用相关数据库表中存在的现有主键值。在你的 Prisma 模式中,这通常由相关模型上的 id 字段表示。

默认情况下,数据库将拒绝任何违反引用完整性的操作,例如删除引用的记录。

如何使用引用动作

引用动作定义在@relation 属性中,并映射到底层数据库中**外键约束**上的操作。如果你没有指定引用动作,Prisma ORM 将回退到默认值

以下模型定义了 UserPost 之间的一对多关系,以及 PostTag 之间的多对多关系,并明确定义了引用动作

schema.prisma
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)。为了允许这样做,UseruserId 必须是 Post 中的可选字段。

Prisma ORM 支持以下引用动作

引用动作默认值

如果你没有指定引用动作,Prisma ORM 使用以下默认值

子句可选关联强制关联
onDeleteSetNullRestrict
onUpdateCascadeCascade

例如,在以下模式中,所有 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[]
}

该模式没有在强制 author 关联字段上明确定义引用动作,这意味着将应用 onDeleteRestrictonUpdateCascade 的默认引用动作。

注意事项

适用以下注意事项

  • 引用动作**不支持**隐式多对多关系。若要使用引用动作,必须定义显式多对多关系,并在连接表上定义你的引用动作。
  • 某些引用动作和必需/可选关联的组合不兼容。例如,在必需关联上使用 SetNull 将在删除引用的记录时导致数据库错误,因为将违反非空约束。有关更多信息,请参阅此 GitHub 问题

引用动作类型

下表显示每个数据库支持哪些引用动作。

数据库CascadeRestrictNoActionSetNullSetDefault
PostgreSQL✔️✔️✔️✔️⌘✔️
MySQL/MariaDB✔️✔️✔️✔️❌ (✔️†)
SQLite✔️✔️✔️✔️✔️
SQL Server✔️❌‡✔️✔️✔️
CockroachDB✔️✔️✔️✔️✔️
MongoDB††✔️✔️✔️✔️

引用动作的特殊情况

引用操作是 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 设置为数据库提供程序时,Prisma ORM 会警告用户在 Prisma 模式中用其他操作替换 SetDefault 引用操作。

PostgreSQL

PostgreSQL 是 Prisma ORM 支持的唯一允许您定义引用非空字段的 SetNull 引用操作的数据库。但是,当操作在运行时触发时,这会引发外键约束错误。

因此,当您在(默认)foreignKeys 关系模式中将 postgres 设置为数据库提供程序时,Prisma ORM 会警告用户将包含在具有 SetNull 引用操作的 @relation 属性中的任何字段标记为可选。对于所有其他数据库提供程序,Prisma ORM 会使用验证错误拒绝模式。

SQL Server

Restrict 不适用于 SQL Server 数据库,但您可以使用 NoAction 代替。

Cascade

  • onDelete: Cascade 删除引用的记录将触发引用记录的删除。
  • onUpdate: Cascade 如果被依赖记录的引用标量字段被更新,则更新关系标量字段。

示例用法

schema.prisma
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 阻止更改引用记录的标识符。

示例用法

schema.prisma
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 **无法**删除。Userid **无法**更改。

警告

Restrict 操作在 Microsoft SQL Server 上**不可用**,并会触发模式验证错误。相反,您可以使用 NoAction,它会产生相同的结果并且与 SQL Server 兼容。

NoAction

NoAction 操作类似于 Restrict,两者之间的区别取决于正在使用的数据库。

  • PostgreSQLNoAction 允许将检查(如果表上存在引用的行)延迟到事务的后期。有关更多信息,请参阅 PostgreSQL 文档
  • MySQLNoAction 的行为与 Restrict 完全相同。有关更多信息,请参阅 MySQL 文档
  • SQLite:当相关的主键被修改或删除时,不采取任何操作。有关更多信息,请参阅 SQLite 文档
  • SQL Server:当引用记录被删除或修改时,会引发错误。有关更多信息,请参阅 SQL Server 文档
  • MongoDB(从 3.6.0 版本开始预览):当记录被修改或删除时,不会对任何相关记录执行任何操作。
警告

如果您正在 使用 Prisma Client 管理关系 而不是在数据库中使用外键,则应注意,目前 Prisma ORM 仅实现引用操作。外键还会创建约束,这使得无法以违反这些约束的方式操作数据:数据库不会执行查询,而是会返回错误。如果您在 Prisma Client 中模拟引用完整性,则不会创建这些约束,因此,如果您将引用操作设置为 NoAction,则不会有任何检查来阻止您破坏引用完整性。

示例用法

schema.prisma
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 **无法**删除。Userid **无法**更改。

SetNull

  • onDelete: SetNull 引用对象的标量字段将设置为 NULL

  • onUpdate: SetNull 更新引用对象的标识符时,引用对象的标量字段将设置为 NULL

SetNull 仅适用于可选关系。在必需关系上,将抛出运行时错误,因为标量字段不能为 null。

schema.prisma
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

更改 Userid 时,其所有已发布帖子的 authorId 将设置为 NULL

SetDefault

  • onDelete: SetDefault 引用对象的标量字段将设置为字段的默认值。

  • onUpdate: SetDefault 引用对象的标量字段将设置为字段的默认值。

这些需要使用 @default 为关系标量字段设置默认值。如果任何标量字段都没有提供默认值,则会抛出运行时错误。

schema.prisma
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'。

Userusername 更改时,其现有帖子的 authorUsername 字段值将设置为 'anonymous'。

数据库特定要求

如果您在数据模型中具有 自关系循环关系,则 MongoDB 和 SQL Server 对引用操作有特定的要求。如果您具有具有 多个级联路径 的关系,则 SQL Server 也有特定的要求。

从 2.25.0 及更早版本升级路径

升级时您可以采取几种路径,具体结果取决于所需的成果。

如果您目前使用迁移工作流,则可以运行内省以检查默认值如何在您的模式中反映。如果需要,您可以手动更新数据库。

您还可以选择跳过检查默认值并运行迁移以使用 新的默认值 更新数据库。

以下假设您已升级到 2.26.0 或更高版本并启用了预览功能标志,或升级到 3.0.0 或更高版本。

使用内省

如果您内省您的数据库,则在数据库级别配置的引用操作将反映在您的 Prisma Schema 中。如果您一直使用 Prisma Migrate 或 prisma db push 来管理数据库模式,那么这些很可能是从 2.25.0 及更早版本开始的默认值

当您运行内省时,Prisma ORM 会将数据库中的所有外键与模式进行比较,如果 SQL 语句 ON DELETEON UPDATE 与默认值**不**匹配,则它们将在模式文件中显式设置。

内省后,您可以查看模式中的非默认子句。需要审查的最重要的子句是 onDelete,它在 2.25.0 及更早版本中默认为 Cascade

警告

如果您使用的是delete()deleteMany() 方法,则**级联删除 现在将执行**,因为 referentialActions 预览功能**删除了 Prisma Client 中之前阻止运行时级联删除的安全网**。请务必检查您的代码并进行相应的调整。

确保您对模式中每个 onDelete: Cascade 的情况都满意。如果不是,请

  • 修改您的 Prisma 模式并使用 db pushdev migrate 来更改数据库

  • 如果您使用仅内省的工作流,则手动更新底层数据库

以下示例将导致级联删除,如果删除了 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 db push 命令)时,将新的默认值应用于您的数据库。

信息

与您第一次运行内省时不同,新的引用操作子句和属性**不会**由 Prisma VSCode 扩展自动添加到您的 prisma 模式中。如果您希望使用除新默认值以外的任何内容,则必须手动添加它们。

在您的 Prisma 模式中显式定义引用操作是可选的。如果您没有为关系显式定义引用操作,则 Prisma ORM 将使用新的默认值

请注意,可以逐一添加引用操作。这意味着您可以将它们添加到单个关系中,而无需手动指定任何内容,从而使其余关系保持默认设置。

检查错误

**在**升级到 2.26.0 并启用引用操作**预览功能**之前,Prisma ORM 会在使用 delete()deleteMany() 删除记录时阻止删除记录以保持引用完整性。Prisma Client 将抛出自定义运行时错误,错误代码为 P2014

**在**升级并启用引用操作**预览功能**之后,Prisma ORM 不再执行运行时检查。您可以改为指定自定义引用操作以保持关系之间的引用完整性。

当您使用NoActionRestrict 阻止删除记录时,与 2.26.0 之前的版本相比,2.26.0 之后的错误消息将有所不同。这是因为它们现在是由数据库而不是 Prisma Client 触发的。可以预期的新的错误代码是 P2003

为了确保捕获这些新错误,您可以相应地调整代码。

捕获错误的示例

以下示例使用下面的博客模式,该模式在 PostUser 之间存在一对多关系,并在 author 字段上设置了Restrict 引用操作。

这意味着如果用户有帖子,则**无法**删除该用户(及其帖子)。

schema.prisma
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()