数据库连接
数据库可以处理的并发连接数是有限的。每个连接都需要占用 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
),并可选择性地调优连接池大小 - 在处理程序外部实例化
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 问题以获取有关预期默认连接池大小的信息:https://github.com/prisma/docs/issues/667
使用外部连接池器
如果您正在使用外部连接池器,请使用默认连接池大小 (num_physical_cpus * 2 + 1
) 作为起点,然后调优连接池大小。外部连接池器应能防止流量高峰使数据库超负荷。
优化并行请求
如果 connection_limit
为 1,此函数将被迫**串行**(一个接一个)发送查询,而不是**并行**。这会降低函数处理请求的能力,并可能导致连接池超时错误。调整 connection_limit
参数,直到流量高峰
Promise.all() {
query1,
query2,
query3
query4,
...
}
不会耗尽数据库连接限制
- 不会导致连接池超时错误
- 无服务器环境中的
PrismaClient
在处理程序外部实例化 PrismaClient
在函数处理程序范围之外实例化 PrismaClient
以增加重用的机会。只要处理程序保持“热”(在使用中),连接就可能被重用
不要显式调用 $disconnect()
import { PrismaClient } from '@prisma/client'
const client = new PrismaClient()
export async function handler() {
/* ... */
}
您不需要在函数结束时显式调用 $disconnect()
,因为容器可能被重用。打开新连接会花费时间,并降低您的函数处理请求的能力。
其他无服务器注意事项
容器重用
无法保证后续的函数调用会命中同一个容器——例如,AWS 可以随时选择创建一个新容器。
代码应假定容器是无状态的,并且仅在连接不存在时才创建连接——Prisma Client JS 已经实现了此逻辑。
僵尸连接
被标记为“待移除”且未被重用的容器仍然会**保持连接打开**,并可能在该状态下停留一段时间(AWS 未知且未记录)。这可能导致数据库连接的次优利用。
一个潜在的解决方案是**清理空闲连接**(serverless-mysql
实现了这个想法,但不能与 Prisma ORM 一起使用)。
并发限制
根据您的无服务器并发限制(并行运行的无服务器函数数量),您可能仍然会耗尽数据库的连接限制。当过多函数并发调用,每个函数都有自己的连接池时,就可能发生这种情况,最终耗尽数据库连接限制。为防止这种情况,您可以将您的无服务器并发限制 设置为低于数据库最大连接限制除以每个函数调用使用的连接数(因为您可能希望能够从其他客户端连接以用于其他目的)的值。
优化连接池
如果查询引擎无法在时间限制之前处理队列中的查询,您将在日志中看到连接池超时异常。连接池超时可能发生在以下情况:
许多用户同时访问您的应用程序
- 您并行发送大量查询(例如,使用
await Promise.all()
) - 如果在配置了建议的连接池大小后仍然持续遇到连接池超时,您可以进一步调优
connection_limit
和pool_timeout
参数。
增加连接池大小
注意
queryCompiler
预览功能。启用后,您的 Prisma Client 将不带基于 Rust 的查询引擎二进制文件生成
请注意,queryCompiler
需要同时启用驱动适配器预览功能。使用 queryCompiler
预览功能时,连接池大小通过您正在使用的原生 JS 驱动程序设置。:
generator client {
provider = "prisma-client-js"
previewFeatures = ["queryCompiler", "driverAdapters"]
}
增加连接池大小允许查询引擎并行处理更多的查询。请注意,您的数据库必须能够支持增加的并发连接数,否则您将**耗尽数据库连接限制**。
要增加连接池大小,请手动将 connection_limit
设置为更高的数字
注意:在无服务器环境中将 connection_limit
设置为 1 是一个建议的起点,但此值也可以进行调优。
datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?schema=public&connection_limit=40"
}
增加连接池超时时间
增加连接池超时时间可以给查询引擎更多时间来处理队列中的查询。您可以在以下场景中考虑这种方法:
您已经增加了 connection_limit
。
- 您确信队列不会增长超过某个大小,否则**您最终会耗尽 RAM**。
- 要增加连接池超时时间,请将
pool_timeout
参数设置为大于默认值(10 秒)的值
禁用连接池超时
datasource db {
provider = "postgresql"
url = "postgresql://johndoe:mypassword@localhost:5432/mydb?connection_limit=5&pool_timeout=20"
}
禁用连接池超时可以防止查询引擎在等待连接 x 秒后抛出异常,并允许队列累积。您可以在以下场景中考虑这种方法:
您在有限的时间内提交大量查询——例如,作为导入或更新数据库中所有客户的任务的一部分。
- 要禁用连接池超时,请将
pool_timeout
参数设置为0
- 您确信队列不会增长超过某个大小,否则**您最终会耗尽 RAM**。
- 要增加连接池超时时间,请将
pool_timeout
参数设置为大于默认值(10 秒)的值
外部连接池器
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
schema.prisma
以使用新的直接 URL# 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
directUrl
字段的更多信息,请参阅此处。datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
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 一起使用时不提供任何连接池优势。
在 GitHub 上编辑此页面