跳转至

Neon 无服务器 PostgreSQL 完全指南

为什么要学 Neon

  1. 像 Git 一样管理数据库:Neon 支持数据库分支(Branching),就像 Git 分支一样。你可以从生产数据库瞬间创建一个分支用于开发或测试,完全隔离且不影响生产。创建分支是瞬时的(copy-on-write),不复制数据。

  2. 真正的 Serverless,按需计费:Neon 的计算节点在没有查询时自动缩容到零,有请求时自动唤醒。你只为实际使用的计算时间和存储空间付费。对于开发环境和低流量应用,成本可以降低 90% 以上。

  3. 计算与存储分离架构:Neon 将 PostgreSQL 的计算层和存储层分离。存储层使用自研的 Pageserver,基于 S3 构建,支持时间旅行(Time Travel)查询历史数据。计算节点可以独立扩缩容。

  4. 完全兼容 PostgreSQL:Neon 运行的就是标准 PostgreSQL(不是仿制品),支持所有 PG 扩展(pgvector、PostGIS 等)、所有 ORM(Prisma、Drizzle、SQLAlchemy 等)、所有工具。迁移到 Neon 不需要修改代码。

  5. 开发者体验极佳:Web 控制台可视化管理、CLI 工具、GitHub 集成(Preview Branches)、连接池内置(PgBouncer)、免费层慷慨(0.5 GB 存储 + 分支)。


核心概念详解

Neon 架构(白话解释)

传统 PostgreSQL 是把数据和计算绑在同一台服务器上。Neon 把它们拆开了:

  • 计算(Compute):运行 PostgreSQL 实例的虚拟机。可以启动、停止、扩缩。没有查询时关机(scale to zero),有查询时自动开机。
  • 存储(Storage / Pageserver):专门存数据的层,基于云对象存储(S3)。支持版本化,可以回溯到任意时间点。
  • Safekeepers:类似 WAL(Write-Ahead Log)接收者,确保数据持久性。

数据库分支(Branching)

概念Git 类比Neon 实现
主分支main生产数据库
功能分支feature/xxx开发/测试数据库
创建分支git checkout -b瞬时,copy-on-write
分支数据复制文件共享底层页面,按需复制
删除分支git branch -d释放独占数据
分支时间点git checkout commit可以从历史时间点创建分支
生产分支 (main)
├── 开发分支 (dev)       → 开发者日常开发
├── PR预览分支 (pr-123)  → GitHub PR 自动创建
├── 测试分支 (staging)   → QA 团队测试
└── 调试分支 (debug)     → 从昨天15:00的数据创建,排查问题

自动扩缩容

无请求 ────────────→ 有请求 ────────────→ 高负载 ────────────→ 无请求
 (0 CU, 休眠)       (唤醒, 0.25 CU)     (自动扩到 8 CU)     (缩回 0 CU)
     |                    |                    |                    |
 不计费              ~300ms冷启动          自动扩容             自动休眠

CU (Compute Unit) = 1 vCPU + 4GB RAM。免费层给 0.25 CU。

Neon vs 传统托管 PostgreSQL 对比

特性NeonAWS RDSSupabasePlanetScale
数据库PostgreSQLPostgreSQL/MySQLPostgreSQLMySQL (Vitess)
Scale to Zero支持不支持不支持支持(付费版)
分支支持(核心功能)不支持不支持支持
时间旅行支持需要手动快照PITR恢复不支持
计算存储分离
连接池内置需自建内置(Supavisor)内置
冷启动~300msN/A(常开)N/A~1-2s
免费层0.5GB + 分支12个月750h500MB5GB
价格模型按用量按实例时间按实例按行读写
扩展支持全部PG扩展大部分大部分MySQL限制

安装与配置

注册并创建项目

  1. 访问 https://neon.tech 注册账号
  2. 创建 Project(选择区域:AWS us-east-1 / eu-central-1 等)
  3. 获取连接字符串

安装 Neon CLI

# macOS
brew install neonctl

# npm 全局安装
npm install -g neonctl

