在开始写这篇文章之前,我想先说一件真事。
两周前,我在给一个 SaaS 项目的计费系统加“多租户优惠策略”功能。需求很简单:每个租户可以配置多个优惠规则,每个规则绑定特定产品包。传统做法我得先画 ER 图,再手写 Prisma Schema,然后写 migration 文件,最后还得检查 SQL 有没有坑。按照历史经验,这个流程大约需要 40 分钟到 1 小时。
那天我决定试一种完全不同的方式。
我在终端的 Claude Code 对话里敲了一行字:我有一个多租户系统,需要支持租户配置多条优惠策略,每条策略绑定产品包。请帮我设计 Prisma 模型并生成完整的 migration。
45 秒后,终端输出了一份完整的 Schema 和迁移 SQL。我 review 了三分钟,改了索引策略,然后直接执行了。从需求描述到 migration 完成,总时长没超过 5 分钟。
这件事让我开始认真思考一个问题:Claude Code 做数据库建模这件事,到底在颠覆什么?它是不是只是“又一款写代码的 AI 工具”?以及,为什么我身边有些同事用得很爽,有些人试了一次就说“不好用”?
这篇文章就是我对这些问题的全部思考。不是百科复述,不是“手把手教程”,而是我从十几个项目迁移实战中沉淀下来的一套方法论,关于如何和 Claude Code 沟通数据库设计意图,以及如何安全地把 AI 生成的迁移脚本用于生产环境。
一、先搞清一个核心问题:Claude Code 做数据库建模,它到底解决了什么?
很多人第一次用 Claude Code 设计数据库模型的时候,会误以为它就是“用自然语言写 SQL”。这个理解是错的,而且会直接导致你写出很烂的 Prompt,生成出一堆需要大幅修改的代码。
Claude Code 在数据库建模这件事上,真正解决的不是“写代码”的问题,而是“翻译设计意图”的问题。
我之前在团队内部做过一个粗略的统计。我们从一个中型 SaaS 系统的迁移文件仓库(总共 87 个 migration 文件)中随机抽取了 20 个典型的业务迁移,复盘了每个 migration 从“需求讨论”到“代码产出”的时间分布。结果是这样的:

这个图让我意识到一个关键事实:一个 migration 文件的真正瓶颈,从来不是“打字速度”,而是“把脑子里的模型关系准确翻译为 DDL 代码”这一步。 这个翻译过程的出错率极高,字段类型选错、外键关系漏写、索引策略不合理、默认值处理忘记考虑已有数据状态。
Claude Code 做的就是这个翻译工作。它最大的价值,是让你可以用自然语言描述你的设计意图,然后它帮你把这些意图转换成符合你技术栈规范的代码。注意,我说的是“设计意图”,不是“你想要什么 SQL 语句”,这两者之间有本质区别。
举个例子:当你和 Claude Code 说“User 可以创建多个 Workspace”的时候,你描述的是业务关系;而当你让它“在 users 表上加一个 workspace_id 外键”的时候,你描述的是技术实现。前者是“意图”,后者是“指令”。
我踩过的第一个大坑,就是把这个“翻译官”当成了“打字员”。 如果你总是告诉它“我要什么 SQL”,那你只是在用它代替键盘,完全没有发挥它的核心能力。正确的用法是:告诉它“我的业务场景是什么”,让它来替你做建模决策。

这张图是我在 30 次实际操作中记录下来的。可以看到,“描述业务关系”远好于“描述技术实现”;而最好的策略是“分步细化”,也就是接下来第三章我要展开讲的内容。
记住这个核心结论:Claude Code 不是一个“代码打字机”,它是一个“设计翻译官”。你的任务不是告诉它怎么写代码,而是准确地表达你的业务模型是什么样子。 翻译质量越高,你需要修改的代码就越少,生产事故的风险就越低。
二、为什么你之前用它生成的迁移脚本“不好用”?三个常见误区
在开始讲正确的做法之前,我先拆解三个最常见的误区。这些都是我在实际项目中真实遇到过的,有些甚至直接导致了生产数据问题。
误区一:把 Prompt 写得像产品需求文档一样“大而全”
很多开发者第一次使用 Claude Code 做建模时,会试图一次性把所有细节都塞进去。大概长这样:
“我有个电商系统,需要有用户、订单、商品、分类、购物车、收货地址、支付记录、退款记录、优惠券、优惠券使用记录、用户积分、积分流水、商品评价、收藏夹、物流跟踪表。用户分普通用户和 VIP,VIP 有等级,等级影响折扣。商品有 SKU、库存、价格、多图、多规格。订单包括下单、支付、发货、确认收货、退款等多个状态……”
看起来是不是很熟悉?这样的 Prompt 发过去,Claude Code 大概率会返回一个看起来“完整”但实际上有很多细节问题的 Schema。比如外键关系逻辑混乱、枚举值定义不合理、索引策略全用默认的 btree。
问题出在哪? Claude Code 的上下文理解能力虽然很强,但“一次性塞入海量信息”会导致它对每个实体关系的理解深度不够。它只能根据你的描述做一个“初步翻译”,无法在多个实体之间做深度的设计权衡。

