Vercel 与 TiDB Cloud Starter:AI 应用全栈数据库实践
有效阅读仅保存在当前浏览器,用于「为你」推荐;暂停或清除记录。
DataHot 速览
文章介绍在 Vercel 上运行 AI 应用时,如何选择能适配 serverless 和 edge runtime 的数据库,并重点推荐 TiDB Cloud Starter。它指出边缘运行时会破坏传统 TCP 连接池,而 TiDB Cloud Starter 通过 HTTPS 连接;从 v0.dev 默认数据库迁移到 TiDB 只需改连接字符串。TiDB 提供原生 VECTOR 类型,可把 embeddings 与业务行数据放在一起,避免额外同步向量库;Vercel Marketplace 集成会自动写入连接环境变量。TiDB 是开源分布式 SQL 数据库,兼容 MySQL,TiDB Cloud Starter 为全托管、自动扩缩容版本,约一秒完成配置。
原收录理由(待核验):对数据从业者,本文具体讨论 serverless/edge 场景下数据库连接、向量存储与部署集成,涉及 AI 应用数据架构的选型和落地。
本文目录 14 节
- 关键要点
- 为什么 Vercel 应用需要一个为边缘构建的无服务器数据库
- 连接池是无服务器所暴露的问题
- 本指南涵盖的运行时
- 从 v0.dev 或 Lovable 原型到已部署的 Vercel 应用
- 启动你的 TiDB Cloud Starter 集群(你的 Vercel 数据库)
- 选择你的入口:Cloud、Starter 或 Zero
- 获取你的连接详细信息
- 为 AI 应用数据建模:会话、聊天日志、偏好设置和检索状态
- 使用 TiDB 向量搜索存储嵌入并驱动检索
- 从边缘连接:Vercel Edge Functions 和 Cloudflare Workers
- 使用 TiDB Cloud 集成在 Vercel 上部署
- TiDB Cloud Starter 与 Vercel Postgres、Neon 和 Supabase 对比
- 启动你的免费 TiDB Cloud Starter 集群
译文
AI 逐段翻译关键要点
- 边缘运行时不适合 TCP 连接池。TiDB Cloud Starter 转而通过 HTTPS 连接。
- 把 v0.dev 起始模板的数据库换成 TiDB,只需更改连接字符串。
- 原生
VECTOR类型让嵌入向量与数据行放在一起,无需同步任何存储。 - Vercel Marketplace 集成会为你写入连接变量。
你有一个运行在 Vercel 上的 AI 应用,或者一个由 v0.dev 在几分钟内生成的原型,现在它需要一个真正的数据库。这个选择比看起来更难,因为无服务器函数和边缘运行时打破了传统数据库赖以构建的假设:长寿命 TCP 连接、有界连接池,以及在请求之间保持温暖的进程。
本指南涵盖完整路径。选择一个能在无服务器和边缘运行时下存活的 Vercel 数据库,为 AI 应用实际存储的数据建模,把嵌入向量放在这些数据旁边而不是单独的向量存储中,从 Vercel Edge Functions 和 Cloudflare Workers 连接,并通过 Vercel Marketplace 集成进行部署,它会为你配置好环境变量。
TiDB 是一个开源分布式 SQL 数据库,在一个引擎中同时支持事务处理和分析处理。TiDB Cloud Starter 是其全托管、自动扩缩容的部署选项,兼容 MySQL,约一秒内即可完成配置。Vercel 是 Next.js 的创造者,也是大多数 AI 应用部署的平台。两者配合得很好,本文的其余部分将展示具体做法。
为什么 Vercel 应用需要一个为边缘构建的无服务器数据库
Vercel 以无服务器函数和边缘函数的形式运行你的后端,而不是长期运行的服务器,这改变了数据库必须支持的内容。无服务器函数按需执行,自动扩缩容,并在请求完成时终止。传统 API 则在请求-响应接口背后保持进程存活,维持其连接池开放,并在多个请求之间复用连接。
选择数据库时,有三点差异很重要:
- 生命周期。无服务器函数响应事件而运行特定的一段代码,然后退出。传统 API 则常驻并管理自己的基础设施。
- 可移植性。无服务器函数绑定到某个平台运行时。传统 API 则可以在任何能够托管进程的地方运行。
- 部署速度。无服务器函数以更短的周期发布,因为无需配置基础设施,这正是 AI 应用在其上迭代的原因。
连接池是无服务器所暴露的问题
每次函数调用都会打开自己的数据库连接。在突发流量下,几百个并发调用就会变成几百个连接,传统的 MySQL 或 Postgres 实例开始拒绝它们。团队通常的应对是添加外部连接池器,这增加了一跳、一个需要运维的组件,以及一种新的故障模式。
TiDB Cloud Starter 采取了不同的路径,使用 TiDB Cloud 无服务器驱动,它通过 HTTPS 而非 TCP 与数据库通信。由于路径中没有持久连接,也就没有连接池会被耗尽。每次调用发起一个 HTTPS 请求,获取结果,然后结束。
本指南涵盖的运行时
以下所有内容都适用于三个目标:Vercel 无服务器函数(Node.js 运行时)、Vercel Edge Functions 和 Cloudflare Workers。边缘运行时是最严格的情况,因为它们完全不允许原始 TCP 套接字。在那里,使用 HTTPS 的驱动不是一种优化,而是唯一能连接的方式。
从 v0.dev 或 Lovable 原型到已部署的 Vercel 应用
像 v0.dev 和 Lovable 这样的 AI 构建器能在几分钟内搭建出一个可运行的 Next.js 应用,然后交给你一个期望数据库 URL、并自带通用 Postgres 或 SQLite 起始模板的项目。把这个起始模板换成 TiDB Cloud Starter 需要三步:原型、连接、部署。
原型。照常生成应用。输出是一个标准的 Next.js 项目,带有一个 API 层、一个数据访问文件,以及一个期望 .env 或 .env.local 中 DATABASE_URL 的文件。
连接。找到生成的应用读取该变量的位置。在 Prisma 项目中,它是 datasource 块中的 prisma/schema.prisma。在 Drizzle 或 Kysely 项目中,它是客户端初始化文件。将其指向 TiDB Cloud Starter 连接字符串:
DATABASE_URL='mysql://<user>:<password>@<host>:4000/<database>?sslaccept=strict'如果脚手架假设的是 Postgres,请将 provider 改为 mysql 并重新生成客户端。TiDB 使用 MySQL 有线协议,因此任何 MySQL 驱动、ORM 或迁移工具都可以不加修改地使用。
部署。推送到 GitHub 并将仓库导入 Vercel,或使用本文后面介绍的 Marketplace 集成,它会在部署期间设置连接变量。
在原型阶段而不是更晚进行替换的原因:生成应用自带的起始数据库是按演示规模配置的。TiDB Cloud Starter 在同一 MySQL 兼容接口上水平扩缩容,因此原型和生产系统针对同一引擎运行相同的代码。
启动你的 TiDB Cloud Starter 集群(你的 Vercel 数据库)
配置数据库只需不到一分钟。登录 TiDB Cloud,按照屏幕上的说明创建一个免费的 TiDB Cloud Starter 集群,然后点击集群名称将其打开。
选择你的入口:Cloud、Starter 或 Zero
存在三种入口,正确的选择取决于你要构建什么。
- TiDB Cloud Zero通过一次 API 调用配置一个临时实例,无需注册,也无需账单信息。实例会在 30 天后过期,除非你认领它们,而认领会将其转换为持久的 TiDB Cloud Starter 实例,数据与模式会自动迁移。Zero 是为智能体驱动的工作流、演示和 CI 而构建的,目前处于公开预览阶段,网址为 zero.tidbcloud.com。
- TiDB Cloud Starter 是 TiDB Cloud 内免费的全托管无服务器层级。对于需要持久化真实数据的 Vercel 应用,这是默认选择。
- TiDB Cloud 是完整平台。Starter 位于其中,而随着应用成长,Essential 和 Dedicated 会增加容量、隔离性和企业级控制。
如果由代理或脚本执行预配,请从 Zero 开始。如果是由人工构建一个需要持续运行超过一周的应用,请从 Starter 开始。
获取你的连接详细信息
在集群视图中,点击 Connect 以打开连接详细信息,并记下 Host、Port 和 User 的值。你将使用它们来构建 ORM 读取的连接字符串。
将这些值存储为 Vercel 环境变量,而不是将它们提交到代码中。在 Vercel 仪表盘中,转到 Settings > Environment Variables,添加 DATABASE_URL,并将其作用域限定为需要它的环境。在本地,将其保存在 .env.local 中,Next.js 默认会将其从 git 中排除。
在继续之前验证连接性:
mysql --connect-timeout 15 -u '<user>' -h '<host>' -P 4000 -D '<database>' \
--ssl-mode=VERIFY_IDENTITY --ssl-ca=/etc/ssl/certs/ca-certificates.crt -p为 AI 应用数据建模:会话、聊天日志、偏好设置和检索状态
AI 应用存储一组可预测的内容:对话会话、其中的消息、代理执行的操作,以及塑造未来响应的偏好设置。Prisma 可以干净地为所有这些建模,其机制与任何 Prisma 项目使用的机制相同。
安装适配器、无服务器驱动程序和 Prisma CLI:
npm install @tidbcloud/prisma-adapter @tidbcloud/serverless
npm install prisma --save-dev在 prisma/schema.prisma 中启用驱动程序适配器并定义模型:
generator client {
provider = "prisma-client-js"
previewFeatures = ["driverAdapters"]
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model Session {
id String @id @default(uuid())
userId String @map("user_id") @db.VarChar(64)
title String? @db.VarChar(255)
createdAt DateTime @default(now()) @map("created_at")
messages Message[]
actions AgentAction[]
@@index([userId, createdAt])
@@map("sessions")
}
model Message {
id BigInt @id @default(autoincrement())
sessionId String @map("session_id") @db.VarChar(36)
role String @db.VarChar(16)
content String @db.Text
tokenCount Int? @map("token_count")
createdAt DateTime @default(now()) @map("created_at")
session Session @relation(fields: [sessionId], references: [id])
@@index([sessionId, createdAt])
@@map("messages")
}
model AgentAction {
id BigInt @id @default(autoincrement())
sessionId String @map("session_id") @db.VarChar(36)
tool String @db.VarChar(64)
input Json?
output Json?
status String @db.VarChar(16)
createdAt DateTime @default(now()) @map("created_at")
session Session @relation(fields: [sessionId], references: [id])
@@index([sessionId, status])
@@map("agent_actions")
}
model UserPreference {
userId String @id @map("user_id") @db.VarChar(64)
settings Json
updatedAt DateTime @updatedAt @map("updated_at")
@@map("user_preferences")
}导出连接字符串并推送 schema:
export DATABASE_URL='mysql://<user>:<password>@<host>:4000/<database>?sslaccept=strict'
npx prisma db push
npx prisma generate关于适配器行为有两点说明。prisma db push、Prisma Migrate 和内省使用传统 TCP 连接,因此请从你的机器或 CI 运行它们,而不是从边缘函数运行。Prisma Client 查询通过适配器经 HTTPS 进行。每个模块初始化一次客户端:
import { PrismaTiDBCloud } from '@tidbcloud/prisma-adapter';
import { PrismaClient } from '@prisma/client';
const adapter = new PrismaTiDBCloud({ url: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });对于早于 v6.6.0 的适配器版本,请先使用 connect()(来自 @tidbcloud/serverless)构建连接,并将其传递给 new PrismaTiDBCloud(connection)。TiDB Cloud 无服务器驱动程序和 Prisma 集成文章更深入地介绍了适配器、事务以及与 TCP 路径的差异。
使用 TiDB 向量搜索存储嵌入并驱动检索
TiDB 具有原生 VECTOR 数据类型,因此嵌入与其所属的会话和消息存储在同一个数据库中。无需预配第二个系统,无需同步作业,也不存在向量存储与应用程序数据库不一致的窗口期。
添加一个包含固定维度向量列和 HNSW 索引的文档表。维度必须与你的嵌入模型匹配,1536 与 OpenAI 的 text-embedding-3-small 匹配:
CREATE TABLE documents (
id BIGINT PRIMARY KEY AUTO_RANDOM,
session_id VARCHAR(36),
content TEXT,
embedding VECTOR(1536),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
VECTOR INDEX idx_embedding ((VEC_COSINE_DISTANCE(embedding)))
);Prisma 没有对 VECTOR 的原生映射,因此请使用原始 SQL 创建该列,并通过无服务器驱动程序或 $queryRaw 查询它。检索就是一条普通的 SQL 查询:
import { connect } from '@tidbcloud/serverless';
const conn = connect({ url: process.env.DATABASE_URL });
const results = await conn.execute(
`SELECT id, content, VEC_COSINE_DISTANCE(embedding, ?) AS distance
FROM documents
ORDER BY distance
LIMIT 10`,
[JSON.stringify(queryEmbedding)]
);当你需要按租户、会话或用户进行过滤时,请先运行最近邻搜索再过滤结果,因为置于向量排序之前的 WHERE 子句可能会阻止索引被使用:
SELECT * FROM (
SELECT id, session_id, content,
VEC_COSINE_DISTANCE(embedding, '[0.1, 0.2, ...]') AS distance
FROM documents
ORDER BY distance
LIMIT 50
) t
WHERE session_id = '<session-id>'
ORDER BY distance
LIMIT 10;收益体现在写入时。将消息、其嵌入和代理操作存储在一起的插入会作为一个事务提交,因此检索状态和应用状态保持一致。有关距离函数、索引行为以及结合全文的混合搜索,请参阅 TiDB 向量搜索文档。
从边缘连接:Vercel Edge Functions 和 Cloudflare Workers
边缘运行时是数据库选择受到考验的地方,因为它们禁止原始 TCP 套接字。任何基于 TCP 连接构建的驱动程序在那里都会失败,无论其如何配置。TiDB Cloud 无服务器驱动程序通过 HTTPS 连接,@tidbcloud/prisma-adapter v5.11.0 及更高版本可在 Vercel Edge Functions 和 Cloudflare Workers 中工作。
在边缘运行的路由处理程序看起来就像普通的 Prisma 查询:
// app/api/messages/route.ts
export const runtime = 'edge';
import { PrismaTiDBCloud } from '@tidbcloud/prisma-adapter';
import { PrismaClient } from '@prisma/client';
const adapter = new PrismaTiDBCloud({ url: process.env.DATABASE_URL });
const prisma = new PrismaClient({ adapter });
export async function GET(request: Request) {
const sessionId = new URL(request.url).searchParams.get('session');
const messages = await prisma.message.findMany({
where: { sessionId: sessionId ?? undefined },
orderBy: { createdAt: 'asc' },
take: 50,
});
return Response.json(messages);
}由于每次调用都会发出自己的 HTTPS 请求,突发的流量会产生一批独立请求,而不是对共享连接池的争用。冷启动时没有预热惩罚,也无需设置连接池大小。
在部署前进行本地测试:
npm run dev打开 http://localhost:3000/api/messages?session=<session-id> 并确认你能获得返回的行。如果响应为空但没有出现错误,则说明连接正常且表为空,这正是 prisma db push 之后的预期状态。
使用 TiDB Cloud 集成在 Vercel 上部署
手动配置开发、预览和生产环境既缓慢又容易出错,尤其是在连接详细信息发生变化时。Vercel Marketplace 上的 TiDB Cloud 集成只需点击几下即可处理,并且该演示应用已发布为 TiDB Cloud Starter Template。
- 打开模板并点击 Deploy。Vercel 会提示你创建一个 GitHub 仓库。给它起个名字,如果它不存在,Vercel 会创建它。
- 在 Add Integrations 部分中,添加 TiDB Cloud。在弹出的窗口中,选择目标 Vercel 项目,然后选择 TiDB Organization、Project 和 Cluster。对于首次部署,默认值即可。
- 选择 Prisma 作为框架,然后点击 Add Integration。Vercel 会将你带回集成屏幕,并显示正在进行的部署。
- 部署完成后,点击 Continue to Dashboard,然后点击 Visit 以确认应用已上线。
- 检查 Settings > Environment Variables。集成已经写入了连接详细信息,因此无需手动粘贴任何内容。
最后一步值得验证而不是假设。预览部署和生产环境读取相同的变量,这正是防止分支部署指向错误数据库的原因。
TiDB Cloud Starter 与 Vercel Postgres、Neon 和 Supabase 对比
大多数 Vercel 开发者在 Vercel Postgres(现通过 Marketplace 为 Neon)、Supabase 和 TiDB Cloud Starter 之间进行选择。这三者都是托管的,都能缩容到零,并且都为边缘运行时提供 HTTP 驱动程序。对 AI 应用重要的差异体现在扩展模型、向量处理和分析方面。
| TiDB Cloud Starter | Vercel Postgres / Neon | Supabase | |
|---|---|---|---|
| 线路协议 | MySQL | PostgreSQL | PostgreSQL |
| 写入扩展 | 跨节点水平扩展,无需手动分片 | 在单个主节点上垂直扩展,读取使用只读副本 | 在单个主节点上垂直扩展,读取使用只读副本 |
| 边缘连接性 | HTTPS 无服务器驱动程序,Prisma 和 Kysely 适配器 | HTTP 驱动程序(@neondatabase/serverless) | 通过 PostgREST 使用 HTTP,或使用 Supavisor 进行池化 TCP |
| 向量搜索 | 原生 VECTOR 类型,在同一数据库中带 HNSW 索引 | pgvector 扩展 | pgvector 扩展 |
| 对实时数据进行分析 | HTAP:在同一系统中实现行存储加列式副本 | 分析查询命中同一个行存储 | 分析查询命中同一个行存储 |
| 超越数据库 | 仅数据库 | 仅数据库 | 身份验证、存储、实时、边缘函数 |
按照你的应用所需而非按行数来读取表。有关各层级和限制的更多详情,请参阅 TiDB Cloud Starter 产品页面。
启动你的免费 TiDB Cloud Starter 集群
现在你已拥有完整路径:一个连接到真实数据库的原型、用于会话和代理状态的 schema、与所描述的行一起存储的嵌入、通过 HTTPS 的边缘连接,以及一个可自行配置的一键部署。
启动一个免费的 TiDB Cloud Starter 集群并将你的 Vercel 应用指向它。该演示应用的完整源代码位于 GitHub 上的演示仓库。
文章 Vercel 和 TiDB Cloud Starter:AI 应用的全栈实战手册 首次发表于 TiDB。
这篇内容对你有用吗?
反馈只用于改善内容筛选,不等同于收藏