最近把我们核心的 Serverless 服务从 Prisma 彻底迁移到了 Kysely,整个人瞬间轻松了。之前用 Prisma 部署到 AWS Lambda,那个冷启动延迟简直是噩梦。最离谱的是,Prisma 那个所谓的 Rust 查询引擎二进制文件(Query Engine),打包之后直接占了三四十兆,每次冷启动都要花上两三秒去解压和初始化这个黑盒引擎,直接拉低了整个服务的响应速度。

更别提 Prisma 那个难用至极的复杂查询了。在处理多表 JOIN 或者稍微复杂一点的聚合报表时,Prisma 的 include 嵌套查询在底层会生成一堆让人血压飙升的 Sub-queries。不仅慢,而且你根本没办法控制 SQL 的生成细节。为了给一个简单的报表做优化,你得被迫写一大堆 prisma.$queryRaw,这直接把原生的 TypeScript 类型保障给搞没了。

为了解决这个问题,我们干脆一刀切,把 ORM 换成了 Kysely。Kysely 是一个纯 TypeScript 编写的 SQL 查询构建器,它没有像 Prisma 那样在后台跑一个庞大的二进制进程,它的原理就是在编译期利用 TypeScript 强大的类型系统,把你的原生数据库 Schema 映射成类型定义,然后通过链式调用直接生成干净、无额外开销的原生 SQL。

下面是我们用 Kysely 配合 kysely-codegen 自动生成的数据库类型来改写多表关联查询的实际代码。对比之前 Prisma 嵌套地狱的写法,这个真的是爽快太多:

import { Database } from 'db-types'; // 自动生成的数据库 Schema 类型
import { Kysely, PostgresDialect } from 'kysely';
import { Pool } from 'pg';

// 声明全局 DB 实例
export const db = new Kysely<Database>({
  dialect: new PostgresDialect({
    pool: new Pool({
      connectionString: process.env.DATABASE_URL,
      max: 10,
    }),
  }),
});

interface OrderReportResult {
  userId: string;
  userName: string;
  totalOrders: number;
  totalSpent: number;
}

export async function getUserOrderReport(minSpent: number): Promise<OrderReportResult[]> {
  const result = await db
    .selectFrom('users')
    .innerJoin('orders', 'orders.user_id', 'users.id')
    .select([
      'users.id as userId',
      'users.name as userName',
      db.fn.count<number>('orders.id').as('totalOrders'),
      db.fn.sum<number>('orders.amount').as('totalSpent'),
    ])
    .where('orders.status', '=', 'COMPLETED')
    .groupBy(['users.id', 'users.name'])
    .having(db.fn.sum<number>('orders.amount'), '>=', minSpent)
    .orderBy('totalSpent', 'desc')
    .limit(50)
    .execute();

  return result;
}

注意看上面的 db-types 导入。我们在 CI 流程中加入了一步 kysely-codegen --out-file=node_modules/db-types/index.d.ts。只要数据库 Schema 变了,类型就会立刻在编译时报错。你在 select 里面多打一个字母或者写错了表名,IDE 就会瞬间爆红,完全不需要像 Prisma 那样依赖庞大的 prisma generate 步骤和运行时开销。

另外在性能层面,Kysely 简直是降维打击。我们做了一组压测,在并发 200 的场景下,同样的联表聚合查询,Kysely 的 QPS 是 Prisma 的 3.2 倍,CPU 占用率下降了近 60%。主要是因为没有了 Rust 查询引擎在 JS 和 Rust 边界频繁进行 JSON 序列化/反序列化的损耗,整个请求的生命周期干净利落。

唯一需要折腾的是最初迁移 Schema 的时候,如果你的表结构有很深的遗留历史问题,可能需要手动调整 kysely-codegen 的一些映射关系。不过比起以后天天提防 Prisma 冷启动抖动和无法优化的 SQL,这点工作量完全是毛毛雨。

现在项目里只剩两个老模块还在用 Prisma。打算下周安排时间把剩下的也全部干掉。对于还在用 Serverless 架构并天天为冷启动发愁的同行,我强烈建议尽早调研下 Kysely。