ORM 选型
要点
- Hono 项目常用的 ORM:Drizzle、Prisma、Kysely
- Drizzle 类型推导好、性能高、轻量,是 TypeScript 项目的首选
- Prisma 生态完善、开发体验好,但运行时有额外开销
- Kysely 是类型安全的 SQL 构建器,适合喜欢写 SQL 的开发者
- ORM 选型需要考虑团队熟悉度、数据库支持、性能要求
- AI 项目通常需要 JSON 查询和向量操作,ORM 需要支持这些特性
1. 三大 ORM 概览
| 维度 | Drizzle | Prisma | Kysely |
|---|---|---|---|
| 定位 | TypeScript ORM | TypeScript ORM | 类型安全 SQL 构建器 |
| Schema 定义 | TypeScript 代码 | .prisma 文件 | TypeScript 类型 |
| 类型推导 | 优秀 | 优秀 | 优秀 |
| 性能 | 高(接近原生) | 中等(有运行时开销) | 高(接近原生) |
| 迁移工具 | Drizzle Kit | Prisma Migrate | 无(需要手写或第三方) |
| 数据库支持 | PostgreSQL、MySQL、SQLite 等 | PostgreSQL、MySQL、SQLite 等 | PostgreSQL、MySQL、SQLite 等 |
| 学习曲线 | 中等 | 低 | 中等 |
| 社区生态 | 成长中 | 成熟 | 较小 |
| 包大小 | 小 | 大 | 小 |
2. Drizzle ORM
Drizzle 是 TypeScript 优先的 ORM,schema 用 TypeScript 代码定义,类型推导完整。
2.1 Schema 定义
// src/schema.ts
import { pgTable, uuid, text, timestamp, jsonb, integer } from 'drizzle-orm/pg-core'
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: text('email').notNull().unique(),
name: text('name').notNull(),
metadata: jsonb('metadata').$type<{ role?: string; tags?: string[] }>(),
createdAt: timestamp('created_at').defaultNow(),
})
export const conversations = pgTable('conversations', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id).notNull(),
title: text('title').notNull(),
model: text('model').notNull(),
messageCount: integer('message_count').default(0),
createdAt: timestamp('created_at').defaultNow(),
})2.2 查询
import { db } from './lib/db'
import { users, conversations } from './schema'
import { eq, and, desc } from 'drizzle-orm'
// 查询所有用户
const allUsers = await db.select().from(users)
// 条件查询
const user = await db.select().from(users).where(eq(users.email, '[email protected]')).get()
// 多条件
const activeUsers = await db
.select()
.from(users)
.where(and(eq(users.name, 'Alice'), desc(users.createdAt)))
// 关联查询
const userWithConversations = await db
.select()
.from(users)
.leftJoin(conversations, eq(users.id, conversations.userId))
.where(eq(users.id, 'user-1'))
// 插入
const [newUser] = await db.insert(users).values({
email: '[email protected]',
name: 'Bob',
}).returning()
// 更新
await db.update(users).set({ name: 'Alice Smith' }).where(eq(users.id, 'user-1'))
// 删除
await db.delete(users).where(eq(users.id, 'user-1'))2.3 迁移
# 生成迁移
pnpm drizzle-kit generate
# 应用迁移
pnpm drizzle-kit migrate
# 开发环境直接推送
pnpm drizzle-kit push2.4 优势
- 类型推导完整:查询结果、插入值、更新值都有类型提示
- 性能高:生成的 SQL 接近手写,运行时开销小
- 轻量:包大小小,启动快
- 灵活:支持原生 SQL 片段(
sql模板字符串) - 多数据库:PostgreSQL、MySQL、SQLite、Turso 等
2.5 劣势
- 生态不如 Prisma 成熟:文档、教程、第三方工具较少
- Studio 功能较弱:Drizzle Studio 不如 Prisma Studio 完善
3. Prisma
Prisma 是最流行的 TypeScript ORM,schema 用独立的 .prisma 文件定义。
3.1 Schema 定义
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
name String
metadata Json?
conversations Conversation[]
createdAt DateTime @default(now())
}
model Conversation {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
title String
model String
messages Message[]
createdAt DateTime @default(now())
}
model Message {
id String @id @default(uuid())
conversationId String
conversation Conversation @relation(fields: [conversationId], references: [id])
role String
content String
tokens Int
createdAt DateTime @default(now())
}3.2 查询
import { prisma } from './lib/db'
// 查询所有用户
const users = await prisma.user.findMany()
// 条件查询
const user = await prisma.user.findUnique({
where: { email: '[email protected]' },
})
// 关联查询
const userWithConversations = await prisma.user.findUnique({
where: { id: 'user-1' },
include: { conversations: true },
})
// 嵌套查询
const userWithMessages = await prisma.user.findUnique({
where: { id: 'user-1' },
include: {
conversations: {
include: { messages: true },
},
},
})
// 插入
const newUser = await prisma.user.create({
data: {
email: '[email protected]',
name: 'Bob',
},
})
// 更新
await prisma.user.update({
where: { id: 'user-1' },
data: { name: 'Alice Smith' },
})
// 删除
await prisma.user.delete({
where: { id: 'user-1' },
})3.3 迁移
# 生成并应用迁移
pnpx prisma migrate dev --name init
# 生产环境应用迁移
pnpx prisma migrate deploy3.4 优势
- 开发体验好:Prisma Studio 可视化查看数据、Prisma Client 类型推导完整
- 生态成熟:文档完善、教程多、社区活跃
- 关系建模清晰:schema 里直接定义关系,查询时 include 关联数据
- 迁移工具完善:Prisma Migrate 稳定可靠
3.5 劣势
- 运行时开销:Prisma Client 有额外的运行时开销(查询引擎)
- 包大小大:Prisma Client 体积较大
- Schema 与代码分离:需要在
.prisma文件和 TypeScript 之间切换 - 向量支持有限:pgvector 支持不如 Drizzle 灵活
4. Kysely
Kysely 是类型安全的 SQL 构建器,不是完整的 ORM。适合喜欢写 SQL 的开发者。
4.1 类型定义
// src/database/types.ts
import { Generated, ColumnType } from 'kysely'
export interface Database {
users: UsersTable
conversations: ConversationsTable
}
export interface UsersTable {
id: Generated<string>
email: string
name: string
metadata: Json | null
created_at: ColumnType<Date, Date | undefined, Date | undefined>
}
export interface ConversationsTable {
id: Generated<string>
user_id: string
title: string
model: string
message_count: number
created_at: ColumnType<Date, Date | undefined, Date | undefined>
}
type Json = Value<Record<string, unknown> | Record<string, unknown>[] | string | number | boolean | null>4.2 查询
import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'
import { Database } from './database/types'
const db = new Kysely<Database>({
dialect: new PostgresDialect({
pool: new Pool({ connectionString: process.env.DATABASE_URL }),
}),
})
// 查询
const users = await db.selectFrom('users').selectAll().execute()
// 条件查询
const user = await db
.selectFrom('users')
.selectAll()
.where('email', '=', '[email protected]')
.executeTakeFirst()
// 关联查询
const userWithConversations = await db
.selectFrom('users')
.innerJoin('conversations', 'users.id', 'conversations.user_id')
.selectAll()
.where('users.id', '=', 'user-1')
.execute()
// 插入
await db.insertInto('users').values({
email: '[email protected]',
name: 'Bob',
}).execute()4.3 优势
- 接近原生 SQL:生成的 SQL 可读性高,容易调试
- 性能高:没有运行时开销
- 类型安全:查询结果、条件值都有类型推导
- 灵活:可以用
sql模板字符串写原生 SQL
4.4 劣势
- 不是完整 ORM:没有迁移工具、没有关系建模
- 学习曲线:需要了解 SQL 和 Kysely API
- 代码量大:复杂查询需要写更多代码
5. 选型决策
是否需要完整的 ORM(迁移、关系建模)?
├── 是 → 团队偏好?
│ ├── TypeScript 优先、轻量 → Drizzle
│ └── 开发体验优先、生态完善 → Prisma
└── 否 → 是否喜欢写 SQL?
├── 是 → Kysely
└── 否 → Drizzle(更简单的 API)
5.1 选 Drizzle 的场景
- 追求性能和轻量
- 团队熟悉 TypeScript,喜欢代码即 schema
- 需要 pgvector 或其他扩展的灵活支持
- 项目需要多数据库支持(包括 Edge 环境)
5.2 选 Prisma 的场景
- 团队 ORM 经验少,需要完善的文档和工具
- 需要 Prisma Studio 可视化查看数据
- 项目以关系建模为主,不需要复杂的扩展
- 团队熟悉 Prisma
5.3 选 Kysely 的场景
- 团队 SQL 经验丰富,喜欢控制生成的 SQL
- 项目有复杂的查询需求
- 不需要迁移工具(用其他方式管理 schema)
6. AI 项目的 ORM 选型
AI 项目通常需要:
- JSON 查询:存储 prompt 元数据、模型配置
- 向量操作:embedding 存储和相似度检索
- 高性能:AI API 调用慢,数据库查询不能成为瓶颈
- Edge 兼容:可能部署在 Cloudflare Workers
基于这些需求:
- Drizzle:推荐。支持 pgvector、JSON 查询灵活、Edge 兼容好
- Prisma:可以。pgvector 支持有限,但基本 CRUD 没问题
- Kysely:可以。需要自己处理迁移和向量操作
7. 混合使用
有些项目会混合使用 ORM 和原生 SQL:
// 简单 CRUD 用 ORM
const users = await db.select().from(users).where(eq(users.id, id))
// 复杂查询用原生 SQL
import { sql } from 'drizzle-orm'
const results = await db.execute(sql`
SELECT u.*, COUNT(c.id) as conversation_count
FROM users u
LEFT JOIN conversations c ON u.id = c.user_id
GROUP BY u.id
ORDER BY conversation_count DESC
LIMIT 10
`)Drizzle 和 Kysely 都支持原生 SQL 片段,Prisma 也支持 $queryRaw。
总结
Hono 项目的 ORM 选型主要在 Drizzle、Prisma、Kysely 之间。
这一节涉及到的几个判断:
- Drizzle:TypeScript 优先、性能高、轻量、pgvector 支持好,适合 AI 项目
- Prisma:生态成熟、开发体验好、适合关系建模为主的项目
- Kysely:类型安全的 SQL 构建器,适合喜欢写 SQL 的开发者
- 选型依据:团队熟悉度、性能要求、数据库支持、扩展需求
AI 项目推荐 Drizzle——类型推导好、pgvector 支持灵活、Edge 兼容好。如果团队熟悉 Prisma,用 Prisma 也可以。
下一篇看 Prisma 实践——深入 Prisma 的 schema 定义、查询 API、迁移管理。