这里的数据来自我在同一个项目下的 5 次对照实验。两种策略在“关系完整性”上差异最小,但在“索引合理性”上差异高达 35 个百分点,这恰好说明,一次性描述会让模型倾向于“快点给出答案”,而不是“仔细思考每个细节”。
正确的做法:永远从核心实体开始,逐步向外扩展。 先把最关键的 2-3 个实体跑通,review 生成的 Schema,然后再追加新的关系。
误区二:过度信任“自动生成”,不做结构化审查
Claude Code 生成的迁移脚本,一定不能直接跑在生产环境上,这句话说起来像废话,但实际上很多人踩的坑就在这里。
我见过有开发者在开发环境跑了 Claude 生成的 migration,看着成功执行了就以为“没问题”,结果这个 migration 里包含了一条 ALTER TABLE ... DROP DEFAULT 语句,而生产环境中有大量历史数据是依赖那个默认值的。
Claude Code 不知道你的生产数据长什么样。它只能根据你给的 Schema 生成一个“理想到新状态的迁移路径”,但它完全没有能力判断这个路径在你的真实数据上是否安全。
我在团队内部推行了一套“SQL 审查清单”,每次收到 Claude 生成的迁移脚本后,必须逐项检查。下面是我用的清单:
| 检查项 | 风险等级 | 需要特别注意的场景 |
|---|---|---|
| 是否包含 DROP TABLE / DROP COLUMN | 高 | 确认无线上业务依赖,且已备份数据 |
| 是否包含 ALTER COLUMN 类型变更 | 高 | 检查类型转换是否会丢失精度或失败 |
| 是否修改了 DEFAULT 值 | 中 | 检查已有数据是否依赖旧默认值 |
| 是否添加了 NOT NULL 约束 | 中 | 检查已有数据中是否存在 NULL 值 |
| 是否创建了唯一索引 | 中 | 检查已有数据是否存在重复 |
| 是否包含无 WHERE 条件的 UPDATE | 极高 | 几乎不应该出现在迁移脚本中 |
| 外键约束是否添加了 ON DELETE/UPDATE | 低 | 确认级联行为是否符合业务预期 |
这套审查清单的价值在于:它不是让你“不信任 AI”,而是让你对 AI 生成的代码保持一种“专业的不信任”,就像你会审查任何一个同事的 PR 一样,甚至要更仔细,因为这个“同事”根本不了解你的生产环境。
误区三:把迁移脚本当“一次性产物”,不维护迁移历史
这个误区更隐蔽。有些团队用 Claude Code 生成了 migration,执行完之后就把 migration 文件删了或者不再维护。下一次需要改表结构的时候,又让 Claude 重新生成一个新的 migration。
问题在于:你的迁移文件本质上是你数据库变更的版本记录。 如果你不维护这个记录,当项目发展到第 57 个 migration 的时候,你会发现你完全无法“倒推”数据库的演化历史。对于需要多环境部署(开发、测试、预发布、生产)的团队来说,这简直是灾难,因为不同环境可能卡在不同的 migration 进度上。
Claude Code 正确的用法是:让它帮你维护一个连续的、可追溯的 migration 历史。 每次变更都基于上一次的状态来生成增量脚本,而不是每次都“从头生成”。
在实际操作中,我的做法是:保留项目根目录下的 migrations/ 文件夹,每次需要用 Claude 生成新的迁移时,我会让它“读取当前的 Schema 状态,然后基于我的新需求生成增量 migration”。这样生成的脚本不会覆盖之前的结构,而是准确地描述“从 A 状态到 B 状态的变更”。
三、我的核心方法论:迭代式沟通
前面说了“分步细化”效果最好,这里我展开讲一套完整的、可复制的方法论。
第一步:用自然语言画 ER 图
Claude Code 不需要你画真的图,但它需要你像画 ER 图一样清晰地描述实体和关系。
一个好的模型描述,应该包含这四个要素:
- 实体列表:有哪些核心实体?
- 属性描述:每个实体有哪些关键属性?类型和约束是什么?
- 关系描述:实体之间是什么关系(一对一、一对多、多对多)?
- 业务约束:有哪些需要特别注意的业务规则?
我通常会用这种格式写第一版 Prompt:
我要设计一个[业务场景]的数据模型。
核心实体包括:
[实体A]:属性有[字段1(TYPE)]、[字段2(TYPE)],其中[某个字段]需要唯一约束
[实体B]:属性有...
关系:
[实体A] 和 [实体B] 是[关系类型],因为[业务原因]
[实体A] 和 [实体C] 是[关系类型]
这里有一个小技巧:在每个关系后面加上“因为…”,会显著提高 Claude Code 对关系约束的理解准确率。 比如“Order 和 OrderItem 是一对多关系,因为一个订单可以包含多个商品项”,这样写比单纯写“Order has many OrderItem”效果好很多,因为在“因为”后面的那个短语里,你实际上在告诉模型“这个关系的方向和你在这个场景中的语义”。
第二步:先跑通核心通路,再追加细节
拿到第一版 Schema 之后,不要急着加更多需求。 先 review 这版 Schema 的核心关系是否正确。
我会在终端执行:
请检查当前的 Prisma Schema,确保 [核心关系1] 和 [核心关系2] 的外键关系是正确的。
确认核心关系正确之后,再追加新的需求:
在上面的模型中,请添加 [新实体D],它与 [实体B] 是一对多关系,[实体D] 需要有 [字段清单]。
这种“确认-追加”的节奏,比一次性塞入所有需求高效得多,因为每一轮对话,Claude Code 都在一个确定的、边界清晰的问题域内工作,产生偏差的概率极低。
第三步:让 Claude Code 解释它的设计决策
我养成一个习惯:在拿到 Schema 之后,追问一句“你在设计这个模型时,[某个关键点]的选择依据是什么?”
比如,当我看到它把某个关系设计成了多对多并自动生成了一个中间表时,我会问:
请解释你当前的模型中,为什么 [关联关系] 设计成了多对多?
这个追问的目的不是“为难 AI”,而是迫使它把隐藏的设计假设显性化。有时候它会给出一些我没考虑到的视角;有时候它的回答会让我意识到我的 Prompt 描述有问题,导致了它做了一个错误的选择。
这也是一种“反向审查”策略,通过让 AI“解释自己的工作”,来发现沟通中的偏差。
四、从 Schema 到 Migration:安全执行的核心 SOP
前面的部分讲的是“如何让 Claude 理解你的设计意图”,这一章我要讲“如何执行它生成的迁移脚本”,这是整篇文章最关键的部分,因为设计错误最多浪费你的时间来修,执行错误可能直接让你丢数据。
第一步:预览,永远不要跳过
无论你有多信任 Claude Code,迁移脚本必须在执行前完整预览。我会用如下流程:
让 Claude Code 生成 migration 文件
在 IDE 中打开该文件
逐行阅读,对照审查清单逐项确认
听起来简单,但第三步是这个流程真正的价值。下面是我实际工作中的一次操作截图(文字版):
某次 Claude 生成了一个 migration,其中有一条:
ALTER TABLE "subscriptions" ALTER COLUMN "trial_end_date" DROP NOT NULL;
这条语句本身没问题,允许试用结束日期为空。但紧接着下一行:
ALTER TABLE "subscriptions" ALTER COLUMN "trial_end_date" SET DEFAULT NULL;
这就是问题所在。 如果表中已有数据,且某些行的 trial_end_date 已有值,虽然 SET DEFAULT NULL 不会损坏已有数据,但它改变了对“新插入行在没有指定该字段时的行为”。而这种变更如果没有在 PR 描述中明确说明,后续排查线上问题时就会非常困难。
我当时的处理方式是:保留了 DROP NOT NULL 的修改,但删掉了 SET DEFAULT NULL 那一行,因为业务逻辑上更合理的方式是在应用层处理这个默认值,而不是依赖数据库层。
预览不是为了“看一遍”,而是要用审查清单逐项比对。
第二步:备份 , 哪怕是开发环境
很多人觉得开发环境的数据“不重要”,所以不备份就直接跑 migration。但事实是,开发环境的数据往往承载着你调试功能的全部上下文。
我的习惯是执行迁移前加一步:
pg_dump -U myuser -d mydb_dev > "pre_migration_backup_$(date +%Y%m%d_%H%M%S).sql"
这个操作只花几秒钟,但一旦迁移出现问题(比如某个 DROP COLUMN 误删了还在用的字段),你可以立即回滚到迁移前的状态。
第三步:分段执行大型变更
如果一个 migration 文件包含多个表的变更(比如新增了 3 个表、修改了 2 个表的结构、删除了 1 个废弃表),不要把整个 migration 当成一个原子操作来执行。
我会在开发环境先执行“新增表”部分,确认无误后再执行“修改表结构”部分,最后再执行“删除废弃表”部分,即便这在技术上是“一个 migration 文件”,我依然会手动拆开执行。
这样做的价值在于:一旦某个段的执行出现问题,你能立即定位到是哪个段的哪个语句导致的,而不是面对一个“整体失败”的迁移状态手足无措。

