跳到主要内容

MongoDB

本指南讨论了使用 Prisma ORM 和 MongoDB 背后的概念,解释了 MongoDB 与其他数据库提供商之间的共性和差异,并引导您完成配置应用程序以使用 Prisma ORM 与 MongoDB 集成的过程。

信息

要将 Prisma ORM 与 MongoDB 连接,请参阅我们的快速入门文档

什么是 MongoDB?

MongoDB 是一个 NoSQL 数据库,它以 BSON 格式(一种类似 JSON 的文档格式,专为存储键值对数据而设计)存储数据。 它通常用于 JavaScript 应用程序开发,因为文档模型很容易映射到应用程序代码中的对象,并且内置支持高可用性和水平扩展。

MongoDB 将数据存储在集合中,这些集合不需要像关系数据库中的表那样预先定义模式。 每个集合的结构也可以随着时间的推移而更改。 这种灵活性可以实现数据模型的快速迭代,但也意味着在使用 Prisma ORM 处理 MongoDB 数据库时,存在许多差异。

与其他数据库提供商的共性

将 Prisma ORM 与 MongoDB 一起使用的一些方面与将 Prisma ORM 与关系数据库一起使用时相同。 您仍然可以

  • 使用 Prisma Schema Language 对数据库进行建模
  • 使用 mongodb 数据库连接器 连接到您的数据库
  • 如果您已经拥有 MongoDB 数据库,则可以为现有项目使用 内省
  • 使用 db push 将模式中的更改推送到数据库
  • 在您的应用程序中使用 Prisma Client,以基于您的 Prisma Schema 以类型安全的方式查询您的数据库

需要考虑的差异

MongoDB 基于文档的结构和灵活的模式意味着将 Prisma ORM 与 MongoDB 一起使用在许多方面与将其与关系数据库一起使用不同。 以下是一些您需要注意的差异领域

  • 定义 ID:MongoDB 文档具有 _id 字段(通常包含 ObjectID)。 Prisma ORM 不支持以 _ 开头的字段,因此需要使用 @map 属性将其映射到 Prisma ORM 字段。 有关更多信息,请参阅 在 MongoDB 中定义 ID

  • 迁移现有数据以匹配您的 Prisma 模式:在关系数据库中,您的所有数据都必须与您的模式匹配。 如果您在迁移时更改模式中特定字段的类型,则还必须更新所有数据以匹配。 相比之下,MongoDB 不强制执行任何特定模式,因此在迁移时需要小心。 有关更多信息,请参阅 如何迁移旧数据以适应新模式

  • 内省和 Prisma ORM 关系:当您内省现有的 MongoDB 数据库时,您将获得一个没有关系的模式,并且需要手动添加缺失的关系。 有关更多信息,请参阅 如何在内省后添加缺失的关系

  • 筛选 null 和缺失字段:MongoDB 区分将字段设置为 null 和根本不设置字段,这在关系数据库中不存在。 Prisma ORM 目前没有表达这种区别,这意味着在筛选 null 和缺失字段时需要小心。 有关更多信息,请参阅 如何筛选 null 和缺失字段

  • 启用复制:Prisma ORM 内部使用 MongoDB 事务 以避免嵌套查询的部分写入。 使用事务时,MongoDB 要求启用数据集的复制。 为此,您需要配置一个 副本集 — 这是一个维护相同数据集的 MongoDB 进程组。 请注意,通过创建一个仅包含一个节点的副本集,仍然可以使用单个数据库。 如果您使用 MongoDB 的 Atlas 托管服务,则副本集已为您配置,但如果您在本地运行 MongoDB,则需要自行设置副本集。 有关更多信息,请参阅 MongoDB 的 部署副本集指南

大型集合的性能注意事项

问题

当通过 Prisma 处理大型 MongoDB 集合时,某些操作可能会变得缓慢且资源密集。 特别是,需要扫描整个集合的操作(例如 count())可能会达到查询执行时间限制,并随着数据集的增长而显着影响性能。

解决方案

为了解决大型 MongoDB 集合的性能问题,请考虑以下方法

  1. 对于大型集合,请考虑使用 MongoDB 的 estimatedDocumentCount() 而不是 count()。 此方法速度更快,因为它使用有关集合的元数据。 您可以使用 Prisma 的 runCommandRaw 方法来执行此命令。

  2. 对于经常访问的计数,请考虑实现计数器缓存。 这涉及维护一个单独的文档,其中包含预先计算的计数,并在添加或删除文档时更新该计数。

如何将 Prisma ORM 与 MongoDB 一起使用

本节提供了有关如何执行需要 MongoDB 特定步骤的任务的说明。

