LESSON 6.3 / DATABASE & STATE

ORM、Schema 与数据库迁移

ORM 让应用用类型和对象访问数据库;迁移则记录结构如何从旧版本安全变化到新版本。

预计阅读15–20 分钟
完成结果数据库结构变化有记录、可审查、可重复。
本课目标
  • 数据库结构变化有记录、可审查、可重复。
  • Prisma / JPA 等 ORM 与 SQL 的关系
  • 为现有表新增可空字段,生成迁移、检查 SQL、执行并验证旧数据。
开始之前
  • 完成 6.2「SQL 与关系型数据库直觉」
01

FOUNDATION

必须理解

ORM 让应用用类型和对象访问数据库;迁移则记录结构如何从旧版本安全变化到新版本。

ORM(Object-Relational Mapping,对象关系映射)把代码中的模型与数据库操作连接起来;迁移则记录数据库结构怎样从旧版本演进到新版本。便利不能替代对 SQL 和数据风险的理解。

先建立整体直觉

ORM 像翻译员,把 TypeScript 对象翻成 SQL;迁移像建筑改造图纸,按顺序记录加门、改管线。翻译员和图纸都可能出错,所以施工前要审查。

贯穿本课的实际场景

Prisma schema 定义 User 与 Task,迁移创建表和外键,服务端通过 Prisma Client 查询当前用户任务;新增 dueAt 字段要生成并检查迁移。

概念 1

Prisma / JPA 等 ORM 与 SQL 的关系

先用白话理解

Prisma Schema 写清模型、字段、关系和约束,Prisma Client 再根据它生成带类型的查询方法。最后真正执行工作的仍然是数据库和 SQL。

基础概念

ORM(Object-Relational Mapping,对象关系映射)把程序中的类型或对象操作映射为数据库查询。Prisma、JPA 等减少重复代码,但底层仍由 SQL 与数据库执行。

进一步理解

ORM 模型描述字段、关系和约束,客户端提供类型化查询。它能防止部分拼接错误,却不会自动设计正确业务模型或高效查询。

复杂查询、事务和性能问题仍需理解生成的 SQL、索引与数据库行为。不同 ORM 的加载策略也可能造成大量隐形请求。

放进实际场景

代码通过 `task.findMany({where:{ownerId}})` 查询任务,ORM 生成参数化 SQL;开发者仍要确认只返回当前用户且没有 N+1 查询。

容易混淆的地方

ORM 不是数据库,也不是 SQL 替代物;它是应用与关系型数据库之间的一层映射和工具。

这一小节记住:使用 ORM 提高表达和类型安全,同时保留检查数据库行为的能力。

概念 2

Schema、客户端、查询和关系加载

先用白话理解

迁移文件记录数据库结构每一步怎么变化,像 Git 记录代码历史。开发时可以反复试,到了生产环境就得考虑旧数据、锁表时间和失败后怎么处理。

基础概念

Schema 是数据结构与约束的声明,生成客户端把声明转换为可调用 API,关系加载决定查询时是否以及怎样取得关联记录。

进一步理解

一对多关系通常由“多”方保存外键。是否一起加载关联数据应由当前用例决定,默认全部加载会增加查询和传输。

Schema 改变只是代码层意图,真实数据库还需迁移。生成客户端也需与新 Schema 同步,否则类型和数据库会处于不同版本。

放进实际场景

Task 的 ownerId 保存外键,owner 描述 ORM 关系;列表页只选择任务字段,详情页需要时再加载所有者公开信息。

容易混淆的地方

类型中的可选字段与数据库可空约束相关但不完全相同;应用没传值、数据库默认值与实际 NULL 要分别理解。

这一小节记住:Schema 描述数据契约,查询应只加载当前行为需要的字段与关系。

概念 3

迁移文件、开发数据库与生产数据库

先用白话理解

如果旧表里已经有一万行数据,突然加一个必填字段,这一万行都没有值。更稳的做法通常是先允许为空、完成回填,再把约束收紧。

基础概念

迁移文件记录数据库结构从一个版本变到下一个版本的具体步骤。开发库用于试验,生产库含真实数据,执行策略必须考虑兼容、锁表、回填和失败恢复。

进一步理解

新增非空字段时,旧行没有值。安全过程常分为增加可空字段、部署兼容代码、回填旧数据、再收紧约束。

迁移应进入版本控制并先在含真实规模副本上演练。直接手改生产表会让源码、迁移历史和实际结构失去一致。

放进实际场景

为任务增加 priority:先允许为空并让新代码兼容,批量回填默认值,验证无 NULL 后再添加非空约束。

容易混淆的地方

