KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
Prisma 数据访问层 — keel 龙骨
Prisma 数据访问层 的参考信息:Prisma 数据访问层
课程导读 · 每条结论都要能拿出查询日志和 EXPLAIN
这门课解决的是一类"代码看起来没问题、跑起来也没报错"的问题:接口为什么偶发唯一键冲突、列表页翻到后面为什么慢、本地能跑上线就 500、一个"加个字段"的改动为什么在生产上变成"要不要清空数据库"。
你现在的起点
- 用过某个 ORM(Prisma / TypeORM / Sequelize / SQLAlchemy 都行),日常增删改查没问题;
- 会写 SQL,但没认真看过 ORM 生成的 SQL——尤其是带关联的那些;
- 知道 N+1 这个词,但说不清
include为什么能"解决"它,也不知道它在什么情况下解决不了; - 用过迁移工具,但没遇到过"库里有表、迁移历史是空的"这种处境。
如果你符合上面这几条,这门课就是为你写的。会写 SQL 是前提——这门课不教 SQL,它教的是"这些 SQL 是被谁、以什么形状发出去的"。不懂前者,后面任何一条结论你都无法验证。
一条能走通的学习路径
先学会看(01 一次 findMany 编译成了什么)
→ 编译器把你的调用变成了什么;多出来的那些字各自解决什么问题
→ 谁在做编译:Prisma 7 的管线与产物
再看这份"能被编译的东西"从哪来(02 schema 是一份两向的契约)
→ 逆向只描述结构、不描述意图;它丢掉的比它给的多
→ 两个方向只能选一个当真相
然后是这门课最实用的一章(03 include 到底发几条 SQL)
→ N+1 的真身、两种策略、以及"一条 SQL"的账单
写的那一侧(04 写路径:从 upsert 到嵌套写)
→ 原子性来自哪、为什么一次 create 是五条、事务的开始为什么看不见
值的旅程(05 值穿过边界时变成了什么)
→ 静默变形 vs 直接报错、精度在哪一层丢、原生 SQL 的边界
改结构的三种处境(06 迁移:从空库、从现成库、从被人改过的库)
→ baseline、drift、以及那个"数据会全丢"的提示
收口(07 管不到的那部分)
→ 连接池、深翻分页、方言、装机体积
你会拿到什么
| 章节 | 一句话 | 关键动作 |
|---|---|---|
| 01 一次 findMany 编译成了什么 | 你写的对象到数据库那边变成了什么 | 查询日志 + 三份时序记录 + 枚举 CAST 的三种位置对照 |
| 02 schema 是一份两向的契约 | 逆向出结构,逆向不出意图 | 一次真 db pull 的告警清单 + "保住 / 丢掉"对照表 |
| 03 include 到底发几条 SQL | "一条 SQL"不等于"一次工作" | N+1 计数 + 两种策略的 SQL 原文 + 两份 EXPLAIN |
| 04 写路径:从 upsert 到嵌套写 | 一次嵌套 create 是五条语句 |
ON CONFLICT 原文 + 分块公式 + 事务日志里缺了什么 |
| 05 值穿过边界时变成了什么 | 同一个问题分裂成"静默变形"和"直接报错" | 类型对照表 + BigInt 序列化失败 + P2010 |
| 06 迁移:从空库、从现成库、从被人改过的库 | status 说没事的时候可能正有事 |
不连库生成 DDL + P3005 + baseline 台账 |
| 07 管不到的那部分 | 池、深翻、方言与体积 | 池容量实测 + 两组 EXPLAIN 的 rows= 对照 |
每章结构统一:现场 → 形态 → 原因 → 本章脉络 → 生产边界 → 动手:可观察结果 → 故障注入 → 自测题 → 现在能解释什么。
三个必须先纠偏的认知
include不是"没有 N+1",是"把 N 次子查询合并成 1 次批量查询"。 代价被挪到了那条IN ($1,...,$N)上——占位符个数等于主表返回的行数。真有一条路能把它变成一条 SQL(join 策略),但那条路在关联是"一对多"时会把"N 次往返"换成"一次往返里的 N 次循环",实测在 3000 篇时慢一倍。没有免费的改写,只有代价搬家。migrate status说 "up to date" 不等于库和 schema 一致。 它比对的是迁移历史,不是数据库的真实结构。库被人手工改过之后它会照说 up to date,而migrate dev给的补救手段是"清空数据、全部重来"。要验证真实一致性,只能用migrate diff。- 类型问题几乎不出在数据库那一侧。
BigInt让接口 500、Decimal静默变成字符串、时间精度在穿过 JS 时丢掉、连原生 SQL 的返回类型都可能被适配器拒绝。这些都不是你的业务逻辑,是类型系统的接缝。
学完这门课你能做什么
- 拿到一段 ORM 代码,在跑之前就能写出它会发出的语句形状(几条、什么顺序、哪条是回读、事务边界在哪);
- 看到一个带关联的列表页需求,能判断它会有几条 SQL、关联多的时候会走哪条路、大概什么量级;
- 遇到"生产数据库 + 想引入迁移"这种处境,能按顺序做对(先补第一份迁移、再 baseline),而不是照提示清空数据;
- 遇到
Do not know how to serialize a BigInt这类报错,能立刻定位到是哪一层、以及为什么建表阶段就能避免; - 知道 ORM 管不到哪几件事(连接池、深翻分页、窗口函数、查询计划),也就知道什么时候该放下 ORM 直接写 SQL。
前置要求
- 会用一条 SQL 表达"取哪些行、按什么排、要哪几列",看得懂
EXPLAIN的输出结构(不用会调优); - 建议先读 数据库与缓存调优——索引为什么快、回表的代价、最左前缀从哪来,那边的 00 章 讲了原理,这边默认你知道;本章里那组"
CAST在参数侧还是列侧"的对照,读不懂索引就完全无法判断; - 会基础 TypeScript 或 JavaScript,能跑一个 Node 脚本。
替身边界
实验以 Prisma 7.10.0 + @prisma/adapter-pg + pg 8.23 + PostgreSQL 16.15 为准。这一行的版本演进比大多数框架都快——本课程记下的多处默认值、CLI 参数、生成器行为都是被某一版改过的(datasource.url 移出 schema、prisma-client 生成器、--to-schema 改名、migrate dev 移除 --skip-generate、连接池默认值变更)。结论的形状("转换在参数侧还是列侧"、"批量查询的占位符随 N 增长"、"基线登记只写台账")跨版本成立,具体数字和参数名请在你自己的版本上复核。
实验数据是一张小表(5000 行 / 20000 行),全部在单机缓存里。凡涉及绝对毫秒数的地方,请只看比值。