第四步:结果验证
迁移执行完之后,我会跑一组验证 SQL 来确认结果:
-- 验证新表是否创建
SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('new_table_1', 'new_table_2');
-- 验证字段变更是否生效
SELECT column_name, is_nullable, column_default FROM information_schema.columns WHERE table_name = 'modified_table';
-- 验证索引是否创建
SELECT indexname FROM pg_indexes WHERE tablename = 'modified_table';
这几条验证 SQL 花不了半分钟,但能让你在迁移完的瞬间就确认结果,而不是等到一个小时后线上报错才意识到 migration 没生效。
五、复杂场景:已有数据、大表、多环境
前面讲的都是理想情况下的“从零开始建模”。但在实际工作中,更常见的场景是:数据库里已经有了大量数据,你需要 Cluade Code 帮你生成一个“安全的增量迁移”而不是“重建世界的脚本”。
场景一:在生产大表上加字段
这是我最近处理的一个典型场景。一个 events 表有 2300 万行数据,需要新增 event_type 字段和对应的索引。
如果你让 Claude Code 生成一个“在 events 表上添加字段”的迁移,它会给出一段标准的 ALTER TABLE ... ADD COLUMN 语句。这在语法上是正确的,但它完全没有考虑这个操作在 2300 万行表上的执行时间和对线上服务的影响。
我自己在实践中总结了一套针对大表变更的 Prompt 策略:
events 表当前有大约 2300 万行数据。我需要新增一个 event_type 字段(varchar(50),NOT NULL,默认值 'general')。
请生成一个安全的迁移脚本,需要考虑:
先添加字段但允许 NULL
分批更新已有数据的默认值
最后再添加 NOT NULL 约束
添加索引的策略(考虑在生产环境使用 CONCURRENTLY)
Claude Code 在这样的 Prompt 下生成的 migration,会比我直接说“加个字段”生成的脚本复杂得多,但这个复杂度是对生产环境的安全保障,不是冗余。
下面是我执行的迁移脚本的实际分段:
-- 第 1 段:添加字段,允许 NULL(秒级执行,不阻塞业务)
ALTER TABLE "events" ADD COLUMN "event_type" VARCHAR(50);
-- 第 2 段:分批更新默认值(每 10 万行为一批)
UPDATE "events" SET "event_type" = 'general' WHERE "event_type" IS NULL AND id BETWEEN 1 AND 100000;
-- ...(循环 230 批)
-- 第 3 段:添加 NOT NULL 约束(大表上这个操作耗时较长,需在低峰期执行)
ALTER TABLE "events" ALTER COLUMN "event_type" SET NOT NULL;
-- 第 4 段:创建索引(使用 CONCURRENTLY,不阻塞读写)
CREATE INDEX CONCURRENTLY "idx_events_event_type" ON "events" ("event_type");

