跳至主要内容

数据库连接

数据库可以处理有限数量的并发连接。每个连接都需要 RAM,这意味着仅仅增加数据库连接限制而没有扩展可用资源

  • ✔ 可能会允许更多进程连接 *但*
  • ✘ 会严重影响 **数据库性能**,并可能导致数据库由于内存不足错误而 **关闭**

您的应用程序 **管理连接** 的方式也会影响性能。本指南介绍了如何在 无服务器环境长时间运行的进程 中进行连接管理。

警告

本指南重点介绍 **关系型数据库** 以及如何配置和调整 Prisma ORM 连接池(MongoDB 使用 MongoDB 驱动程序连接池)。

长时间运行的进程

长时间运行的进程的示例包括托管在 Heroku 或虚拟机等服务上的 Node.js 应用程序。使用以下清单作为在长时间运行的环境中进行连接管理的指南

推荐的连接池大小 (connection_limit) 在长时间运行的进程中 开始使用 的是 **默认池大小** (num_physical_cpus * 2 + 1) ÷ **应用程序实例数量**。

信息

num_physical_cpus 指的是您的应用程序正在运行的机器的 CPU 数量。

如果您只有一个应用程序实例

  • 默认情况下将应用默认池大小 (num_physical_cpus * 2 + 1) - 您无需设置 connection_limit 参数。
  • 您可以选择 调整池大小

如果您有多个应用程序实例

  • 您必须 **手动** 设置 connection_limit 参数。例如,如果您的计算池大小为 *10* 并且您有两个应用程序实例,则 connection_limit 参数应 **不超过 *5***。
  • 您可以选择 调整池大小

PrismaClient 在长时间运行的应用程序中

在 **长时间运行** 的应用程序中,我们建议您

  • ✔ 创建 一个 PrismaClient 实例,并在整个应用程序中重复使用它
  • ✔ 仅在 *开发环境中* 将 PrismaClient 分配给全局变量,以 防止热重载创建新实例

重复使用单个 PrismaClient 实例

要重复使用单个实例,请创建一个导出 PrismaClient 对象的模块

client.ts
import { PrismaClient } from '@prisma/client'

let prisma = new PrismaClient()

export default prisma

该对象在模块第一次导入时被 缓存。后续请求返回缓存的对象,而不是创建新的 PrismaClient

app.ts
import prisma from './client'

async function main() {
const allUsers = await prisma.user.findMany()
}

main()

您不必完全复制上面的示例 - 目标是确保 PrismaClient 被缓存。例如,您可以 context 对象中实例化 PrismaClient,然后将其 传递到 Express 应用程序中

不要显式地 $disconnect()

在持续为请求提供服务的长时间运行的应用程序的上下文中,您 无需显式地 $disconnect()。如果在每次查询后断开连接,则打开新的连接会花费时间,并且会减慢应用程序的速度。

防止热重载创建新的 PrismaClient 实例

Next.js 这样的框架支持热重载已更改的文件,这使您可以看到应用程序的更改,而无需重新启动。但是,如果框架刷新了负责导出 PrismaClient 的模块,则会导致 **在开发环境中创建额外的、不需要的 PrismaClient 实例**。

作为解决方法,您可以将 PrismaClient 存储为仅在开发环境中的全局变量,因为全局变量不会被重新加载

client.ts
import { PrismaClient } from '@prisma/client'

const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }

export const prisma =
globalForPrisma.prisma || new PrismaClient()

if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma

导入和使用 Prisma 客户端的方式不会改变

app.ts
import { prisma } from './client'

async function main() {
const allUsers = await prisma.user.findMany()
}

main()

无服务器环境 (FaaS)

无服务器环境的示例包括托管在 AWS Lambda、Vercel 或 Netlify Functions 上的 Node.js 函数。使用以下清单作为在无服务器环境中进行连接管理的指南

无服务器挑战

在无服务器环境中,每个函数都会创建 PrismaClient自己的实例,并且每个客户端实例都有自己的连接池。

考虑以下示例,其中单个 AWS Lambda 函数使用 PrismaClient 连接到数据库。connection_limit 为 **3**

An AWS Lambda function connecting to a database.

流量激增会导致 AWS Lambda 生成两个额外的 lambda 来处理增加的负载。每个 lambda 都会创建一个 PrismaClient 实例,每个实例的 connection_limit 为 **3**,这会导致最多 **9** 个连接到数据库

Three AWS Lambda function connecting to a database.

200 个 *并发函数*(因此有 600 个可能的连接)响应流量激增 📈 可以非常快地耗尽数据库连接限制。此外,任何 暂停 的函数都会默认保持其连接打开,并阻止其他函数使用它们。

  1. 首先 connection_limit 设置为 1
  2. 如果较小的池大小不够,请考虑使用 像 PgBouncer 这样的外部连接池器

无服务器环境中推荐的池大小 (connection_limit) 取决于

没有外部连接池器

如果您没有使用外部连接池,请从将池大小 (connection_limit) 设置为1开始,然后优化。每个传入请求都会启动一个短暂的 Node.js 进程,并且许多具有高 connection_limit 的并发函数在流量高峰期间可能会迅速耗尽数据库连接限制

以下示例演示如何在连接 URL 中将 connection_limit 设置为 1

postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public&connection_limit=1
提示

如果您正在使用 AWS Lambda 且没有配置 connection_limit,请参阅以下 GitHub 问题以获取有关预期默认池大小的信息:https://github.com/prisma/docs/issues/667

使用外部连接池

