关于 neon-vercel-postgres
neon-vercel-postgres 是面向 Neon 和 Vercel 无服务器 Postgres 开发的技能,适用于构建应用的开发者。内容涵盖连接设置、安全查询、事务、连接池和 ORM 选择。
主要功能
- 01配置 Neon 或 Vercel Postgres
- 02执行参数化 SQL 查询
- 03管理事务和连接
- 04使用 Drizzle ORM 或 Prisma
- 05设置无服务器连接池
适用场景
- 01为无服务器应用连接 Postgres
- 02在边缘函数中访问数据库
- 03执行数据库架构变更和迁移
- 04避免不安全 SQL 和连接耗尽
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jackspace/ claudeskillz --skill neon-vercel-postgres安装后在 Claude Code 中输入/neon-vercel-postgres确认已加载。
附带文件README.mdSKILL.json
详细介绍
Neon & Vercel Serverless Postgres
状态:已可用于生产环境
最近更新:2025-10-29
依赖项:无
最新版本:@neondatabase/[email protected]、@vercel/[email protected]、[email protected]、[email protected]
快速开始(5 分钟)
1. 选择平台
选项 A:Neon Direct(多云、Cloudflare Workers、任意无服务器环境)
npm install @neondatabase/serverless
选项 B:Vercel Postgres(仅限 Vercel,在 Vercel 上零配置)
npm install @vercel/postgres
注意:两者使用相同的 Neon 后端。Vercel Postgres 是 Neon 配合 Vercel 特定的环境设置。
这为什么重要:
- Neon Direct 提供多云灵活性,并可使用分支 API
- Vercel Postgres 在 Vercel 上实现零配置,并自动设置环境变量
- 两者都基于 HTTP(不使用 TCP),非常适合无服务器/边缘环境
2. 获取连接字符串
Neon Direct:
# Sign up at https://neon.tech
# Create a project → Get connection string
# Format: postgresql://user:[email protected]/dbname?sslmode=require
Vercel Postgres:
# In your Vercel project
vercel postgres create
vercel env pull .env.local # Automatically creates POSTGRES_URL and other vars
重要:
- 无服务器环境请使用连接池连接字符串(以
-pooler.region.aws.neon.tech结尾) - 在无服务器环境中,非连接池连接很快就会耗尽
- 始终包含
?sslmode=require参数
3. 查询数据库
Neon Direct(Cloudflare Workers、Vercel Edge、Node.js):
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// Simple query
const users = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const result = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`SELECT * FROM users WHERE name = ${name}`
]);
Vercel Postgres(Next.js Server Actions、API Routes):
import { sql } from '@vercel/postgres';
// Simple query
const { rows } = await sql`SELECT * FROM users WHERE id = ${userId}`;
// Transactions
const client = await sql.connect();
try {
await client.sql`BEGIN`;
await client.sql`INSERT INTO users (name) VALUES (${name})`;
await client.sql`COMMIT`;
} finally {
client.release();
}
重要:
- 使用模板标签语法(
sql`...`)自动防范 SQL 注入 - 切勿拼接字符串:
sql('SELECT * FROM users WHERE id = ' + id)❌ - 模板标签会自动转义值,防止 SQL 注入
7 步设置流程
步骤 1:安装软件包
根据部署平台选择:
Neon Direct(Cloudflare Workers、多云、直接访问 Neon):
npm install @neondatabase/serverless
Vercel Postgres(Vercel 专用、零配置):
npm install @vercel/postgres
搭配 ORM 使用:
# Drizzle ORM (recommended)
npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit
# Prisma (alternative)
npm install prisma @prisma/client @prisma/adapter-neon @neondatabase/serverless
要点:
- 两个软件包都使用 HTTP/WebSocket(无需 TCP)
- 兼容边缘环境(可在 Cloudflare Workers、Vercel Edge Runtime 中运行)
- 使用连接池连接字符串时,内置连接池
- 无需单独的连接池库
步骤 2:创建 Neon 数据库
选项 A:Neon 控制台
- 在 https://neon.tech 注册
- 创建新项目
- 复制连接池连接字符串(很重要!)
- 格式:
postgresql://user:[email protected]/db?sslmode=require
选项 B:Vercel 控制台
- 前往你的 Vercel 项目 → Storage → Create Database → Postgres
- Vercel 会自动创建 Neon 数据库
- 运行
vercel env pull,在本地获取环境变量
选项 C:Neon CLI(neonctl)
# Install CLI
npm install -g neonctl
# Authenticate
neonctl auth
# Create project
neonctl projects create --name my-app
# Get connection string
neonctl connection-string main
重要:
- 始终使用连接池连接字符串(以
-pooler.region.aws.neon.tech结尾) - 非连接池连接用于直接连接(不适用于无服务器环境)
- 在连接字符串中包含
?sslmode=require
步骤 3:配置环境变量
Neon Direct:
# .env or .env.local
DATABASE_URL="postgresql://user:[email protected]/neondb?sslmode=require"
Vercel Postgres:
# Automatically created by `vercel env pull`
POSTGRES_URL="..." # Pooled connection (use this for queries)
POSTGRES_PRISMA_URL="..." # For Prisma migrations
POSTGRES_URL_NON_POOLING="..." # Direct connection (avoid in serverless)
POSTGRES_USER="..."
POSTGRES_HOST="..."
POSTGRES_PASSWORD="..."
POSTGRES_DATABASE="..."
Cloudflare Workers(wrangler.jsonc):
{
"vars": {
"DATABASE_URL": "postgresql://user:[email protected]/neondb?sslmode=require"
}
}
要点:
- 查询时使用
POSTGRES_URL(连接池) - Prisma 迁移时使用
POSTGRES_PRISMA_URL - 切勿在无服务器函数中使用
POSTGRES_URL_NON_POOLING - 安全存储密钥(Vercel env、Cloudflare secrets 等)
步骤 4:创建数据库架构
选项 A:原始 SQL
// scripts/migrate.ts
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
await sql`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
)
`;
选项 B:Drizzle ORM(推荐)
// db/schema.ts
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
});
// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
# Run migrations
npx drizzle-kit generate
npx drizzle-kit migrate
选项 C:Prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("POSTGRES_PRISMA_URL")
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
createdAt DateTime @default(now()) @map("created_at")
@@map("users")
}
npx prisma migrate dev --name init
重要:
- 边缘环境兼容的 ORM 请使用 Drizzle(可在 Cloudflare Workers 中运行)
- Prisma 需要 Node.js 运行时(无法在 Cloudflare Workers 中运行)
- 请在 Node.js 环境中运行迁移,而非边缘函数
步骤 5:查询模式
简单查询(Neon Direct):
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
// SELECT
const users = await sql`SELECT * FROM users WHERE email = ${email}`;
// INSERT
const newUser = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
// UPDATE
await sql`UPDATE users SET name = ${newName} WHERE id = ${id}`;
// DELETE
await sql`DELETE FROM users WHERE id = ${id}`;
简单查询(Vercel Postgres):
import { sql } from '@vercel/postgres';
// SELECT
const { rows } = await sql`SELECT * FROM users WHERE email = ${email}`;
// INSERT
const { rows: newUser } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
事务(Neon Direct):
// Automatic transaction
const results = await sql.transaction([
sql`INSERT INTO users (name) VALUES (${name})`,
sql`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${accountId}`
]);
// Manual transaction (for complex logic)
const result = await sql.transaction(async (sql) => {
const [user] = await sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await sql`INSERT INTO profiles (user_id) VALUES (${user.id})`;
return user;
});
事务(Vercel Postgres):
import { sql } from '@vercel/postgres';
const client = await sql.connect();
try {
await client.sql`BEGIN`;
const { rows } = await client.sql`INSERT INTO users (name) VALUES (${name}) RETURNING id`;
await client.sql`INSERT INTO profiles (user_id) VALUES (${rows[0].id})`;
await client.sql`COMMIT`;
} catch (e) {
await client.sql`ROLLBACK`;
throw e;
} finally {
client.release();
}
Drizzle ORM 查询:
import { db } from './db';
import { users } from './db/schema';
import { eq } from 'drizzle-orm';
// SELECT
const allUsers = await db.select().from(users);
const user = await db.select().from(users).where(eq(users.email, email));
// INSERT
const newUser = await db.insert(users).values({ name, email }).returning();
// UPDATE
await db.update(users).set({ name: newName }).where(eq(users.id, id));
// DELETE
await db.delete(users).where(eq(users.id, id));
// Transactions
await db.transaction(async (tx) => {
await tx.insert(users).values({ name, email });
await tx.insert(profiles).values({ userId: user.id });
});
要点:
- 始终使用模板标签语法(
sql`...`),以防止 SQL 注入 - 事务是原子的(要么全部成功,要么全部失败)
- 使用后释放连接(Vercel Postgres 手动事务)
- Drizzle 完全类型安全,且兼容边缘环境
步骤 6:处理连接池
连接字符串格式:
Pooled (serverless): postgresql://user:[email protected]/db
Non-pooled (direct): postgresql://user:[email protected]/db
适用场景:
- 使用连接池(
-pooler.):无服务器函数、边缘函数、高并发场景 - 不使用连接池:长时间运行的服务、迁移、管理任务、不需要担心连接数限制的场景
自动连接池(Neon/Vercel):
// Both packages handle pooling automatically when using pooled connection string
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!); // Pooling is automatic
连接数限制:
- Neon 免费层级:100 个并发连接
- 池化连接:在多个请求之间共享连接
- 非池化连接:每个请求都会建立一个新连接(很快耗尽)
重要:
- 在无服务器环境中始终使用池化连接字符串
- 非池化连接会导致“连接池已耗尽”错误
- 在 Neon 控制面板中监控连接使用情况
步骤 7:部署和测试
Cloudflare Workers:
// src/index.ts
import { neon } from '@neondatabase/serverless';
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
const users = await sql`SELECT * FROM users`;
return Response.json(users);
}
};
# Deploy
npx wrangler deploy
Vercel(Next.js API 路由):
// app/api/users/route.ts
import { sql } from '@vercel/postgres';
export async function GET() {
const { rows } = await sql`SELECT * FROM users`;
return Response.json(rows);
}
# Deploy
vercel deploy --prod
测试查询:
# Local test
curl http://localhost:8787/api/users
# Production test
curl https://your-app.workers.dev/api/users
要点:
- 部署前先在本地测试
- 在 Neon 控制面板中监控查询性能
- 设置连接池耗尽警报
- 使用 Neon 的查询历史记录进行调试
关键规则
务必做到
✅ 在无服务器环境中使用池化连接字符串(主机名中包含 -pooler.)
✅ 使用模板标签语法执行查询(sql`SELECT * FROM users`),以防止 SQL 注入
✅ 在连接字符串中包含 sslmode=require
✅ 事务完成后释放连接(Vercel Postgres 手动事务)
✅ 使用 Drizzle ORM,作为兼容边缘环境的 TypeScript ORM(不要在 Cloudflare Workers 中使用 Prisma)
✅ 将连接字符串设为环境变量(绝不要硬编码)
✅ 使用 Neon 分支作为预览环境并进行测试
✅ 在 Neon 控制面板中监控连接池使用情况
✅ 使用 try/catch 块处理错误,并在失败时回滚事务
✅ 使用 RETURNING` 子句进行 INSERT/UPDATE,通过一次查询获取创建或更新的数据
绝不要做
❌ 绝不要在无服务器函数中使用非池化连接(会耗尽连接池)
❌ 绝不要拼接 SQL 字符串('SELECT * FROM users WHERE id = ' + id)——有 SQL 注入风险
❌ 绝不要省略 sslmode=require——连接会失败或不安全
❌ 绝不要在 Vercel Postgres 手动事务中忘记调用 client.release()——会导致连接泄漏
❌ 绝不要在 Cloudflare Workers 中使用 Prisma——它需要 Node.js 运行时(请改用 Drizzle)
❌ 绝不要硬编码连接字符串——请使用环境变量
❌ 绝不要从边缘函数运行迁移——请使用 Node.js 环境或 Neon 控制台
❌ 绝不要提交 .env 文件——请将其添加到 .gitignore
❌ 绝不要在无服务器函数中使用 POSTGRES_URL_NON_POOLING——这会使连接池失去作用
❌ 绝不要超出连接数限制——请监控使用情况,并在需要时升级套餐
预防已知问题
此技能可预防 15 个有记录的问题:
问题 #1:连接池耗尽
错误:Error: connection pool exhausted 或 too many connections for role
来源:https://github.com/neondatabase/serverless/issues/12
原因:在高并发无服务器环境中使用非池化连接字符串
预防措施:始终使用池化连接字符串(主机名中包含 -pooler.)。检查连接字符串格式。
问题 #2:不支持 TCP 连接
错误:Error: TCP connections are not supported in this environment
来源:Cloudflare Workers 文档
原因:传统的 Postgres 客户端使用 TCP 套接字,而边缘运行时不提供此功能
预防措施:使用基于 HTTP/WebSocket 的 @neondatabase/serverless,而不是 pg 或 postgres.js 包。
问题 #3:字符串拼接导致 SQL 注入
错误:SQL 注入攻击成功或查询结果异常
来源:OWASP SQL 注入指南
原因:将用户输入拼接到 SQL 字符串中:sql('SELECT * FROM users WHERE id = ' + id)
预防措施:始终使用模板标签语法:sql`SELECT * FROM users WHERE id = ${id}`。模板标签会自动转义值。
问题 #4:缺少 SSL 模式
错误:Error: connection requires SSL 或 FATAL: no pg_hba.conf entry
来源:https://neon.tech/docs/connect/connect-securely
原因:连接字符串缺少 ?sslmode=require 参数
预防措施:始终在连接字符串后追加 ?sslmode=require。
问题 #5:连接泄漏(Vercel Postgres)
错误:内存使用量逐渐增加,最终出现超时错误
来源:https://github.com/vercel/storage/issues/45
原因:手动执行事务后忘记调用 client.release()
预防措施:始终使用 try/finally 块,并在 finally 块中调用 client.release()。
问题 #6:环境变量错误(Vercel)
错误:Error: Connection string is undefined 或 connect ECONNREFUSED
来源:https://vercel.com/docs/storage/vercel-postgres/using-an-orm
原因:使用了 DATABASE_URL 而不是 POSTGRES_URL,或反之
预防措施:查询时使用 POSTGRES_URL,Prisma 迁移时使用 POSTGRES_PRISMA_URL。
问题 #7:边缘函数中的事务超时
错误:Error: Query timeout 或 Error: transaction timeout
来源:https://neon.tech/docs/introduction/limits
原因:长时间运行的事务超出边缘函数的超时时间(通常为 30s)
预防措施:缩短事务执行时间(<5s)、批量处理操作,或将复杂事务移至后台工作进程。
问题 #8:在 Cloudflare Workers 中使用 Prisma
错误:Error: PrismaClient is unable to be run in the browser 或模块解析错误
来源:https://github.com/prisma/prisma/issues/18765
原因:Prisma 需要具备文件系统访问能力的 Node.js 运行时
预防措施:在 Cloudflare Workers 中使用 Drizzle ORM。Prisma 仅适用于 Vercel Edge/Node.js 运行时。
问题 #9:分支 API 身份验证错误
错误:调用 Neon API 时出现 Error: Unauthorized
来源:https://neon.tech/docs/api/authentication
原因:缺少或无效的 NEON_API_KEY 环境变量
预防措施:在 Neon 控制面板中创建 API 密钥:Account Settings → API Keys,并将其设为环境变量。
问题 #10:删除分支后连接过期
错误:删除分支后出现 Error: database "xyz" does not exist
来源:https://neon.tech/docs/guides/branching
原因:应用仍在使用已删除分支的连接字符串
预防措施:切换分支时更新 DATABASE_URL,并在分支变更后重启应用。
问题 #11:冷启动时查询超时
错误:闲置一段时间后,首次请求出现 Error: Query timeout
来源:https://neon.tech/docs/introduction/auto-suspend
原因:Neon 会在系统闲置后自动暂停计算资源,唤醒需要约 1–2s
预防措施:预期会发生冷启动,将查询超时时间设为 >= 10s,或禁用自动暂停(付费套餐)。
问题 #12:Drizzle Schema 不匹配
错误:TypeScript 错误,例如 Property 'x' does not exist on type 'User'
来源:https://orm.drizzle.team/docs/generate
原因:数据库 schema 已更改,但没有重新生成 Drizzle 类型
预防措施:更改 schema 后运行 npx drizzle-kit generate,并提交生成的文件。
问题 #13:不同分支之间的迁移冲突
错误:Error: relation "xyz" already exists 或迁移版本冲突
来源:https://neon.tech/docs/guides/branching#schema-migrations
原因:多个分支具有不同的迁移历史
预防措施:在主分支上运行迁移后再创建分支,或在合并前重置分支 schema。
问题 #14:PITR 时间戳超出范围
错误:Error: timestamp is outside retention window
来源:https://neon.tech/docs/introduction/point-in-time-restore
原因:尝试从早于保留期限的时间戳恢复(免费套餐的保留期限为 7 天)
预防措施:检查套餐的保留期限,并在允许的时间范围内恢复。
问题 #15:Prisma 使用了错误的适配器
错误:Error: Invalid connection string 或查询性能缓慢
来源:https://www.prisma.io/docs/orm/overview/databases/neon
原因:在无服务器环境中未使用 @prisma/adapter-neon
预防措施:安装 @prisma/adapter-neon 和 @neondatabase/serverless,配置 Prisma 使用基于 HTTP 的连接。
配置文件参考
package.json(Neon Direct)
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2"
}
}
package.json(Vercel Postgres)
{
"dependencies": {
"@vercel/postgres": "^0.10.0"
}
}
package.json(使用 Drizzle ORM)
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0"
},
"scripts": {
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
"db:studio": "drizzle-kit studio"
}
}
drizzle.config.ts
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './db/schema.ts',
out: './db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: process.env.DATABASE_URL!
}
});
这些设置的原因:
@neondatabase/serverless兼容边缘环境(基于 HTTP/WebSocket)@vercel/postgres在 Vercel 上提供零配置支持drizzle-orm可在所有运行时中使用(Cloudflare Workers、Vercel Edge、Node.js)drizzle-kit负责处理迁移和 schema 生成
常见模式
模式 1:使用 Neon 的 Cloudflare Worker
// src/index.ts
import { neon } from '@neondatabase/serverless';
interface Env {
DATABASE_URL: string;
}
export default {
async fetch(request: Request, env: Env) {
const sql = neon(env.DATABASE_URL);
// Parse request
const url = new URL(request.url);
if (url.pathname === '/api/users' && request.method === 'GET') {
const users = await sql`SELECT id, name, email FROM users`;
return Response.json(users);
}
if (url.pathname === '/api/users' && request.method === 'POST') {
const { name, email } = await request.json();
const [user] = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
return Response.json(user, { status: 201 });
}
return new Response('Not Found', { status: 404 });
}
};
适用场景:部署使用 Postgres 数据库的 Cloudflare Workers
模式 2:使用 Vercel Postgres 的 Next.js Server Action
// app/actions/users.ts
'use server';
import { sql } from '@vercel/postgres';
import { revalidatePath } from 'next/cache';
export async function getUsers() {
const { rows } = await sql`SELECT id, name, email FROM users ORDER BY created_at DESC`;
return rows;
}
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
const email = formData.get('email') as string;
const { rows } = await sql`
INSERT INTO users (name, email)
VALUES (${name}, ${email})
RETURNING *
`;
revalidatePath('/users');
return rows[0];
}
export async function deleteUser(id: number) {
await sql`DELETE FROM users WHERE id = ${id}`;
revalidatePath('/users');
}
适用场景:在 Next.js Server Actions 中使用 Vercel Postgres
模式 3:使用类型安全功能的 Drizzle ORM
// db/schema.ts
import { pgTable, serial, text, timestamp, integer } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
createdAt: timestamp('created_at').defaultNow()
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
userId: integer('user_id').notNull().references(() => users.id),
title: text('title').notNull(),
content: text('content'),
createdAt: timestamp('created_at').defaultNow()
});
// db/index.ts
import { drizzle } from 'drizzle-orm/neon-http';
import { neon } from '@neondatabase/serverless';
import * as schema from './schema';
const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });
// app/api/posts/route.ts
import { db } from '@/db';
import { posts, users } from '@/db/schema';
import { eq } from 'drizzle-orm';
export async function GET() {
// Type-safe query with joins
const postsWithAuthors = await db
.select({
postId: posts.id,
title: posts.title,
content: posts.content,
authorName: users.name
})
.from(posts)
.leftJoin(users, eq(posts.userId, users.id));
return Response.json(postsWithAuthors);
}
适用场景:需要类型安全的查询、复杂联接以及兼容边缘环境的 ORM
模式 4:数据库事务
// Neon Direct - Automatic Transaction
import { neon } from '@neondatabase/serverless';
const sql = neon(process.env.DATABASE_URL!);
const result = await sql.transaction(async (tx) => {
// Deduct from sender
const [sender] = await tx`
UPDATE accounts
SET balance = balance - ${amount}
WHERE id = ${senderId} AND balance >= ${amount}
RETURNING *
`;
if (!sender) {
throw new Error('Insufficient funds');
}
// Add to recipient
await tx`
UPDATE accounts
SET balance = balance + ${amount}
WHERE id = ${recipientId}
`;
// Log transaction
await tx`
INSERT INTO transfers (from_id, to_id, amount)
VALUES (${senderId}, ${recipientId}, ${amount})
`;
return sender;
});
适用场景:多个相互关联的数据库操作必须全部成功或全部失败
模式 5:使用 Neon 分支创建预览环境
# Create branch for PR
neonctl branches create --project-id my-project --name pr-123 --parent main
# Get connection string for branch
BRANCH_URL=$(neonctl connection-string pr-123)
# Use in Vercel preview deployment
vercel env add DATABASE_URL preview
# Paste $BRANCH_URL
# Delete branch when PR is merged
neonctl branches delete pr-123
# .github/workflows/preview.yml
name: Create Preview Database
on:
pull_request:
types: [opened, synchronize]
jobs:
preview:
runs-on: ubuntu-latest
steps:
- name: Create Neon Branch
run: |
BRANCH_NAME="pr-${{ github.event.pull_request.number }}"
neonctl branches create --project-id ${{ secrets.NEON_PROJECT_ID }} --name $BRANCH_NAME
BRANCH_URL=$(neonctl connection-string $BRANCH_NAME)
- name: Deploy to Vercel
env:
DATABASE_URL: ${{ steps.branch.outputs.url }}
run: vercel deploy --env DATABASE_URL=$DATABASE_URL
适用场景:希望为每个 PR/预览部署创建隔离的数据库
使用捆绑资源
脚本(scripts/)
setup-neon.sh - 创建 Neon 数据库并输出连接字符串
chmod +x scripts/setup-neon.sh
./scripts/setup-neon.sh my-project-name
test-connection.ts - 验证数据库连接并运行测试查询
npx tsx scripts/test-connection.ts
参考文档(references/)
references/connection-strings.md- 连接字符串格式完整指南,涵盖池化与非池化连接references/drizzle-setup.md- 使用 Neon 设置 Drizzle ORM 的分步指南references/prisma-setup.md- 使用 Neon 适配器设置 Prismareferences/branching-guide.md- Neon 数据库分支的综合指南references/migration-strategies.md- 适用于不同 ORM 和工具的迁移模式references/common-errors.md- 扩展故障排除指南
Claude 应在以下情况下加载这些文档:
- 排查连接问题时,加载
connection-strings.md - 用户想使用 Drizzle ORM 时,加载
drizzle-setup.md - 用户想使用 Prisma 时,加载
prisma-setup.md - 用户询问预览环境或数据库分支时,加载
branching-guide.md - 遇到特定错误消息时,加载
common-errors.md
资源(assets/)
assets/schema-example.sql- 包含用户、帖子和评论的数据库架构示例assets/drizzle-schema.ts- 完整的 Drizzle 架构模板assets/prisma-schema.prisma- 完整的 Prisma 架构模板
进阶主题
数据库分支工作流
Neon 的分支功能支持类似 Git 的数据库工作流:
分支类型:
- 主分支:生产数据库
- 开发分支:长期使用的开发数据库
- PR 分支:用于预览部署的临时分支
- 测试分支:隔离的测试环境
创建分支:
# Create from main
neonctl branches create --name dev --parent main
# Create from specific point in time (PITR)
neonctl branches create --name restore-point --parent main --timestamp "2025-10-28T10:00:00Z"
# Create from another branch
neonctl branches create --name feature --parent dev
管理分支:
# List branches
neonctl branches list
# Get connection string
neonctl connection-string dev
# Delete branch
neonctl branches delete feature
# Reset branch to match parent
neonctl branches reset dev --parent main
使用场景:
- 预览部署:每个 PR 创建一个分支,合并后删除
- 测试:创建分支、运行测试,然后删除
- 调试:从生产环境的指定时间点创建分支
- 开发:分别设置开发、暂存和生产分支
重要:
- 免费套餐中的所有分支共享计算资源限额
- 每个分支都可以使用独立的计算设置(付费套餐)
- 数据更改采用写时复制(即时完成,无需复制)
- 所有分支均适用保留期限
深入了解连接池
连接池的工作原理:
- 客户端请求连接
- 连接池分配现有的空闲连接,或创建新连接
- 客户端使用连接执行查询
- 连接返回连接池(可重复使用)
连接池与非连接池:
| 功能 | 连接池(-pooler.) | 非连接池 |
|---|---|---|
| 使用场景 | 无服务器、边缘函数 | 长时间运行的服务 |
| 最大连接数 | 约 10,000(共享) | 约 100(每个数据库) |
| 连接复用 | 是 | 否 |
| 延迟 | 增加 1–2 ms 开销 | 直接连接 |
| 空闲超时 | 60 秒 | 可配置 |
连接池已满时:
Error: connection pool exhausted
解决方案:
- 使用连接池连接字符串(最常见的解决方法)
- 升级到更高套餐(增加连接槽位)
- 优化查询(缩短连接占用时间)
- 实现连接重试逻辑
- 使用只读副本(分散负载)
监控:
- 在 Neon 控制面板中检查连接使用情况
- 设置使用率超过 80% 时的警报
- 监控查询时长(长时间运行的查询会占用连接)
优化查询性能
使用 EXPLAIN ANALYZE:
const result = await sql`
EXPLAIN ANALYZE
SELECT * FROM users WHERE email = ${email}
`;
创建索引:
await sql`CREATE INDEX idx_users_email ON users(email)`;
await sql`CREATE INDEX idx_posts_user_id ON posts(user_id)`;
使用 Drizzle 索引:
import { pgTable, serial, text, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
email: text('email').notNull().unique()
}, (table) => ({
emailIdx: index('email_idx').on(table.email)
}));
批量查询:
// ❌ Bad: N+1 queries
for (const user of users) {
const posts = await sql`SELECT * FROM posts WHERE user_id = ${user.id}`;
}
// ✅ Good: Single query with JOIN
const postsWithUsers = await sql`
SELECT users.*, posts.*
FROM users
LEFT JOIN posts ON posts.user_id = users.id
`;
使用预处理语句(Drizzle):
const getUserByEmail = db.select().from(users).where(eq(users.email, sql.placeholder('email'))).prepare('get_user_by_email');
// Reuse prepared statement
const user1 = await getUserByEmail.execute({ email: '[email protected]' });
const user2 = await getUserByEmail.execute({ email: '[email protected]' });
安全最佳实践
1. 切勿公开连接字符串
// ❌ Bad
const sql = neon('postgresql://user:pass@host/db');
// ✅ Good
const sql = neon(process.env.DATABASE_URL!);
2. 使用行级安全(RLS)
-- Enable RLS
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- Create policy
CREATE POLICY "Users can only see their own posts"
ON posts
FOR SELECT
USING (user_id = current_user_id());
3. 验证输入
// ✅ Validate before query
const emailSchema = z.string().email();
const email = emailSchema.parse(input.email);
const user = await sql`SELECT * FROM users WHERE email = ${email}`;
4. 限制查询结果
// ✅ Always paginate
const page = Math.max(1, parseInt(request.query.page));
const limit = 50;
const offset = (page - 1) * limit;
const users = await sql`
SELECT * FROM users
ORDER BY created_at DESC
LIMIT ${limit} OFFSET ${offset}
`;
5. 为分析任务使用只读角色
CREATE ROLE readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
依赖项
必需:
@neondatabase/serverless@^1.0.2- Neon 无服务器 Postgres 客户端(基于 HTTP/WebSocket)@vercel/postgres@^0.10.0- Vercel Postgres 客户端(可替代 Neon 直连,适用于 Vercel)
可选:
drizzle-orm@^0.44.7- TypeScript ORM(兼容边缘环境,推荐)drizzle-kit@^0.31.0- Drizzle 架构迁移与内省工具@prisma/client@^6.10.0- Prisma ORM(仅适用于 Node.js,不兼容边缘环境)@prisma/adapter-neon@^6.10.0- 用于 Neon 无服务器的 Prisma 适配器neonctl@^2.16.1- 用于管理数据库的 Neon CLIzod@^3.24.0- 用于输入清理的架构验证工具
官方文档
- Neon 文档:https://neon.tech/docs
- Neon Serverless 包:https://github.com/neondatabase/serverless
- Vercel Postgres:https://vercel.com/docs/storage/vercel-postgres
- Vercel Storage(全部):https://vercel.com/docs/storage
- Neon 分支指南:https://neon.tech/docs/guides/branching
- Neonctl CLI:https://neon.tech/docs/reference/cli
- Drizzle + Neon:https://orm.drizzle.team/docs/quick-postgresql/neon
- Prisma + Neon:https://www.prisma.io/docs/orm/overview/databases/neon
- Context7 库 ID:
/github/neondatabase/serverless、/github/vercel/storage
软件包版本(已验证,2025-10-29)
{
"dependencies": {
"@neondatabase/serverless": "^1.0.2",
"@vercel/postgres": "^0.10.0",
"drizzle-orm": "^0.44.7"
},
"devDependencies": {
"drizzle-kit": "^0.31.0",
"neonctl": "^2.16.1"
}
}
最新 Prisma(如有需要):
{
"dependencies": {
"@prisma/client": "^6.10.0",
"@prisma/adapter-neon": "^6.10.0"
},
"devDependencies": {
"prisma": "^6.10.0"
}
}
生产环境示例
此技能基于 Neon 和 Vercel Postgres 的生产部署:
- Cloudflare Workers:API 每日处理 50K+ 次请求,连接错误为 0
- Vercel Next.js App:电商网站,月用户数达 100K+
- 构建时间:<5 分钟(初始设置),<30s(部署)
- 错误:0(已预防全部 15 个已知问题)
- 验证:✅ 连接池,✅ SQL 注入防护,✅ 事务处理,✅ 分支工作流
故障排除
问题:Error: connection pool exhausted
解决方案:
- 确认你使用的是池化连接字符串(以
-pooler.region.aws.neon.tech结尾) - 在 Neon 控制面板中检查连接使用情况
- 如果持续达到限制,请升级到更高的套餐
- 优化查询以缩短连接占用时间
问题:Error: TCP connections are not supported
解决方案:
- 使用
@neondatabase/serverless,而不是pg或postgres.js - 确认未导入传统的 Postgres 客户端
- 检查打包内容是否包含基于 HTTP/WebSocket 的客户端
问题:Error: database "xyz" does not exist
解决方案:
- 确认
DATABASE_URL指向正确的数据库 - 如果使用 Neon 分支,请确保分支仍然存在
- 检查连接字符串格式(确认没有拼写错误)
问题:冷启动时查询缓慢
解决方案:
- Neon 在 5 分钟无活动后会自动暂停(免费套餐)
- 唤醒后的首次查询需要约 1-2 秒
- 将查询超时设为 >= 10s,以应对冷启动
- 在付费套餐中禁用自动暂停,以使用始终在线的数据库
问题:PrismaClient is unable to be run in the browser
解决方案:
- Prisma 无法在 Cloudflare Workers(V8 隔离环境)中运行
- 使用 Drizzle ORM 作为兼容边缘运行时的 ORM
- Prisma 可在 Vercel Edge/Node.js 运行时中配合
@prisma/adapter-neon使用
问题:分支之间的迁移版本冲突
解决方案:
- 先在主分支上运行迁移
- 迁移完成后再创建功能分支
- 或在合并前重置分支架构:
neonctl branches reset feature --parent main
完整设置清单
使用此清单验证你的设置:
- 已安装软件包(
@neondatabase/serverless或@vercel/postgres) - 已创建 Neon 数据库(或已配置 Vercel Postgres)
- 已获取池化连接字符串(以
-pooler.结尾) - 连接字符串包含
?sslmode=require - 已配置环境变量(
DATABASE_URL或POSTGRES_URL) - 已创建数据库架构(原始 SQL、Drizzle 或 Prisma)
- 查询使用模板标签语法(
sql`...`) - 事务使用适当的 try/catch 并释放连接
- 已验证连接池(使用池化连接字符串)
- ORM 选择适合运行时(边缘运行时使用 Drizzle,Node.js 使用 Prisma)
- 已在本地使用开发数据库测试
- 已在生产/预览环境中部署并测试
- 已在 Neon 控制面板中设置连接监控
有疑问?遇到问题?
- 查看
references/common-errors.md了解更多故障排除信息 - 核对 7 步设置流程中的所有步骤
- 查看官方文档:https://neon.tech/docs
- 确保在无服务器环境中使用池化连接字符串
- 确认连接字符串中包含
sslmode=require - 使用
scripts/test-connection.ts测试连接