# 验证安装
neonctl --version

# 登录
neonctl auth

CLI 基本操作

# 列出项目
neonctl projects list

# 创建新项目
neonctl projects create --name my-project --region-id aws-us-east-1

# 列出分支
neonctl branches list --project-id <project-id>

# 创建分支
neonctl branches create --project-id <project-id> --name dev

# 从特定时间点创建分支(时间旅行)
neonctl branches create --project-id <project-id> --name debug-branch --parent main --timestamp "2024-12-01T15:00:00Z"

# 获取连接字符串
neonctl connection-string --project-id <project-id> --branch-name dev

# 删除分支
neonctl branches delete --project-id <project-id> --branch-name dev

# 执行 SQL
neonctl sql --project-id <project-id> --query "SELECT version();"

连接字符串格式

postgresql://username:password@ep-xxx-yyy-123456.us-east-1.aws.neon.tech/dbname?sslmode=require

关键参数: - sslmode=require:Neon 要求 SSL 连接 - 端点 ID(ep-xxx-yyy-123456):用于路由到正确的计算节点

连接池配置

Neon 内置连接池(基于 PgBouncer),通过在连接字符串中添加 -pooler 使用:

# 直连(适合迁移、长事务)
postgresql://user:pass@ep-xxx.neon.tech/db?sslmode=require

# 连接池(适合无服务器环境,如 Vercel/Cloudflare)
postgresql://user:pass@ep-xxx-pooler.neon.tech/db?sslmode=require

快速上手:5 分钟最小示例

Node.js + @neondatabase/serverless

mkdir neon-demo && cd neon-demo
npm init -y
npm install @neondatabase/serverless dotenv

.env

DATABASE_URL=postgresql://user:pass@ep-xxx-pooler.neon.tech/dbname?sslmode=require

index.js

import { neon } from '@neondatabase/serverless';
import 'dotenv/config';

const sql = neon(process.env.DATABASE_URL);

async function main() {
  // 创建表
  await sql`
    CREATE TABLE IF NOT EXISTS todos (
      id SERIAL PRIMARY KEY,
      title TEXT NOT NULL,
      completed BOOLEAN DEFAULT false,
      created_at TIMESTAMP DEFAULT NOW()
    )
  `;

  // 插入数据
  await sql`INSERT INTO todos (title) VALUES ('学习 Neon')`;
  await sql`INSERT INTO todos (title) VALUES ('写示例代码')`;

  // 查询数据
  const todos = await sql`SELECT * FROM todos ORDER BY id`;
  console.log('所有待办:', todos);

  // 参数化查询
  const search = '学习';
  const results = await sql`
    SELECT * FROM todos WHERE title LIKE ${'%' + search + '%'}
  `;
  console.log('搜索结果:', results);

  // 更新
  await sql`UPDATE todos SET completed = true WHERE id = 1`;

  // 事务
  await sql.transaction([
    sql`INSERT INTO todos (title) VALUES ('任务A')`,
    sql`INSERT INTO todos (title) VALUES ('任务B')`,
  ]);

  console.log('完成!');
}

main().catch(console.error);

运行:

node index.js


进阶用法

场景一:Prisma 集成

npm install prisma @prisma/client @prisma/adapter-neon @neondatabase/serverless
npx prisma init

prisma/schema.prisma

generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["driverAdapters"]
}

datasource db {
  provider  = "postgresql"
  url       = env("DATABASE_URL")
  directUrl = env("DIRECT_DATABASE_URL") // 直连用于迁移
}

model User {
  id    Int     @id @default(autoincrement())
  email String  @unique
  name  String?
  posts Post[]
}

model Post {
  id        Int      @id @default(autoincrement())
  title     String
  content   String?
  published Boolean  @default(false)
  author    User     @relation(fields: [authorId], references: [id])
  authorId  Int
  createdAt DateTime @default(now())
}

.env

DATABASE_URL=postgresql://user:pass@ep-xxx-pooler.neon.tech/db?sslmode=require
DIRECT_DATABASE_URL=postgresql://user:pass@ep-xxx.neon.tech/db?sslmode=require

