返回
RSS TiDB Blog AI 逐段翻译 发布 2026-08-29 00:44

Prisma ORM与TiDB Serverless设置指南

DataHot 速览

TiDB Cloud官方博客介绍如何在AI应用中用Prisma ORM连接TiDB。内容强调TiDB兼容MySQL,现有schema可无缝使用,并推荐通过@tidbcloud/prisma-adapter走HTTPS查询以满足serverless/edge环境,同时说明迁移和introspection仍需TCP连接。还针对AI应用的数据建模给出主键和索引设计建议。

为什么值得关注:面向数据从业者提供了Prisma连接TiDB的完整技术路径和serverless环境下的关键限制,对AI应用的数据建模有直接参考价值。

本文目录 14 节
  1. 关键要点
  2. 简介
  3. 什么是 Prisma ORM?
  4. 为什么 Prisma 是 Vibe Coding 技术栈的默认 ORM
  5. 为什么使用 TiDB Cloud Prisma Adapter?
  6. 逐步使用 Prisma 设置 TiDB
  7. 安装适配器
  8. 指定 ES 模块
  9. 配置环境
  10. 版本兼容性以及适配器未涵盖的内容
  11. 使用 Cursor 或 ChatGPT 生成适用于 TiDB 的 Prisma Schema
  12. 使用 Prisma 为 AI 应用数据建模:用户、会话、消息、工具调用、记忆
  13. Prisma还是Drizzle?TiDB两者都支持
  14. 启动免费的TiDB Cloud Starter实例并连接Prisma

译文

AI 逐段翻译

关键要点

  • TiDB 兼容 MySQL,因此 Prisma 可以通过 provider = "mysql" 连接,且现有的 MySQL 模式无需更改即可使用。
  • 使用 @tidbcloud/prisma-adapter 通过 HTTPS 而非 TCP 进行查询——这对于无服务器函数和边缘运行时(Vercel Edge、Cloudflare Workers)是必需的,并可避免 TiDB Cloud 的连接限制。
  • 适配器仅覆盖 Prisma Client 的查询。迁移、db push 和 introspection 仍需要标准的 TCP 连接,因此请在环境配置中保留两条连接路径。
  • 当建模 AI 应用数据(会话、消息、工具调用、记忆)时,使用 uuid()cuid(2) 而非自增或 cuid() 作为主键以避免写入热点,并添加复合索引如 [sessionId, createdAt] 以便于聊天历史读取。

简介

Prisma 是大多数团队在 TypeScript 项目中使用的 ORM(对象关系映射器),它通过标准的 MySQL provider 连接到 TiDB,因此为 MySQL 编写的模式可以不加更改地使用。连接是工作的关键所在。无服务器函数和边缘运行时在每次调用时都会打开和丢弃连接,这是长期 TCP 连接池最不擅长的流量模式。

本指南涵盖安装适配器、配置环境、定义模式、运行查询和事务,以及建模 AI 应用持久化的数据。还介绍了适配器不支持的内容,因为迁移和 introspection 仍需要 TCP 连接。

虽然我们使用 TiDB Cloud Starter 来说明步骤,但这些步骤同样适用于 TiDB Cloud Essential。如果您使用 Dedicated、Premium 和 BYOC 层级,请遵循 此文档

什么是 Prisma ORM?

Prisma 是一个开源的 Node.js 和 TypeScript ORM,从声明式模式文件生成类型安全的客户端。它支持 MySQL、PostgreSQL、SQLite 和 SQL Server。您在 schema.prisma 中一次描述模型,运行 generate 步骤,即可获得一个带有自动补全和编译时检查的客户端,针对您自己的表。

以下两点对后续内容很重要。

  • Prisma Client 是生成的查询构建器。它针对您的模式进行类型化,因此拼写错误的字段会在构建时而不是在生产中失败。
  • Prisma 查询引擎 将客户端调用转换为 SQL,然后将语句交给驱动程序执行。驱动适配器替换了该驱动程序,而保留转换步骤不变。这就是 Prisma 通过 HTTP 而非 TCP 套接字访问数据库的方式。

由于 TiDB 兼容 MySQL,Prisma 将其视为 MySQL。您的 provider 是 mysql,为 MySQL 编写的模式文件可以原样使用。

为什么 Prisma 是 Vibe Coding 技术栈的默认 ORM