如何迁移现有数据以匹配您的 Prisma 模式

随着时间的推移迁移数据库是开发周期的重要组成部分。 在开发过程中,您将需要更新您的 Prisma 模式(例如,添加新字段),然后更新开发环境数据库中的数据,并最终将更新后的模式和新数据推送到生产数据库。

信息

使用 MongoDB 时,请注意您的模式和数据库之间的“耦合”旨在比 SQL 数据库更宽松; MongoDB 不会强制执行模式,因此您必须验证数据完整性。

这些更新模式和数据库的迭代任务可能会导致您的模式与数据库中的实际数据之间出现不一致。 让我们看一个可能发生这种情况的场景,然后检查您和您的团队可以考虑用于处理这些不一致的几种策略。

场景:您需要为用户包括电话号码以及电子邮件。 您当前在 schema.prisma 文件中具有以下 User 模型

prisma/schema.prisma
model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
}

您可以使用多种策略来迁移此模式

  • “按需”更新:使用此策略,您和您的团队已同意可以根据需要对模式进行更新。 但是,为了避免由于数据和模式之间的不一致而导致迁移失败,团队内部达成协议,任何添加的新字段都显式定义为可选。

    在上面的场景中,您可以将可选的 phoneNumber 字段添加到 Prisma 模式中的 User 模型

    prisma/schema.prisma
    model User {
    id String @id @default(auto()) @map("_id") @db.ObjectId
    email String
    phoneNumber String?
    }

    然后使用 npx prisma generate 命令重新生成您的 Prisma Client。 接下来,更新您的应用程序以反映新字段,并重新部署您的应用程序。

    由于 phoneNumber 字段是可选的,您仍然可以查询未定义电话号码的旧用户。 数据库中的记录将随着应用程序用户开始在新字段中输入其电话号码而“按需”更新。

    另一种选择是在必填字段上添加默认值,例如

    prisma/schema.prisma
    model User {
    id String @id @default(auto()) @map("_id") @db.ObjectId
    email String
    phoneNumber String @default("000-000-0000")
    }

    然后,当您遇到缺少的 phoneNumber 时,该值将被强制转换为 000-000-0000

  • “无重大更改”更新:此策略建立在第一个策略的基础上,您的团队进一步达成共识,即您不重命名或删除字段,仅添加新字段,并且始终将新字段定义为可选。 可以通过在 CI/CD 过程中添加检查来验证模式是否没有向后不兼容的更改,从而加强此策略。

  • “一次性”更新:此策略类似于关系数据库中的传统迁移,其中所有数据都会更新以反映新模式。 在上面的场景中,您将创建一个脚本,以将电话号码字段的值添加到数据库中的所有现有用户。 然后,您可以使该字段成为应用程序中的必填字段,因为模式和数据是一致的。

如何在内省后添加缺失的关系

在内省现有 MongoDB 数据库之后,您将需要手动添加模型之间的关系。 MongoDB 没有通过外键定义关系的概念,就像在关系数据库中一样。 但是,如果您的 MongoDB 中有一个集合,其中包含一个“类似外键”的字段,该字段与另一个集合的 ID 字段匹配,则 Prisma ORM 将允许您模拟集合之间的关系。

例如,采用一个包含两个集合 UserPost 的 MongoDB 数据库。 这些集合中的数据格式如下,其中 userId 字段将用户链接到帖子

User 集合

  • _id 字段,类型为 objectId
  • email 字段,类型为 string

Post 集合

  • _id 字段,类型为 objectId
  • title 字段,类型为 string
  • userId,类型为 objectID

使用 db pull 进行内省时,这将拉入 Prisma Schema,如下所示

prisma/schema.prisma
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
userId String @db.ObjectId
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
}

这缺少 UserPost 模型之间的关系。 要修复此问题,请手动向 Post 模型添加一个 user 字段,并使用 @relation 属性,使用 userId 作为 fields 值,将其链接到 User 模型,并向 User 模型添加一个 posts 字段作为反向关系

prisma/schema.prisma
model Post {
id String @id @default(auto()) @map("_id") @db.ObjectId
title String
userId String @db.ObjectId
user User @relation(fields: [userId], references: [id])
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
posts Post[]
}

有关如何在 Prisma ORM 中使用关系的更多信息,请参阅我们的文档

如何筛选 null 和缺失字段

要了解 MongoDB 如何区分 null 和缺失字段,请考虑一个具有可选 name 字段的 User 模型的示例

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String
name String?
}

首先,尝试创建一个记录,其中 name 字段显式设置为 null。 Prisma ORM 将按预期返回 name: null

