数据库连接
数据库可以处理的并发连接数是有限的。每个连接都需要 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并且您有2个应用程序实例,则connection_limit
参数应不超过5。 - 您可以选择性地调整连接池大小。
长时间运行应用程序中的 PrismaClient
在长时间运行的应用程序中,我们建议您
- ✔ 创建一个
PrismaClient
实例并在整个应用程序中重用它 - ✔ 将
PrismaClient
分配给全局变量,仅在开发环境中,以防止热重载创建新实例
重用单个 PrismaClient
实例
要重用单个实例,请创建一个导出 PrismaClient
对象的模块
import { PrismaClient } from '@prisma/client'
let prisma = new PrismaClient()
export default prisma
模块首次导入时,该对象会被缓存。后续请求返回缓存的对象,而不是创建新的 PrismaClient
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
存储为全局变量,仅在开发环境中这样做,因为全局变量不会被重新加载
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 的方式不会改变
import { prisma } from './client'
async function main() {
const allUsers = await prisma.user.findMany()
}
main()
每个 CLI 命令创建的连接数
在针对 Postgres、MySQL 和 SQLite 的本地测试中,每个 Prisma CLI 命令通常使用单个连接。下表显示了这些测试中观察到的范围。您的环境可能会产生略有不同的结果。
命令 | 连接数 | 描述 |
---|---|---|
migrate status | 1 | 检查迁移状态 |
migrate dev | 1–4 | 在开发环境中应用待处理的迁移 |
migrate diff | 1–2 | 将数据库 schema 与迁移历史进行比较 |
migrate reset | 1–2 | 重置数据库并重新应用迁移 |
migrate deploy | 1–2 | 在生产环境中应用待处理的迁移 |
db pull | 1 | 将数据库 schema 拉取到 Prisma schema 中 |
db push | 1–2 | 将 Prisma schema 推送到数据库 |
db execute | 1 | 执行原始 SQL 命令 |
db seed | 1 | 用初始数据填充数据库 |
无服务器环境 (FaaS)
无服务器环境的示例包括托管在 AWS Lambda、Vercel 或 Netlify Functions 上的 Node.js 函数。请使用以下清单作为无服务器环境中连接管理的指南
- 熟悉无服务器连接管理挑战
- 根据是否使用外部连接池设置连接池大小 (
connection_limit
),并可选择调整连接池大小 - 在 handler 外部实例化
PrismaClient
,并且不要显式地调用$disconnect()
- 配置函数并发并处理空闲连接
无服务器挑战
在无服务器环境中,每个函数都会创建 PrismaClient
的自己的实例,并且每个客户端实例都有自己的连接池。
考虑以下示例,其中单个 AWS Lambda 函数使用 PrismaClient
连接到数据库。 connection_limit
为 3
流量激增导致 AWS Lambda 生成另外两个 lambda 来处理增加的负载。每个 lambda 创建一个 PrismaClient
实例,每个实例的 connection_limit
为 3,这导致数据库的最大连接数为 9
200 个并发函数(以及因此产生的 600 个可能的连接)响应流量激增 📈 会非常快速地耗尽数据库连接限制。此外,默认情况下,任何处于暂停状态的函数都会保持连接打开,并阻止其他函数使用它们。
- 首先将
connection_limit
设置为1
- 如果较小的连接池大小不够用,请考虑使用像 PgBouncer 这样的外部连接池工具
推荐的连接池大小
无服务器环境中推荐的连接池大小 (connection_limit
) 取决于
不使用外部连接池工具
如果您没有使用外部连接池工具,请首先将连接池大小 (connection_limit
) 设置为 1,然后进行优化。每个传入请求都会启动一个生命周期很短的 Node.js 进程,并且许多具有高 connection_limit
的并发函数在流量激增期间会很快耗尽数据库连接限制。
以下示例演示了如何在连接 URL 中将 connection_limit
设置为 1
- PostgreSQL
- MySQL
postgresql://USER:PASSWORD@HOST:PORT/DATABASE?schema=public&connection_limit=1
mysql://USER:PASSWORD@HOST:PORT/DATABASE?connection_limit=1
如果您正在使用 AWS Lambda 并且没有配置 connection_limit
,请参阅以下 GitHub issue 以获取有关预期默认连接池大小的信息: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
在 handler 外部实例化 PrismaClient
在函数 handler 范围外部实例化 PrismaClient
,以增加重用的机会。只要 handler 保持“热”(正在使用),连接就有可能被重用
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_limit
和 pool_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
属性中添加一个提供数据库直接连接的环境变量
# Connection URL to your database using PgBouncer.
DATABASE_URL="postgres://root:password@127.0.0.1:54321/postgres?pgbouncer=true"
# Direct connection URL to the database used for migrations
DIRECT_URL="postgres://root:password@127.0.0.1:5432/postgres"
然后,您可以更新您的 schema.prisma
以使用新的直接 URL
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 一起使用时,它不提供任何连接池优势。