npx prisma db push

使用 Neon Serverless Driver + Prisma

import { Pool, neonConfig } from '@neondatabase/serverless';
import { PrismaNeon } from '@prisma/adapter-neon';
import { PrismaClient } from '@prisma/client';
import ws from 'ws';

neonConfig.webSocketConstructor = ws;
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const adapter = new PrismaNeon(pool);
const prisma = new PrismaClient({ adapter });

// 正常使用 Prisma
const users = await prisma.user.findMany({
  include: { posts: true },
});

场景二:Drizzle ORM 集成

npm install drizzle-orm @neondatabase/serverless
npm install -D drizzle-kit

src/db/schema.ts

import { pgTable, serial, text, boolean, 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(),
  title: text('title').notNull(),
  content: text('content'),
  published: boolean('published').default(false),
  authorId: integer('author_id').references(() => users.id),
});

src/db/index.ts

import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import * as schema from './schema';

const sql = neon(process.env.DATABASE_URL!);
export const db = drizzle(sql, { schema });

// 使用
const allUsers = await db.select().from(schema.users);
const userWithPosts = await db.query.users.findMany({
  with: { posts: true },
});

场景三:GitHub Preview Branches

在 Neon 控制台中启用 GitHub 集成后,每个 PR 会自动创建一个数据库分支:

# .github/workflows/preview.yml
name: Preview Environment
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Create Neon Branch
        id: create-branch
        uses: neondatabase/create-branch-action@v5
        with:
          project_id: ${{ secrets.NEON_PROJECT_ID }}
          branch_name: pr-${{ github.event.number }}
          api_key: ${{ secrets.NEON_API_KEY }}

      - name: Run Migrations
        run: npx prisma migrate deploy
        env:
          DATABASE_URL: ${{ steps.create-branch.outputs.db_url_with_pooler }}

      - name: Comment on PR
        uses: actions/github-script@v7
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: `Preview branch created!\nDB: \`${{ steps.create-branch.outputs.branch_id }}\``
            })

场景四:时间旅行查询

# 从24小时前创建分支
neonctl branches create \
  --project-id <id> \
  --name recovery-branch \
  --parent main \
  --timestamp "2024-12-14T10:00:00Z"

# 连接到恢复分支查看历史数据
psql "$(neonctl connection-string --branch-name recovery-branch)"
-- 在恢复分支中查看历史数据
SELECT * FROM users WHERE deleted_at IS NOT NULL;

-- 恢复误删的数据:从恢复分支导出,导入到主分支
\copy (SELECT * FROM users WHERE id = 42) TO '/tmp/recovered_user.csv' CSV

场景五:Vercel + Next.js 集成

# 在 Vercel 中添加 Neon 集成
# Vercel Dashboard → Integrations → Neon

# 或手动设置环境变量
# DATABASE_URL(池化连接,用于运行时)
# DIRECT_DATABASE_URL(直连,用于迁移)
// app/api/users/route.ts (Next.js App Router)
import { neon } from '@neondatabase/serverless';

export async function GET() {
  const sql = neon(process.env.DATABASE_URL!);
  const users = await sql`SELECT * FROM users LIMIT 10`;
  return Response.json(users);
}

export async function POST(request: Request) {
  const sql = neon(process.env.DATABASE_URL!);
  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 });
}

场景六:pgvector 向量搜索

-- 启用 pgvector 扩展
CREATE EXTENSION IF NOT EXISTS vector;

-- 创建带向量列的表
CREATE TABLE documents (
  id SERIAL PRIMARY KEY,
  content TEXT NOT NULL,
  embedding vector(1536)  -- OpenAI ada-002 维度
);

-- 创建索引
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

-- 插入数据(嵌入向量由应用层生成)
INSERT INTO documents (content, embedding)
VALUES ('Neon 是 Serverless PostgreSQL', '[0.1, 0.2, ...]');