如果您使用外部连接池,请使用默认池大小 (num_physical_cpus * 2 + 1) 作为起点,然后调整池大小。外部连接池应该能够防止流量高峰压垮数据库。

针对并行请求进行优化

如果您很少或从未超过将池大小设置为 1 时的数据库连接限制,则可以进一步优化连接池大小。考虑一个并行发送查询的函数

Promise.all() {
query1,
query2,
query3
query4,
...
}

如果 connection_limit 为 1,则该函数被迫串行(一个接一个)发送查询,而不是并行发送。这会降低函数处理请求的能力,并可能导致池超时错误。调整 connection_limit 参数,直到流量高峰

  • 不会耗尽数据库连接限制
  • 不会导致池超时错误

PrismaClient 在无服务器环境中

在处理程序外部实例化 PrismaClient

函数处理程序范围之外实例化 PrismaClient,以增加重用机会。只要处理程序保持“热身”(正在使用),连接就有可能被重用

import { PrismaClient } from '@prisma/client'

const client = new PrismaClient()

export async function handler() {
/* ... */
}

不要显式地 $disconnect()

无需在函数结束时显式地 $disconnect(),因为容器可能被重用。打开新连接需要时间,会降低函数处理请求的能力。

其他无服务器注意事项

容器重用

无法保证函数的后续附近调用会命中同一个容器 - 例如,AWS 可以随时选择创建新容器。

代码应该假定容器是无状态的,并且仅在不存在连接时才创建连接 - Prisma Client JS 已经实现了此逻辑。

僵尸连接

标记为“要删除”且未被重用的容器会保持连接打开,并且可能在该状态下停留一段时间(来自 AWS 的未知且未记录)。这会导致数据库连接利用率不佳。

一种可能的解决方案是清理空闲连接serverless-mysql实现了此想法,但不能与 Prisma ORM 一起使用)。

并发限制

根据您的无服务器并发限制(并行运行的无服务器函数数量),您可能仍然会耗尽数据库的连接限制。当太多函数并发调用时,每个函数都有自己的连接池,最终会耗尽数据库连接限制,就会发生这种情况。为了防止这种情况,您可以将无服务器并发限制设置为小于数据库最大连接限制除以每个函数调用使用的连接数(因为您可能希望能够从另一个客户端连接到数据库以执行其他操作)。

优化连接池

如果查询引擎无法在时间限制之前处理队列中的查询,您将在日志中看到连接池超时异常。如果出现以下情况,可能会发生连接池超时:

  • 许多用户同时访问您的应用程序
  • 您并行发送大量查询(例如,使用 await Promise.all()

如果您在配置推荐的池大小后仍然始终遇到连接池超时,则可以进一步调整 connection_limitpool_timeout 参数。

增加池大小

增加池大小允许查询引擎并行处理更多查询。请注意,您的数据库必须能够支持增加的并发连接数,否则您将耗尽数据库连接限制

要增加池大小,请手动将 connection_limit 设置为更大的数字

datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?schema=public&connection_limit=40"
}

注意:在无服务器环境中将 connection_limit 设置为 1 是一个推荐的起点,但也可以调整此值

增加池超时

增加池超时会给查询引擎更多时间来处理队列中的查询。您可能在以下情况下考虑这种方法:

  • 您已经增加了 connection_limit
  • 您确信队列不会超过一定大小,否则最终会导致内存不足

要增加池超时,请将 pool_timeout 参数设置为大于默认值(10 秒)的值

datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?connection_limit=5&pool_timeout=20"
}

禁用池超时

禁用池超时会阻止查询引擎在等待连接 x 秒后抛出异常,并允许队列堆积。您可能在以下情况下考虑这种方法:

  • 您在有限的时间内提交大量查询 - 例如,作为将数据库中的所有客户导入或更新的作业的一部分。
  • 您已经增加了 connection_limit
  • 您确信队列不会超过一定大小,否则最终会导致内存不足

要禁用池超时,请将 pool_timeout 参数设置为 0

datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?connection_limit=5&pool_timeout=0"
}

外部连接池

Prisma Accelerate 和 PgBouncer 这样的连接池可以防止您的应用程序耗尽数据库的连接限制。

如果您想使用 Prisma CLI 对数据库执行其他操作,例如迁移和内省,您需要在 Prisma 架构中添加一个环境变量,该环境变量在 datasource.directUrl 属性中提供到数据库的直接连接

.env
# Connection URL to your database using PgBouncer.
DATABASE_URL="postgres://root:[email protected]:54321/postgres?pgbouncer=true"

# Direct connection URL to the database used for migrations
DIRECT_URL="postgres://root:[email protected]:5432/postgres"

然后,您可以更新您的 schema.prisma 以使用新的直接 URL

schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}

有关 directUrl 字段的更多信息,请点击此处

Prisma Accelerate

Prisma Accelerate 是由 Prisma 构建的托管外部连接池,它集成在Prisma 数据平台 中,并为您处理连接池。

PgBouncer

PostgreSQL 只支持一定数量的并发连接,当服务使用量增加时,这个限制会很快达到 - 特别是在无服务器环境 中。

PgBouncer维护一个到数据库的连接池,并通过位于 Prisma Client 和数据库之间,代理传入的客户端连接。这减少了数据库在任何给定时间需要处理的进程数量。PgBouncer 向数据库传递有限数量的连接,并将其他连接排队,以便在连接可用时进行传递。要使用 PgBouncer,请参阅使用 PgBouncer 配置 Prisma Client

AWS RDS Proxy

由于 AWS RDS Proxy 固定的连接方式,它在与 Prisma Client 一起使用时不提供任何连接池益处