跳到主要內容

資料庫連線

資料庫可以處理的並行連線數量有限。每個連線都需要 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 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

在處理常式外部實例化 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: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

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 一起使用時,它不會提供任何連線池優勢