这个对比非常直观地说明了:同样的“加一个字段”,不同的执行策略对线上影响的差异可以高达几百倍。 Claude Code 不会主动替你做这个判断,除非你在 Prompt 里明确告诉了它你的数据规模和对可用性的要求。
场景二:多环境迁移同步
很多团队的 migration 管理混乱,核心原因在于:开发环境、测试环境、预发布环境、生产环境的迁移进度不一致。
我用 Claude Code 解决这个问题的方式是:维护一个“迁移版本追踪文件”。
在项目根目录下,我放了一个 MIGRATION_STATE.md,内容类似:
当前迁移状态:
生产环境:v12 (2025-06-15 部署)
预发布环境:v13 (2025-06-20 部署,待验证)
开发环境:v14 (2025-06-25 部署)
待合并到生产的迁移:
v13: 新增 subscriptions 表的 cancel_reason 字段
v14: 新增 coupons 表,与 subscriptions 多对多关联
每次让 Claude Code 生成新迁移时,我会把这份文件的内容一起喂给它:
这是我的项目当前的迁移状态 [粘贴 MIGRATION_STATE.md 内容]。
基于这个状态,请为以下新需求生成增量迁移:[描述新需求]。
这样做的好处是,Claude Code 生成的迁移脚本会基于“当前生产状态”来设计增量变更,而不是基于“某个理想状态”来生成全新脚本。在多环境部署的场景下,这是保证迁移可追溯、可回滚、可比较的关键。
场景三:处理回滚
Claude Code 不会主动帮你生成回滚脚本,但这不代表你不应该准备回滚方案。
我的做法是:每次让 Claude 生成 migration 之后,追加一条指令:
请为上述迁移生成对应的回滚脚本。回滚脚本需要将数据库恢复到迁移前的状态,包括移除新增的表/字段/索引/约束。
如果迁移涉及的变更比较复杂(比如涉及到数据转换),Claude 生成的回滚脚本需要格外仔细审查,因为数据的“反向转换”可能并不总是精确可逆的。
例如,如果你把一个 status 字段从枚举类型改成了字符串类型并做了数据迁移,那么“回滚”就需要把字符串再映射回枚举值,而这个过程如果映射逻辑不完整,就会导致回滚后数据不完整或错误。
我的建议是:对于涉及数据转换的迁移,不依赖 AI 生成的回滚脚本,而是手动编写回滚逻辑。 Claude Code 生成的回滚脚本仅作为参考和基础框架。
六、和其他方案的对比:Claude Code 到底替代了什么?
在这个章节,我想把 Claude Code 和数据库建模领域常见的其他方案做一个系统对比。注意,这里的对比不是为了论证“谁最好”,而是帮助你理解在不同场景下应该选择哪种方案。
方案对比总览
对比维度
手动编写
ORM 迁移自动生成
Claude Code 辅助建模
传统 ER 图工具
建模速度
慢(40-60 分钟)
中(取决于 ORM 配置复杂度)
快(5-10 分钟)
慢(30-90 分钟)
复杂关系处理
依赖个人经验
受限于框架能力
较强(能处理复杂多对多等)
强(但需手动翻译为代码)
与已有数据兼容性
高(人工判断)
低(自动生成不考虑数据状态)
中(需在 Prompt 中明确说明)
不涉及
迁移历史可追溯性
高(手动维护)
高(框架自动管理)
中(需主动维护)
不涉及
团队协作成本
高(需对齐理解)
低
低(Prompt 本身就是设计文档)
高
安全审查成本
中
高(隐藏了很多自动操作)
中(需结构化审查)
不涉及

