何时使用
- 在 Serverless Functions / Edge / Next.js on Vercel 等环境接入 Neon Postgres,需要正确处理连接池与连接数上限时。
- 用 Prisma 或 Drizzle 对接 Neon,需要区分「池化连接」与「直连」(迁移/DDL)时。
- 想用数据库分支(copy-on-write)为每个 PR/预览部署/调试场景创建隔离副本时。
- 需要应对 scale-to-zero 冷启动延迟、配置自动伸缩与计算规格时。
不该用的边界:
- 自建或非 Neon 的 Postgres 运维、备份恢复、参数调优——本技能特定于 Neon 的连接串/分支/PgBouncer 行为。
- 纯 SQL 语句优化、索引设计、查询性能问题本身。
- 其他云数据库(RDS、Supabase、PlanetScale 等)的集成。
步骤
- 准备两个连接串:池化(主机名含
-pooler)用于应用查询;直连(无-pooler)用于迁移/DDL。两者都需sslmode=require。 - 选 ORM 与驱动:Prisma 用
url(池化)+directUrl(直连);Drizzle 在 Edge/一次性查询用neon-http,需要事务用neon-serverless(WebSocket Pool)。 - 单例化客户端:复用全局 Prisma 实例或共享 Pool,避免每请求新建连接。Serverless 本地池保持
max: 5-10,靠 PgBouncer 承接高并发。 - 迁移始终走直连:
prisma migrate deploy/drizzle-kit migrate。 - 分支化预览:在 CI(GitHub Actions / Vercel 集成)中「每个 PR 建分支 → 跑迁移 → 部署 → PR 关闭即删分支」。
- 生产环境:关闭 scale-to-zero、设置最小计算规格,应用层加冷启动重试。
指令
# 创建分支(从 main)
neon branches create --name feature/new-feature --parent main
# 按时间点创建分支(PITR 调试)
neon branches create --name debug/yesterday --parent main --timestamp "2024-01-15T10:00:00Z"
neon branches list
neon connection-string feature/new-feature # 获取分支连接串
neon branches delete feature/new-feature # 用完即删
# Prisma 迁移(自动走 directUrl)
npx prisma migrate dev
npx prisma migrate deploy
# Drizzle
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit
npx drizzle-kit generate
npx drizzle-kit migrate
连接串格式(关键区别在 -pooler):
# 池化(应用查询)
postgres://user:pass@ep-cool-name-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require
# 直连(迁移/DDL)
postgres://user:pass@ep-cool-name.us-east-2.aws.neon.tech/neondb?sslmode=require
# PG17+ 降低冷启动握手延迟
...neon.tech/db?sslmode=require&sslnegotiation=direct
示例
Prisma 接入(.env + schema + 单例):
DATABASE_URL="postgres://user:password@ep-xxx-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require"
DIRECT_URL="postgres://user:password@ep-xxx.us-east-2.aws.neon.tech/neondb?sslmode=require"
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // 池化
directUrl = env("DIRECT_URL") // 直连,迁移必需
}
// lib/prisma.ts —— 全局单例,避免每请求新建
import { PrismaClient } from '@prisma/client';
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined };
export const prisma = globalForPrisma.prisma ?? new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
});
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
Drizzle —— Edge 用 HTTP 驱动,事务用 WebSocket:
// HTTP:最快的一次性查询(不支持事务)
import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
// WebSocket:需要事务时
import { Pool } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-serverless';
const pool = new Pool({ connectionString: process.env.DATABASE_URL, max: 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 10000 });
export const db = drizzle(pool, { schema });
await db.transaction(async (tx) => {
await tx.insert(users).values({ email: 'test@example.com' });
});
Vercel 每 PR 一个数据库分支(GitHub Actions 摘要):
- uses: neondatabase/create-branch-action@v5
id: create-branch
with:
project_id: ${{ secrets.NEON_PROJECT_ID }}
branch_name: preview/pr-${{ github.event.pull_request.number }}
api_key: ${{ secrets.NEON_API_KEY }}
- name: Run migrations
env:
DATABASE_URL: ${{ steps.create-branch.outputs.db_url_with_pooler }}
run: npx prisma migrate deploy
# PR 关闭时用 neondatabase/delete-branch-action@v3 删除分支
Vercel 集成注入变量:DATABASE_URL(池化)、DATABASE_URL_UNPOOLED(直连,对应 Prisma 的 directUrl)。把 prisma migrate deploy 加进 vercel-build。
冷启动重试(应对 scale-to-zero 唤醒):
const MAX_RETRIES = 3, RETRY_DELAY = 1000;
export async function queryWithRetry<T>(query: () => Promise<T>): Promise<T> {
let lastError: Error | undefined;
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try { return await query(); }
catch (error: any) {
lastError = error;
if (error.code === 'P1001' || error.code === 'P1002') { // 连接错误=可能在冷启动
await new Promise(r => setTimeout(r, RETRY_DELAY * attempt));
continue;
}
throw error;
}
}
throw lastError;
}
注意事项
反模式(高危优先):
- 用池化连接跑迁移:DDL 经 PgBouncer 会失败 → Prisma 设
directUrl指向非池化端点;directUrl也不能指向 pooler。 - Serverless 里用原生
pg/TCP 驱动:部分 Edge 环境不支持 → 改用@neondatabase/serverless。 - 用 HTTP 驱动 (
neon()) 跑事务:不支持 → 事务用 WebSocket 的Pool。 - 每请求新建连接或新建客户端:迅速耗尽连接数 → 复用单例/连接池。
- Serverless 本地池
max设太高:函数实例多 × 每实例一池 = 连接爆表 → 本地池保持 5-10,靠 PgBouncer。 - 生产开启 scale-to-zero:首请求增加 500ms+ 冷启动延迟 → 生产分支关闭 scale-to-zero、设最小计算规格;首连接加指数退避重试。
- 分支不清理:存储与混乱累积 → PR 关闭自动删分支。删除父分支会影响子分支,注意层级。
- 多预览共用一个库:相互干扰 → 集成里开「每预览一分支」,并在 build 步骤跑迁移防 schema 漂移。
约束与配额:
- pooler 最多约 10000 并发连接,但仍消耗底层 Postgres 连接;Neon 超级用户保留约 7 个连接。
- 计算规格对应直连上限参考:0.25 CU≈112、0.5 CU≈225、1 CU≈450、2 CU≈901、4 CU≈1802、8 CU≈3604。
- 规格建议:开发 0.25 CU + scale-to-zero;预发 0.5 CU;生产 1+ CU 且关闭 scale-to-zero;高流量 2-4 CU 起 + 自动伸缩。
校验红线(提交前自查):
- 客户端代码出现直连 URL(应只暴露池化 URL,且仅服务端用)→ 错误。
- 硬编码连接串(应走环境变量)→ 错误。
- 连接串缺
sslmode=require→ 警告,Neon 强制 SSL。 - Prisma 缺
directUrl或directUrl指向 pooler → 迁移会失败,错误。
互见
- 需要鉴权(User 表带 clerkId)→ clerk-auth
- 需要缓存/会话存储 → redis-specialist
- 需要超出 Postgres 能力的全文搜索 → algolia-search
- 需要部署与环境变量/预览库联动 → vercel-deployment
采编自 sickn33/antigravity-awesome-skills(MIT,原条目源自 vibeship-spawner-skills)。