Prisma 的模式是一个单一的声明式文件,语法可读,这使得它成为编码代理最可靠生成的格式。向 Cursor 或 ChatGPT 请求数据模型时,您更可能得到 schema.prisma 而不是原始的 DDL。当目标数据库兼容 MySQL 时,生成的模式可以直接运行。

通常的问题是方言。在大量 Postgres 示例语料上训练的模型会生成 Postgres 类型,您需要花费时间将 serial 和 jsonb 重写为 MySQL 可接受的类型。将 provider 切换为 mysql 是第一个更改,但不是唯一的更改。原生类型属性也必须随之更改。@db.Uuid、@db.JsonB 和 @db.Inet 在 MySQL 连接器上都会验证失败。有帮助的是这些失败发生的位置:prisma validate 会指出属性和其所在的行,因此您可以在任何内容到达数据库之前一次性修复它们。

generate 步骤也会检查模型的工作。如果代理发明了一个不成立的关系,prisma generate 和 TypeScript 编译器会在其到达查询之前捕获它。

为什么使用 TiDB Cloud Prisma Adapter?

@tidbcloud/prisma-adapter 包允许 Prisma Client 通过 HTTPS 而非长期 TCP 连接访问 TiDB Cloud。它位于客户端和 TiDB Cloud 无服务器驱动程序之间,从查询引擎获取 SQL 并通过 HTTP 发送。路径中没有连接池。

TiDB Cloud Starter 实例的三个限制使情况具体化。您可获得 400 个并发连接,或设置支出限额后 5000 个。连接若保持打开超过 30 分钟可能会被终止。此外,文档建议将连接生命周期限制在约 5 分钟。在 AWS 上,公共端点空闲超时为 340 秒。

在无服务器函数中,池化的 Prisma 设置会遇到所有三个问题。每次调用要么支付 TCP 握手费用,要么使用一个已经被关闭的池化连接。通过 HTTP,每个查询都是一个请求,因此没有需要调整大小的池,也不会在查询中途发现未使用的连接。

适配器也是从边缘运行时访问 TiDB 的唯一方式。Prisma Client 无法在 Vercel Edge Functions 或 Cloudflare Workers 中打开 TCP 套接字。

PingCAP 维护此适配器,Prisma 将其列在社区维护的驱动适配器中。错误和版本支持问题请前往该仓库的问题跟踪器,而不是 Prisma。

逐步使用 Prisma 设置 TiDB

六个步骤:安装包、设置 ES 模块类型、设置连接字符串、定义模式、push 并 generate、通过适配器实例化客户端。在免费的 Starter 实例上,这大约需要十分钟。

安装适配器

npm install @tidbcloud/[email protected] @tidbcloud/serverless dotenvnpm install [email protected] --save-dev

dotenv 是下面的查询脚本所必需的。在 pnpm 上显式安装它很重要,因为 pnpm 不会像 npm 那样提升传递依赖。

指定 ES 模块

这里的所有示例都使用 import 语法,因此 package.json 需要 ES 模块标志。没有它,运行查询脚本会失败并显示 Cannot use import statement outside a module:

{

  "type": "module"

}

配置环境

// .env
DATABASE_URL="mysql://username:password@host:4000/database?sslaccept=strict"

如果您只使用 Prisma Client 而不使用迁移,则端口和 SSL 参数是不必要的,mysql://username:password@host/database 就足够了。

不要提交这个文件。在 Vercel 或 Cloudflare 上,将 DATABASE_URL 设置为平台环境变量。对于 Vercel,集成市场中的 TiDB Cloud 集成会为您生成连接变量,并且适用于 Starter 和 Essential 计划,这样就不必在预览和生产环境之间手动传递凭据。

此步骤也是让边缘部署工作的关键。Prisma Client 无法在 Vercel Edge Functions 或 Cloudflare Workers 中打开 TCP 套接字,因此 HTTPS 路径是您的查询能够在这些运行时中运行的原因。

TiDB Cloud 控制台中的连接对话框。

定义您的 schema:

// schema.prismagenerator client {    provider = "prisma-client-js"}
datasource db {    provider = "mysql"    url      = env("DATABASE_URL")}
model user {    id    Int     @id @default(autoincrement())    email String? @unique(map: "uniq_email") @db.VarChar(255)    name  String? @db.VarChar(255)}

然后同步 schema 并生成客户端:

npx prisma db push
npx prismagenerate

db push 通过 TCP 连接,而不是通过适配器。这是两条连接路径第一次分叉,下面的兼容性部分将涵盖其余内容。

使用适配器运行和查询