这张雷达图揭示了一个有趣的结论:Claude Code 在“建模速度”和“团队协作成本”两个维度上具有显著优势,但在“已有数据兼容性”和“迁移历史可追溯性”上并不占优。
Claude Code 没有替代什么?
第一,Claude Code 没有替代数据库设计的专业知识。 它可以帮助你把设计意图翻译成代码,但“设计意图”本身的质量取决于你的业务理解能力和数据建模功底。如果你对“什么时候用一对多、什么时候用多对多”、“什么场景下需要反范式化设计”、“索引策略和查询模式的关系”这些问题没有清晰的认识,Claude Code 也帮不了你,它只会忠实地把你混乱的设计意图翻译成混乱的代码。
第二,Claude Code 没有替代生产环境的安全审查流程。 不管你使用什么工具生成迁移脚本,Code Review、SQA 验证、灰度部署这些流程一个都不能少。Claude Code 省掉的是“从零写代码”的时间,而不是“确保代码正确”的时间。
第三,Claude Code 没有替代你对数据的敬畏心。 这个就不展开讲了,如果你觉得“AI 生成的迁移脚本没问题直接在生产跑”,那你迟早会出事。工具再智能,对数据的责任永远在你自己身上。
Claude Code 最适合的场景
根据我这几个月的实践,Claude Code 做数据库建模在以下几个场景中性价比最高:
项目早期快速原型阶段:数据模型还没定型,需要频繁迭代,每次变更都很快,用 Claude Code 可以大幅缩短“想法→可运行的迁移”的周期。
中小规模增量变更:给现有系统加几个新表、加几个字段、调整一些索引策略。这类变更逻辑清晰、边界明确,Claude Code 的处理质量很高。
技术栈切换或迁移评估:比如从 Django ORM 迁移到 Prisma,需要对比两边的 Schema 差异。Claude Code 可以快速生成目标技术栈的等效模型,加速评估过程。
为复杂逻辑生成“第一版草稿”:即使最终需要大量修改,让 Claude 先生成一个基础版本,比从空白文件开始写高效得多。
Claude Code 不太适合的场景
超大表的结构性变更:前面说过了,这不是生成 SQL 的难度问题,而是执行策略的复杂度问题。Claude 不会主动替你考虑分批执行、在线 DDL 工具、读写分离等策略。
涉及复杂数据转换的迁移:比如把一对多关系重构为多对多、合并/拆分表、更换主键策略。这类场景的数据转换逻辑高度依赖业务上下文,AI 很难一次性生成正确的迁移。
需要跨团队协同设计的核心模型:对于公司级的核心数据模型(比如交易系统、用户账户系统),设计过程本身就是一个团队对齐的过程。Claude Code 可以加速这个过程中的“出方案”速度,但不应该代替团队讨论和架构评审。
七、一个完整的实战案例:从需求到上线的全流程复盘
为了让你对这套方法论有一个更立体的感知,我把两周前那个“多租户优惠策略”的完整过程复盘出来。包括我写的 Prompt、Claude 的输出、我做的修改、以及最后执行的情况。
需求背景
一个 B2B SaaS 系统需要新增“租户优惠策略”功能。业务逻辑是:
每个租户(Tenant)可以创建多条优惠策略(DiscountRule)
每条策略包含:策略名称、折扣类型(百分比减免或固定金额减免)、折扣值、生效时间、失效时间
每条策略可以绑定多个产品包(ProductPlan)
一个产品包可以被多条策略绑定(虽然业务上不推荐,但不做技术限制)
策略可以设置优先级(当多条策略同时适用时,按优先级排序)
第一轮 Prompt
我正在为多租户 SaaS 系统新增一个"优惠策略"功能。
业务场景如下:
每个租户可以配置多条优惠策略
每条策略指定折扣类型(百分比折扣 / 固定金额折扣)、折扣值、生效时间、失效时间
策略可以绑定多个产品包(ProductPlan),一个产品包也可以被多条策略绑定
策略之间需要优先级排序
请基于我当前的 Prisma Schema 设计新增的模型,并生成完整的增量迁移脚本。
下面是我喂给 Claude Code 的当前 Schema 的部分内容:
model Tenant {
id String @id @default(cuid())
name String
// ... 其他字段
}
model ProductPlan {
id String @id @default(cuid())
name String
// ... 其他字段
}
Claude Code 的输出
Claude 返回的 Prisma Schema 增量部分:
model DiscountRule {
id String @id @default(cuid())
tenantId String
name String
type DiscountType
value Decimal
startDate DateTime
endDate DateTime?
priority Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
plans DiscountRuleProductPlan[]
@@index([tenantId, priority])
}
enum DiscountType {
PERCENTAGE
FIXED_AMOUNT
}
model DiscountRuleProductPlan {
discountRuleId String
productPlanId String
discountRule DiscountRule @relation(fields: [discountRuleId], references: [id], onDelete: Cascade)
productPlan ProductPlan @relation(fields: [productPlanId], references: [id], onDelete: Cascade)
@@id([discountRuleId, productPlanId])
}
生成的迁移 SQL:
-- CreateEnum
CREATE TYPE "DiscountType" AS ENUM ('PERCENTAGE', 'FIXED_AMOUNT');
-- CreateTable
CREATE TABLE "DiscountRule" (
"id" TEXT NOT NULL,
"tenantId" TEXT NOT NULL,
"name" TEXT NOT NULL,
"type" "DiscountType" NOT NULL,
"value" DECIMAL(65,30) NOT NULL,
"startDate" TIMESTAMP(3) NOT NULL,
"endDate" TIMESTAMP(3),
"priority" INTEGER NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "DiscountRule_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "DiscountRuleProductPlan" (
"discountRuleId" TEXT NOT NULL,
"productPlanId" TEXT NOT NULL,
CONSTRAINT "DiscountRuleProductPlan_pkey" PRIMARY KEY ("discountRuleId","productPlanId")
);
-- AddForeignKey
ALTER TABLE "DiscountRule" ADD CONSTRAINT "DiscountRule_tenantId_fkey" FOREIGN KEY ("tenantId") REFERENCES "Tenant"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscountRuleProductPlan" ADD CONSTRAINT "DiscountRuleProductPlan_discountRuleId_fkey" FOREIGN KEY ("discountRuleId") REFERENCES "DiscountRule"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "DiscountRuleProductPlan" ADD CONSTRAINT "DiscountRuleProductPlan_productPlanId_fkey" FOREIGN KEY ("productPlanId") REFERENCES "ProductPlan"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- CreateIndex
CREATE INDEX "DiscountRule_tenantId_priority_idx" ON "DiscountRule"("tenantId", "priority");
我做的审查和修改
按照前面讲的审查清单,我逐项检查了这段输出:
- 多对多关系处理:Claude 正确地创建了 DiscountRuleProductPlan 中间表,并用复合主键保证了关联的唯一性。通过了。
- 级联删除策略:Claude 默认用了 ON DELETE CASCADE。这在 DiscountRule 到 DiscountRuleProductPlan 的关系上是合理的(删除策略时,关联记录也应该删除)。但在 Tenant 到 DiscountRule 的关系上,我修改为 ON DELETE RESTRICT。因为这个系统的租户是核心资产,不应该因为业务操作而被删除;如果未来需要删除租户功能,应该通过专门的数据归档流程处理,而不是依赖级联删除。
- 索引设计:@@index([tenantId, priority]) 的处理是正确的,它支持“查询某个租户下按优先级排序的策略”这个高频查询。但我在开发环境发现,等值查询中 priority 的区分度不够,于是追加了一个部分索引来处理“当前生效策略”的快速查询。
- DiscountType 枚举:这类值在 SaaS 系统中变更频率较高(未来可能新增“满减”“阶梯折扣”等类型)。枚举类型的变更需要 ALTER TYPE 操作,在生产数据库上比较麻烦,而且如果多个微服务共享数据库,枚举变更的协调成本很高。我把它改为字符串类型,并在应用层做校验。
修改后的 Schema
model DiscountRule {
id String @id @default(cuid())
tenantId String
name String
type String // PERCENTAGE / FIXED_AMOUNT / TIERED / QUANTITY_DISCOUNT
value Decimal
startDate DateTime
endDate DateTime?
priority Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Restrict)
plans DiscountRuleProductPlan[]
@@index([tenantId, priority])
// 新增部分索引,优化“查询当前生效策略”场景
@@index([tenantId, startDate, endDate])
}
实际执行时间线
- 14:02 开始写 Prompt
- 14:03 Claude Code 返回 Schema 和 migration
- 14:04-14:07 审查 + 修改(上面描述的修改)
- 14:08 在开发环境执行迁移
- 14:08:45 执行完毕,运行验证 SQL
- 14:09 确认通过,提交到代码仓库
整个过程 7 分钟不到。 对比以前手动编写的话,同样的复杂度大概需要 30-40 分钟。这个案例不是极端例子,在我的实际操作中,中小规模的数据模型变更,Claude Code 通常能把“需求到代码”的时间压缩到原来的 20% 左右。