-- 余弦相似度搜索
SELECT id, content, 1 - (embedding <=> '[0.1, 0.2, ...]') AS similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'
LIMIT 5;

场景七:自动扩缩容配置

# 通过 Neon API 配置自动扩缩
curl -X PATCH \
  "https://console.neon.tech/api/v2/projects/<project-id>/endpoints/<endpoint-id>" \
  -H "Authorization: Bearer $NEON_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endpoint": {
      "autoscaling_limit_min_cu": 0.25,
      "autoscaling_limit_max_cu": 4,
      "suspend_timeout_seconds": 300
    }
  }'
参数说明推荐值
autoscaling_limit_min_cu最小计算单元0 (scale to zero) 或 0.25
autoscaling_limit_max_cu最大计算单元根据负载,1-8
suspend_timeout_seconds空闲多久后休眠300 (5分钟)

场景八:数据库迁移工作流

# 1. 从 main 创建迁移分支
neonctl branches create --name migration-test --parent main

# 2. 在迁移分支上运行迁移
DATABASE_URL=$(neonctl connection-string --branch-name migration-test) \
  npx prisma migrate deploy

# 3. 验证迁移结果
DATABASE_URL=$(neonctl connection-string --branch-name migration-test) \
  npx prisma db seed

# 4. 确认无误后,在 main 上执行
DATABASE_URL=$(neonctl connection-string --branch-name main) \
  npx prisma migrate deploy

# 5. 清理迁移分支
neonctl branches delete --branch-name migration-test

常见问题与排错

问题一:冷启动延迟

症状:第一次查询需要 300ms-2s。

优化: 1. 设置 suspend_timeout_seconds 为较大值(如 600) 2. 使用最低 CU 设为 0.25(不完全缩容到 0) 3. 使用连接池端点减少连接建立时间 4. 设置预热查询(定期 ping)

问题二:连接数限制

症状too many connections 错误。

解决: - 使用池化连接端点(-pooler) - Serverless 环境(Vercel/Cloudflare Workers)必须使用池化连接 - 直连的连接数限制取决于 CU 大小

CU 大小    最大连接数
0.25 CU    112
0.5 CU     225
1 CU       450
2 CU       901
4 CU       1802

问题三:SSL 连接错误

Error: no pg_hba.conf entry for host "x.x.x.x", user "xxx", database "xxx", SSL off

解决:确保连接字符串包含 sslmode=require

// Node.js
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  ssl: { rejectUnauthorized: true },
});

问题四:分支存储空间

每个分支共享主分支的数据(copy-on-write),只有修改过的页面占用额外空间。但大量写入操作的分支会占用更多存储。

# 查看分支存储使用
neonctl branches list --project-id <id> --output json | jq '.[] | {name, logical_size}'

问题五:如何从现有 PostgreSQL 迁移

# 1. 从旧数据库导出
pg_dump -h old-host -U user -d dbname -Fc -f backup.dump

# 2. 导入到 Neon
pg_restore -h ep-xxx.neon.tech -U user -d dbname backup.dump

# 或使用 Neon 的导入功能
neonctl import --project-id <id> --source "postgresql://user:pass@old-host/db"

问题六:查询性能调优

-- 启用查询统计扩展
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;

-- 查看慢查询
SELECT query, mean_exec_time, calls, total_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

-- 分析查询执行计划
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM users WHERE email = 'test@example.com';

参考资源

  • 官方文档:https://neon.tech/docs
  • Neon CLI 文档:https://neon.tech/docs/reference/neon-cli
  • GitHub:https://github.com/neondatabase/neon
  • Neon Serverless Driver:https://github.com/neondatabase/serverless
  • Pricing:https://neon.tech/pricing
  • Neon + Prisma 指南:https://neon.tech/docs/guides/prisma
  • Neon + Drizzle 指南:https://neon.tech/docs/guides/drizzle
  • Neon + Vercel 指南:https://neon.tech/docs/guides/vercel
  • Neon API 参考:https://api-docs.neon.tech/reference
  • Neon 社区论坛:https://community.neon.tech/