const createNull = await prisma.user.create({
data: {
email: '[email protected]',
name: null,
},
})
console.log(createNull)
显示CLI结果
{
id: '6242c4ae032bc76da250b207',
email: '[email protected]',
name: null
}

如果您直接检查 MongoDB 数据库,您还将看到一条新记录,其中 name 设置为 null

{
"_id": "6242c4af032bc76da250b207",
"email": "[email protected]",
"name": null
}

接下来,尝试创建一个记录,而不显式设置 name 字段

const createMissing = await prisma.user.create({
data: {
email: '[email protected]',
},
})
console.log(createMissing)
显示CLI结果
{
id: '6242c4ae032bc76da250b208',
email: '[email protected]',
name: null
}

Prisma ORM 仍然返回 name: null,但如果您直接查看数据库,您将看到该记录根本没有定义 name 字段

{
"_id": "6242c4af032bc76da250b208",
"email": "[email protected]"
}

Prisma ORM 在这两种情况下都返回相同的结果,因为我们目前无法在 MongoDB 中指定 null 字段和根本未定义的字段之间的区别 — 请参阅 此 Github 问题 以获取更多信息。

这意味着您目前在筛选 null 和缺失字段时必须小心。 筛选 name: null 的记录将仅返回第一条记录,其中 name 显式设置为 null

const findNulls = await prisma.user.findMany({
where: {
name: null,
},
})
console.log(findNulls)
显示CLI结果
[
{
id: '6242c4ae032bc76da250b207',
email: '[email protected]',
name: null
}
]

这是因为 name: null 正在检查相等性,而不存在的字段不等于 null

要同时包含缺失字段,请使用 isSet 过滤器 显式搜索 null 或未设置的字段。 这将返回两条记录

const findNullOrMissing = await prisma.user.findMany({
where: {
OR: [
{
name: null,
},
{
name: {
isSet: false,
},
},
],
},
})
console.log(findNullOrMissing)
显示CLI结果
[
{
id: '6242c4ae032bc76da250b207',
email: '[email protected]',
name: null
},
{
id: '6242c4ae032bc76da250b208',
email: '[email protected]',
name: null
}
]

有关将 MongoDB 与 Prisma ORM 一起使用的更多信息

开始将 MongoDB 与 Prisma ORM 一起使用的最快方法是参考我们的快速入门文档

这些教程将引导您完成连接到 MongoDB、推送模式更改以及使用 Prisma Client 的过程。

更多参考信息可在 MongoDB 连接器文档中找到。

有关如何设置和管理 MongoDB 数据库的更多信息,请参阅 Prisma 数据指南

示例

要连接到 MongoDB 服务器,请在您的 Prisma Schema 中配置 datasource

schema.prisma
datasource db {
provider = "mongodb"
url = env("DATABASE_URL")
}

传递给 datasource 块的字段是

  • provider:指定 mongodb 数据源连接器。
  • url:指定 MongoDB 服务器的连接 URL。 在这种情况下,使用环境变量来提供连接 URL。
警告

MongoDB 数据库连接器使用事务来支持嵌套写入。 事务需要副本集部署。 部署副本集的最简单方法是使用Atlas。 免费开始使用。

连接详细信息

连接 URL

MongoDB 连接 URL 可以通过不同的方式配置,具体取决于您托管数据库的方式。 标准配置由以下组件组成

Structure of the MongoDB connection URL

基本 URL 和路径

连接 URL 的基本 URL 和路径部分由您的身份验证凭据、主机(以及可选的端口号)和数据库组成。

mongodb://USERNAME:PASSWORD@HOST/DATABASE

以下组件构成数据库的基本 URL

名称占位符描述
用户USERNAME您的数据库用户的名称,例如 janedoe
密码PASSWORD您的数据库用户的密码
主机HOST运行 mongod 实例的主机。 如果您正在运行分片集群,这将是 mongos 实例。 这可以是主机名、IP 地址或 UNIX 域套接字。
端口PORT您的数据库服务器正在运行的端口,例如 1234。 如果未提供,则使用默认值 27017
数据库DATABASE要使用的数据库的名称。 如果未指定,但设置了 authSource 选项,则使用 authSource 数据库名称。 如果连接字符串中的数据库和 authSource 选项均未指定,则默认为 admin
信息

参数

连接 URL 也可以接受参数。 以下示例设置了三个参数

  • ssl 连接
  • connectTimeoutMS
  • maxPoolSize
mongodb://USERNAME:PASSWORD@HOST/DATABASE?ssl=true&connectTimeoutMS=5000&maxPoolSize=50

有关连接字符串参数的完整列表,请参阅 MongoDB 连接字符串文档。 没有 Prisma ORM 特定的参数。

使用 ObjectId