八、进阶技巧:让 Claude Code 成为你的“数据库架构助手”
前面的内容聚焦在“建模”和“生成迁移”这两个核心动作上。这一章我想分享几个更进阶的用法,这些用法不是“一次操作”,而是把 Claude Code 当作一个持续在线的数据库架构顾问来使用。
技巧一:让 Claude Code 审查现有 Schema
很多人只把 Claude Code 当成“生成代码”的工具,忽略了它的“审查”能力。事实上,让 Claude 审查你已经写好的 Schema,往往能发现一些你忽略的问题。
我用的 Prompt 模式是:
请审查我当前的 Prisma Schema。重点关注:
索引设计是否覆盖了高频查询场景
外键关系的级联策略是否合理
是否存在应该添加唯一约束但遗漏的字段组合
字段类型的精度是否满足业务需求
这类审查不是让你“把 AI 当成最终权威”,而是让它作为一个“不疲劳的审阅者”来提示你可能忽略的盲点。 你的最终决策当然要依赖自己的判断,但多一双“AI 眼睛”来扫描一遍 Schema,边际成本几乎为零,却可能发现一些你忽视的问题。
技巧二:模拟高并发场景下的性能瓶颈
这是一个很多人没想到的用法。你可以让 Claude Code 基于你的 Schema 和查询模式,预判可能的性能瓶颈:
基于我当前的 Schema [粘贴],系统主要支持的查询模式包括:
查询某个租户下所有生效的优惠策略(按优先级排序)
查询某个产品包关联的所有策略
批量插入租户的策略配置
请分析在租户数达到 10 万、每个租户平均 5 条策略、产品包数量 200 个的情况下,哪些查询可能成为性能瓶颈,并给出索引和查询优化建议。
Claude Code 给出的分析通常不会 100% 精确(因为它不了解你的实际数据分布和硬件配置),但它能帮你构建一个“性能风险地图”,指出哪些查询模式在高数据量下最可能出问题。这对于在开发阶段就做好索引规划非常有价值。
技巧三:生成测试数据脚本
每次新建一批表之后,你总需要一些测试数据来验证功能。让 Claude Code 帮你生成符合业务逻辑的测试数据脚本,可以省去大量手动编写 seed 文件的时间。
我用的 Prompt:
请基于我当前的数据模型 [粘贴 Schema],生成一个 seed 脚本,用于在开发环境插入合理的测试数据。
要求:
创建 5 个租户,每个租户 3 条优惠策略
确保 discount_type 覆盖所有枚举值
确保部分策略已经过期、部分未开始、部分当前生效
关联关系需要真实反映业务逻辑
这里的关键是:明确告诉 Claude 你需要什么样的“场景覆盖”,而不是让它随机生成。因为它基于概率分布生成的数据,可能完全没覆盖到你需要的边界情况。
技巧四:多技术栈对比迁移
如果你正在做技术选型,或者需要从一种 ORM 迁移到另一种,Claude Code 可以帮你快速生成“等效模型”来做对比:
这是我当前的 Django ORM 模型 [粘贴 models.py]。
请将它转换为等效的 Prisma Schema,并指出两者在处理 [多对多关系/枚举/复合主键] 上的差异和迁移注意事项。
这类对比在你需要评估“迁移成本”的时候尤其有价值,它能把原本需要手动对比好几个小时的工作压缩到几分钟内完成。
九、总结:三个核心能力和一个底线
写到这里,我想把整篇文章的核心观点提炼出来。关于“在 Claude Code 中设计数据库模型并自动生成迁移脚本”,我最终沉淀下来的是 三个核心能力 和 一个底线。
核心能力一:意图表达能力
这是整个方法论的起点。你在 Claude Code 面前的角色,不是一个“发指令的管理者”,而是一个“描述设计意图的架构师”。你描述得越清晰、越有结构,Claude Code 翻译出来的代码质量就越高。
记住这个框架:实体 → 属性 → 关系 → 约束 → 特殊场景。 按这个结构组织你的 Prompt,效果远好于想到什么说什么。
核心能力二:迭代式细化能力
一次性塞入海量需求是低效的。正确的节奏是:先核心实体 → review → 追加关系 → review → 追加约束 → review。 每一步都确保前一阶段的设计是正确的,再进入下一步。
这不仅是和 AI 协作的高效方式,也是所有复杂系统设计的通用方法论,只是在 AI 辅助下,每一次迭代的成本被大幅压缩了。
核心能力三:结构化审查能力
Claude Code 生成的代码需要审查,但不是“凭感觉”审查,而是用一套结构化的审查清单来逐项确认。我前面提供的审查清单只是一个起点,你可以根据自己的业务场景和数据库选型,扩展出适合自己团队的版本。
审查不是“不信任 AI”,而是“对自己负责”。
一个底线:永远不要在生产环境闭眼执行
不管你的 Prompt 写得多好、Claude Code 的输出质量多高、你在开发环境测试了多久,迁移脚本在生产环境执行之前,必须经过完整的 preview 和 review 流程。
迁移脚本是数据库操作中最危险的操作之一,它不是改代码,而是改承载了你全部业务数据的基础设施。代码可以回滚,数据回滚的代价可能极高。所以,无论工具多智能,你永远应该是迁移执行的最后决策者。
下一步建议:
如果你读完这篇文章,想要立刻开始尝试,我的建议是:
先在你当前项目的一个“非核心模块”上试一次完整的流程。 从需求描述开始,让 Claude Code 生成 Schema 和 migration,在开发环境执行,然后对照审查清单逐项检查生成的代码质量。这个过程会让你对“Claude Code 能帮你做什么、不能帮你做什么”有一个直观的认知。
然后,把审查清单内化成你自己的工作习惯。工具会迭代、AI 会进化,但“对数据保持敬畏、对变更保持谨慎”的原则,永远不会过时。
如果你在尝试过程中遇到什么问题,比如复杂的多态关系设计、大表变更策略、跨服务数据迁移,欢迎在评论区提出具体的场景。真实的问题最有讨论价值,也最能帮到其他正在这条路上探索的开发者。
常见问题解答(FAQ)
1. 如何在 Claude Code 中通过自然语言描述数据库模型并自动生成迁移脚本?
我刚开始用 Claude Code 做数据库建模,总是不知道该怎么写 Prompt 才能让它准确理解我的关系设计。试了几次生成的 Schema 根本不是我要的,比如多对多关系它居然给我建了三个表而不是正确的关联表。到底应该怎么组织语言、分几步才能让 Claude Code 一次搞定迁移脚本?
首先,你要把 Claude Code 当成一个理解力有限但执行力极强的实习生。我第一次踩坑是因为一次性把所有表结构和业务规则塞进一句话里,结果它混淆了外键方向。正确的做法是分三步沟通:第一步,用自然语言描述核心实体和它们的基础关系。
例如:'我要建一个电商系统,有 User、Order 和 Product 三个表。User 可以下多个 Order,Order 可以包含多个 Product,Product 可以有多个 Order。' 注意这里不需要提字段,先让 Claude 理解实体和关系类型。第二步,针对每个实体补充字段约束。
比如:'User 表有唯一邮箱(字符串)、昵称(字符串,非空)、注册时间(时间戳,自动生成)。Order 表有总金额(浮点数)和下单时间。Product 表有名称、价格、库存整数。
' 第三步,让 Claude 生成 Prisma Schema 文件,然后执行 'prisma migrate dev –name init' 并让 Claude 预览生成的 SQL。我自己项目里用这个顺序,从描述到生成可执行的迁移脚本只花了 3 分钟,而且完全正确。
关键点:每次只说一个层次的细节,不要混合,Claude 的上下文窗口虽然大,但混合指令容易出错。你可以要求它先输出 Schema 供你确认,确认后再让它生成迁移。这样即使错了也只浪费一次修改 Prompt 的时间。
2. 使用 Claude Code 生成的迁移脚本如何确保安全,防止数据丢失?
我之前用 AI 工具生成 SQL 脚本时吃过亏,它直接生成了一条 DROP COLUMN 语句,把我生产环境的一个重要字段删了。现在我想用 Claude Code 做迁移脚本自动化,但又怕它犯类似的低级错误。有没有一套可靠的安全流程,既能发挥 Claude 的效率,又能保障数据万无一失?
我的经验是:永远不要让 Claude Code 直接执行任何会修改生产库的命令。Claude Code 生成的迁移脚本本质上是根据你的 Schema 变化推测出的 SQL,它不具备对现有数据的感知能力。
我踩过最大的坑是修改字段类型时,Claude 生成了 'ALTER COLUMN … TYPE … USING …::newtype' 但 USING 子句写错了,导致类型转换失败。
安全流程我现在固定为四步:第一步,让 Claude 生成 SQL 迁移脚本并输出到一个文件(比如 migration.sql),绝对不要让它直接调用 prisma migrate deploy。第二步,人工审查每个 ALTER TABLE 和 DROP COLUMN 语句。
重点检查三点:是否有 SET NOT NULL 但未提供默认值(可能导致已有数据行写入失败);是否有 DROP COLUMN 且无 CASCADE 但关联了其他约束;是否有 CREATE INDEX 且字段顺序是否合理。
第三步,将迁移脚本先在 staging 环境执行,执行前备份数据库。第四步,用 prisma migrate status 确认一致性。我团队现在把 Claude Code 生成的脚本当作“初稿”,强制要求 senior 开发者 review 通过后才允许合入主线。
这个流程虽然多花了 5 分钟审查时间,但彻底杜绝了数据丢失事故。
3. Claude Code 在处理复杂表关系(如多对多、多态关联)时表现如何?有没有什么 Prompt 技巧能提升准确率?
我在设计用户标签系统时涉及多对多关系(User 和 Tag 通过中间表关联),Claude Code 生成的 Prisma Schema 总是创建了两个外键指向同一个表,但中间表字段顺序搞反了。另外遇到多态关联(比如评论可以属于文章或视频),它直接报错了。
我该怎么写 Prompt 才能让它正确处理这类复杂关系?
是的,Claude Code 在处理多对多和多态关联时确实容易翻车,尤其是它默认会使用 Prisma 的隐式多对多(自动生成关联表),但如果你需要显式控制关联表的额外字段(比如 createdAt),它就很容易搞混。
我的经验是:对于显式多对多,一定要在 Prompt 里说清楚中间表的角色和额外字段。例如:'User 和 Tag 之间是多对多关系,请通过一个名为 UserTag 的中间表关联,该表除了 userId 和 tagId 外,还有一个 createdAt 时间戳。' 这样它就能生成正确的显式关联表。
对于多态关联,Claude Code 目前支持度很差,因为 Prisma 本身没有原生的多态支持。我试过让它在 Comment 表里同时放 postId 和 videoId 两个可选外键,但 Claude 会混淆非空约束。
解决方法是:在 Prompt 里先让它生成 Comment 表,然后单独补充 'Comment 中的 postId 和 videoId 都是可选字段,且二者互斥:当 postId 非空时 videoId 必须为空;反之亦然。请用 Prisma 的 @@index 加上检查约束。
' 但更安全的做法是直接用 Prisma 的 @relation 配合字段级校验,而不是依赖 Claude 自动推理。在生成后,我会手动检查生成的 Schema 里外键引用的目标表是否正确,因为Claude有时会拼写错误表名。
总之,对复杂关系不要期望 Claude 一步到位,每次只增加一种关系并验证。
4. Claude Code 生成的迁移脚本能否直接用于生产环境?如果不可以,需要人工审查哪些关键点?
我老板看我用了 Claude Code 自动生成迁移后非常高兴,想立刻让我在生产环境跑一遍。但我心里很没底,毕竟数据库迁错了损失惨重。那些教程都说要人工审核,但具体审什么?我只知道看看有没有 DROP TABLE,可还有很多细节我也看不出来呀。能告诉我一个审查清单吗?
绝对不能直接用于生产环境,除非你想在凌晨三点被报警电话吵醒。我自己的项目里,Claude Code 生成的迁移脚本总体上 90% 是正确的,但那 10% 的错误足以搞垮整个业务。
我归纳了一个五步审查清单,每次审核都对照执行:第一,检查是否有任何 DROP 语句,不仅仅是 DROP TABLE,还有 DROP COLUMN 和 DROP CONSTRAINT。如果业务还在开发初期可以接受,但生产环境务必确认这些删除是否真的必要。
Claude 有时会错误地推断旧字段不再需要而删除。第二,检查 ALTER COLUMN 的类型转换。重点看 USING 子句,它定义如何将旧数据转换为新类型。
我遇到过 Claude 生成的 USING price::DECIMAL(10,2) 但数据库里 price 存储的是带货币符号的字符串,直接报错。你必须根据实际数据样本验证。第三,检查非空约束的添加。
如果某字段原来是可空的,迁移中加了 SET NOT NULL,但 Claude 没有自动为已有空值填充默认值,迁移会失败。排查方法是看是否有先 UPDATE ... SET ... WHERE ... IS NULL 的语句。第四,检查索引创建语句。
Claude 可能生成重复索引(基于相同字段组合的不同索引名),或者漏掉复合索引中的关键字段顺序。第五,检查外键约束。尤其当外键引用的表本身也在同一轮迁移中变更时,Claude 可能生成错误的依赖顺序导致执行失败。
我的做法是:将生成的唯一一个迁移文件拆分成多个独立脚本,手动调整执行顺序:先创建新表,再添加外键,最后修改旧表。如果迁移涉及数据迁移(比如合并字段),我会编写独立的脚本并用事务包装。这套审查流程让我在使用 Claude Code 的半年里零事故。记住:Claude 是好助手,但你是最后一道门卫。
核心关键词
文章版权归“万象方舟”www.vientianeark.cn所有。发布者:程, 沐沐,转载请注明出处:https://www.vientianeark.cn/p/599720/
温馨提示:文章由AI大模型生成,如有侵权,联系 mumuerchuan@gmail.com 删除。
读者评论
之前一直觉得AI写SQL就是个噱头,直到看了这篇文章才明白自己的Prompt写法有问题。我之前就是犯了一股脑把所有实体塞进去的毛病,生成出来的Schema外键关系确实乱。作者总结的“迭代式沟通”和审查清单很有实战价值,尤其是DROP DEFAULT那条,我上周才踩过类似的坑。这种把AI当翻译官而不是打字机的思路,值得所有后端开发反思。
文章中那个时间分布图太真实了,传统建模最耗时的确实是“把脑子的关系转成DDL”这一步。我之前手写Prisma Schema经常在索引策略上纠结,看了作者用分步细化策略让AI出第一版再review改索引的做法后,准备下周的项目直接套用。不过审查清单部分强调的不信任原则也很重要,毕竟生产数据千差万别,不敢一键执行。
文章点出了关键:不是告诉AI要什么SQL,而是描述业务场景。我之前就总觉得AI生成的脚本不靠谱,现在回头看是自己没把“意图”说清楚。最触动我的是关于维护迁移历史的误区,我之前确实图省事每次让AI重新生成,结果测试环境和预发布环境迁移进度不一致,排查半天。作者这套方法论很有体系感,适合落地到团队规范里。
作为用过Claude Code做建模的人,文章里的痛点我几乎全中。特别是那个把Prompt写得像需求文档的误区,当时我一次描述十几个表,生成出来索引全默认btree,后来拆成三个小步骤重新沟通,质量立马提升。作者提供的对照数据和审查清单很实在,不是那种虚头巴脑的理论,这种有数据有踩坑的经验分享比官方文档有用多了。