// query.js
import { PrismaTiDBCloud } from '@tidbcloud/prisma-adapter';
import { PrismaClient } from '@prisma/client';
import dotenv from 'dotenv';

dotenv.config();
const connectionString = `${process.env.DATABASE_URL}`;

const adapter = new PrismaTiDBCloud({ url: connectionString });
const prisma = new PrismaClient({ adapter });

// insert
const user = await prisma.user.create({
    data: {
        email: '[email protected]',
        name: 'test',
    },
})
console.log(user)

// query
console.log(await prisma.user.findMany())

// delete
await prisma.user.delete({
    where: { id: user.id },
})

事务使用数组形式,其中操作要么全部成功,要么全部失败。

const createUser1 = prisma.user.create({
    data: { email: '[email protected]', name: 'User One' },
})

const createUser2 = prisma.user.create({
    data: { email: '[email protected]', name: 'User One duplicate' },
})

const createUser3 = prisma.user.create({
    data: { email: '[email protected]', name: 'User Three' },
})

try {
    // fails together, because email is unique
    await prisma.$transaction([createUser1, createUser2])
} catch (e) {
    console.log(e)
    // succeeds together
    await prisma.$transaction([createUser3], { isolationLevel: 'ReadCommitted' })
}

交互式回调形式 prisma.$transaction(async (tx) => { … }) 也可以使用。适配器实现了 startTransaction,因此两种形式都可用。

注意隔离级别的值。Prisma 采用自己的 PascalCase 枚举 ReadCommitted,并在适配器看到它之前将其转换为 SQL 形式。传递“READ COMMITTED”不起作用。

需要设计考虑的一个限制:在 Starter 和 Essential 计划中,事务的最长持续时间为 30 分钟。对于请求范围的工作来说,这通常不是问题。它对于批量回填很重要,应该分批进行,而不是包装在单个事务中。

版本兼容性以及适配器未涵盖的内容

适配器仅涵盖 Prisma Client。迁移和内省通过 TCP 连接,这意味着一个可工作的设置通常需要两条路径都可用:HTTPS 用于运行时查询,TCP 用于 schema 更改。在环境配置中保留两者,否则 prisma db push 会因为仅客户端连接字符串而失败。

通过 HTTPS(适配器)通过 TCP(标准)
Prisma Client 查询
事务,数组形式
事务,交互式形式
prisma db push
prisma migrate
prisma db pull(内省)
Vercel Edge、Cloudflare Workers

对部署的实际影响:schema 更改不能从边缘函数运行。请从 CI 或本地使用 TCP 连接字符串运行它们,并让部署的运行时仅使用 HTTPS 进行查询。

适配器版本与 Prisma 次要版本保持一致。首先选择您的 Prisma 版本,然后选择匹配次要版本中的最新适配器版本:

适配器Prisma ClientServerless Driver
v6.17.xv6.17.x>=v0.1.0
v6.12.xv6.12.x>=v0.1.0
v6.6.xv6.6.x>=v0.1.0

使用 Cursor 或 ChatGPT 生成适用于 TiDB 的 Prisma Schema

有效的提示应指明数据库名称,说明其兼容 MySQL,列出实体和关系,并请求索引。缺少这四个元素,您将得到 Postgres 风格的 schema,然后需要手动翻译。

运行输出前需要检查的内容:

  • 原生类型。@db.Uuid、@db.JsonB、@db.Inet 是 Postgres 类型,在 MySQL 连接器上验证会失败。请使用 String @db.VarChar(36)、Json 和 String @db.VarChar(45)。serial 变为 Int @default(autoincrement()),text[] 变为关系表。
  • 主键,这一点是 TiDB 特有的。顺序键会将写入集中在一个区域。这包括 @default(autoincrement()) 和任何单调递增的索引,也包括 cuid(),它带有时间戳前缀,因此按创建顺序排序。生成的 schema 通常使用这两种。请使用 uuid() 或 cuid(2),它们是随机的,并且在 MySQL 上验证没问题。
  • 索引。模型通常添加单列索引,而查询需要复合索引。仅靠 sessionId 上的索引无法满足 where sessionId = ? order by createdAt 的查询。
  • 级联行为。onDelete 经常被省略。请有意决定,因为代理数据会累积,孤立行会占用存储配额。
  • 显式 @db.Text。无界 String 在 MySQL 上映射为 VARCHAR(191),并且无需前缀长度即可索引,因此无需注意。问题是生成的 schema 在你想要索引的字段上写入了 @db.Text。将 @db.Text 保留给真正长的内容。