代码回滚不自动回滚数据库。某些结构删除和数据转换不可逆,必须单独设计向前修复或恢复方案。

这一小节记住:数据库结构变化要被记录、审查、演练,并与应用版本保持兼容。

Prisma 关系模型
运行前先确认
  • Prisma Schema 中已经存在 User 模型及其 tasks 反向关系。
  • 生成迁移前确认目标数据库与现有数据,不直接对生产执行开发重置。
model Task {
  id        String   @id @default(cuid())
  title     String
  done      Boolean  @default(false)
  ownerId   String
  owner     User     @relation(fields: [ownerId], references: [id])
  createdAt DateTime @default(now())
}
这段代码在做什么

ownerId 是外键值,owner 描述 Prisma 层关系;数据库迁移会建立相应约束。

  1. id 使用 cuid 生成主键,done 与 createdAt 由数据库/客户端提供默认值。
  2. ownerId 保存真实外键值,owner 声明该字段引用 User.id。
  3. 修改模型后需生成并审查迁移 SQL,再更新客户端类型。
你应该观察到

迁移创建任务表、外键和默认值;无效 ownerId 被约束拒绝,查询时可以按需加载 owner,而不是复制用户数据。

02

AI COLLABORATION

AI 如何参与

让 AI 生成迁移后必须检查实际 SQL,确认没有意外删除、重建或丢失数据。

推荐协作顺序

  1. 1

    把当前 schema、目标变化和已有数据情况发给 AI。

  2. 2

    让它先说明旧数据会遇到什么,再给分阶段迁移方案。

  3. 3

    生成迁移后请它解释 SQL,但最终以测试数据库的真实结果为准。

可直接使用的 Prompt
请为 Next.js + PostgreSQL + Prisma 的任务应用设计 User、Task 模型。Task 包含 id、title、done、ownerId、createdAt。先解释关系和删除策略,再给 schema。随后模拟新增必填 dueAt,说明为什么不能直接上线以及安全迁移步骤。
应该得到什么

模型有清晰主键、外键和时间字段;迁移方案考虑旧数据,不只给 prisma migrate 命令。

人工检查清单

  • 确认没有保存明文密码,级联删除符合产品意图,生成的迁移 SQL 会被人工检查。
  • 让 AI 展示迁移实际 SQL、旧数据处理和新旧应用并存时的兼容路径。
  • 在含旧数据的测试副本执行迁移、回退或恢复演练,并核对行数与约束。
03

COMMON TRAPS

常见误区

错误不是需要隐藏的失败,而是帮助你看清系统边界的证据。下面三类问题在 AI 辅助学习中最常出现。

误区 1

生成迁移后直接上线

你会看到
只看到命令成功,没有读它将删除或重建什么。
为什么发生
ORM 的便捷 API 会隐藏生成 SQL,未经检查的关系加载可能产生大量查询或越过归属条件。
怎样纠正
检查迁移 SQL,在带旧数据的副本上完整跑一遍。
误区 2

给旧表直接加必填字段

你会看到
历史记录没有值,迁移立刻失败。
为什么发生
直接改生产库没有可重复历史,其他环境无法同步,下一次自动迁移还可能误判当前状态。
怎样纠正
先允许为空或提供默认值,回填以后再收紧约束。
误区 3

把 ORM 当成不用懂 SQL

你会看到
查询很慢或关系异常时,只会继续换 ORM 写法。
为什么发生
破坏性重置为开发空库设计,会删除数据,不应出现在生产修复流程。
怎样纠正
查看生成的 SQL、索引和数据库约束,理解最终执行发生在哪里。
04

HANDS-ON

动手任务

为现有表新增可空字段,生成迁移、检查 SQL、执行并验证旧数据。

准备条件
  • 完成 6.2「SQL 与关系型数据库直觉」

跟着做

  1. 01

    安装并初始化 Prisma,连接本地 PostgreSQL。

  2. 02

    定义 User、Task 及一对多关系。

  3. 03

    生成第一份迁移并阅读 SQL。

  4. 04

    写 seed 或脚本创建一名用户与任务。

  5. 05

    用 Prisma Client 完成创建、查询、更新并核对数据库。

完成标志

数据库结构变化有记录、可审查、可重复。

加餐挑战

新增 dueAt 可选字段,写出从可选到必填的两阶段迁移计划。

离开本课前,自问四件事

  • ORM、Schema、生成客户端与数据库分别承担什么?
  • 关系加载为什么要按用例选择而不是默认全部取得?
  • 给有旧数据的表新增必填字段应怎样分阶段?
  • 为什么应用代码回滚并不保证数据库也能回到原状态?
完成本课了吗?

确认完成后,会同步更新学习中心的课程学习进度。