MongoDB 文档的 _id 字段包含 ObjectId 是一种常见的做法

{
"_id": { "$oid": "60d599cb001ef98000f2cad2" },
"createdAt": { "$date": { "$numberLong": "1624611275577" } },
"email": "[email protected]",
"name": "Ella",
"role": "ADMIN"
}

映射到底层数据库中 ObjectId 的任何字段(最常见的是 ID 和关系标量字段)

  • 必须是 StringBytes 类型
  • 必须包含 @db.ObjectId 属性
  • 可以选择使用 @default(auto()) 在文档创建时自动生成有效的 ObjectId

这是一个使用 String 的示例

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
// Other fields
}

这是另一个使用 Bytes 的示例

model User {
id Bytes @id @default(auto()) @map("_id") @db.ObjectId
// Other fields
}

另请参阅:在 MongoDB 中定义 ID 字段

生成 ObjectId

要在您的应用程序中生成有效的 ObjectId(用于测试目的或手动设置 ID 字段值),请使用 bson 包。

npm install --save bson
import { ObjectId } from 'bson'

const id = new ObjectId()

与关系数据库连接器的差异

本节介绍 MongoDB 连接器与 Prisma ORM 关系数据库连接器的不同之处。

不支持 Prisma Migrate

目前,没有计划添加对 Prisma Migrate 的支持,因为 MongoDB 项目不依赖于内部模式,在这些模式中,更改需要使用额外的工具进行管理。 @unique 索引的管理通过 db push 实现。

不支持 @@idautoincrement()

不支持 @@id 属性(多个字段的 ID),因为 MongoDB 中的主键始终位于模型的 _id 字段上。

不支持 autoincrement() 函数(创建递增的 @id 值),因为 autoincrement() 不适用于 MongoDB 中 _id 字段具有的 ObjectID 类型。

循环引用和参照操作

如果您的模型中存在循环引用(无论是来自自引用还是模型之间关系的循环),并且您使用 参照操作,则必须设置 NoAction 的参照操作以防止操作的无限循环。

有关更多详细信息,请参阅 参照操作的特殊规则

副本集配置

MongoDB 只允许您在副本集上启动事务。 Prisma ORM 在内部使用事务来避免嵌套查询的部分写入。 这意味着我们继承了需要配置副本集的要求。

当您尝试在未配置副本集的部署上使用 Prisma ORM 的 MongoDB 连接器时,Prisma ORM 会显示消息 Error: Transactions are not supported by this deployment。 错误消息的完整文本如下

PrismaClientUnknownRequestError2 [PrismaClientUnknownRequestError]:
Invalid `prisma.post.create()` invocation in
/index.ts:9:21

6 await prisma.$connect()
7
8 // Create the first post
→ 9 await prisma.post.create(
Error in connector: Database error. error code: unknown, error message: Transactions are not supported by this deployment
at cb (/node_modules/@prisma/client/runtime/index.js:34804:17)
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
clientVersion: '3.xx.0'
}

为了解决这个问题,我们建议您将您的部署更改为配置了副本集的部署。

一种简单的方法是使用 MongoDB Atlas 启动一个免费实例,该实例开箱即用地支持副本集。

还有一种选择可以在本地运行副本集,请参考此指南:https://mongodb.ac.cn/docs/manual/tutorial/convert-standalone-to-replica-set

MongoDB 和 Prisma schema 之间的类型映射

MongoDB 连接器将 Prisma ORM 数据模型中的标量类型映射到 MongoDB 的本机列类型,如下所示

或者,请参阅Prisma schema 参考,了解按 Prisma 类型组织的类型映射。

从 Prisma ORM 到 MongoDB 的本机类型映射

Prisma ORMMongoDB
Stringstring
Booleanbool
Intint
BigIntlong
Floatdouble
Decimal当前不支持
DateTimetimestamp
BytesbinData
Json

当前不支持的 MongoDB 类型

  • Decimal128
  • Undefined
  • DBPointer
  • Null
  • Symbol
  • MinKey
  • MaxKey
  • Object
  • Javascript
  • JavascriptWithScope
  • Regex

从 MongoDB 到 Prisma ORM 类型的内省映射

在内省 MongoDB 数据库时,Prisma ORM 使用相关的标量类型。 一些特殊类型还会获得额外的本机类型注释

MongoDB(类型 | 别名)Prisma ORM支持本机数据库类型属性注释
objectIdString✔️@db.ObjectId

内省添加了尚不支持的本机数据库类型,作为Unsupported 字段

schema.prisma
model Example {
id String @id @default(auto()) @map("_id") @db.ObjectId
name String
regex Unsupported("RegularExpression")
}