使用 Prisma 为 AI 应用数据建模:用户、会话、消息、工具调用、记忆

代理应用程序持久化一组可识别的实体,读取模式很窄。您更常获取一个会话按顺序排列的消息,或用户最近的会话,而不是在整个集合上运行分析。这应该驱动索引设计。

model User {
  id        String    @id @default(uuid())
  email     String?   @unique @db.VarChar(255)
  createdAt DateTime  @default(now())
  sessions  Session[]
}
model Session {  id        String         @id @default(uuid())  userId    String  status    String         @db.VarChar(32)  user      User           @relation(fields: [userId], references: [id], onDelete: Cascade)
  startedAt DateTime       @default(now())
  messages  Message[]
  memory    MemoryObject[]

  @@index([userId, startedAt])
}

model Message {
  id        String     @id @default(uuid())
  sessionId String
  role      String     @db.VarChar(16)
  content   String     @db.Text
  createdAt DateTime   @default(now())
  session   Session    @relation(fields: [sessionId], references: [id], onDelete: Cascade)
  toolCalls ToolCall[]

  @@index([sessionId, createdAt])
}

model ToolCall {
  id        String  @id @default(uuid())
  messageId String
  toolName  String  @db.VarChar(128)
  arguments Json
  result    Json?
  status    String  @db.VarChar(32)
  message   Message @relation(fields: [messageId], references: [id], onDelete: Cascade)

  @@index([messageId])
}

model MemoryObject {
  id        String   @id @default(uuid())
  sessionId String
  kind      String   @db.VarChar(64)
  payload   Json
  updatedAt DateTime @updatedAt
  session   Session  @relation(fields: [sessionId], references: [id], onDelete: Cascade)

  @@index([sessionId, kind])
}

使用 uuid() 而不是 cuid() 或自增整数。顺序主键会将写入集中在 TiDB 的单个区域,而 cuid() 带有时间戳前缀,因此按创建顺序排序,行为类似。代理工作负载在这些表上恰好是写密集型的。

该 schema 中还有三处是经过深思熟虑的。

Message 上的 @@index([sessionId, createdAt]) 是支持聊天历史读取的复合索引。没有它,渲染对话将进行扫描和排序。

全表使用 onDelete: Cascade。代理数据增长迅速,会话会被删除。没有级联规则,您会积累孤立的工具调用,这些会占用存储。级联会贯穿整个深度。删除用户会删除其会话,每个会话会带走其消息、工具调用和记忆对象。如果身份存储在外部提供商(如 Clerk、Auth0 或 WorkOS)中,请删除 User 模型,并将 userId 保留为不透明字符串。在身份提供者中删除用户不会删除他们在 TiDB 中的数据,因此清理成为应用层级的任务,通常通过用户删除 webhook 实现。删除该用户在 TiDB 中的会话,现有的从 Session 到消息、工具调用和记忆对象的级联将处理其余部分。

为工具参数和结果使用 Json,而不是每个工具一个规范化列,因为工具 schema 比数据库更改更频繁。

将其保留在一个兼容MySQL的数据库中的理由是操作性的。会话状态、消息历史、工具结果和向量嵌入在单个请求路径中一起读取。将它们分散到文档存储、向量数据库和数据仓库中,会将一个查询变成三次网络调用,并使一致性成为你的问题。

Prisma还是Drizzle?TiDB两者都支持

两者都作为MySQL连接到TiDB,因此选择归结为你希望在代码和SQL之间有多少抽象,而不是兼容性。Prisma提供声明式模式、生成类型和迁移工作流。Drizzle更接近SQL,运行时更小,查询语法读起来就像它生成的语句。希望将模式作为单一事实来源的团队选择Prisma。希望看到SQL的团队选择Drizzle。

启动免费的TiDB Cloud Starter实例并连接Prisma

以上所有内容都在免费层运行。每个TiDB Cloud Starter实例包括每月免费配额:5 GiB行存储、5 GiB列存储和5000万请求单元。行存储是Prisma应用程序消耗的。在需要信用卡之前,每个组织还可以运行最多五个免费实例。

创建实例,复制连接字符串,并完成设置部分。如果适配器在某个Prisma版本上出现问题,请在GitHub仓库中提出问题。

这篇内容对你有用吗?

反馈只用于改善内容筛选,不等同于收藏

分享这条资讯
分享海报
保存图片
iOS 也可以长按图片保存