跳到主要内容

数据库连接

数据库可以处理的并发连接数有限。每个连接都需要 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 参数。
  • 您可以选择调整池大小

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

长时间运行的应用程序中的 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 Client 的方式不会改变

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_limit3

An AWS Lambda function connecting to a database.

流量高峰导致 AWS Lambda 生成两个额外的 Lambda 来处理增加的负载。每个 Lambda 都创建一个 PrismaClient 实例,每个实例的 connection_limit3,从而导致最多 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
  • 您确信队列不会增长到超过某个大小,否则您最终将耗尽 RAM

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

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

禁用池超时

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

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

要禁用池超时,请将 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 schema 的 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 Data Platform 中,并为您处理连接池。

PgBouncer

PostgreSQL 仅支持一定数量的并发连接,并且当服务使用量增加时,尤其是在 无服务器环境 中,此限制可能会很快达到。

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

AWS RDS Proxy

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