← BACK TO BLOG

AI 开发经验:用 Migration 管理数据库结构

在 AI 辅助开发中,把数据库表结构以 Migration 文件纳入代码仓库,既能减少人工维护出错,也能让 AI 更准确理解业务数据模型。

次阅读

背景

用 Cursor、Copilot 这类 AI 工具写代码时,有一个很实际的感受:AI 对业务上下文的理解,很大程度上取决于工作区里有什么

代码写得再漂亮,如果 AI 不知道 orders 表和 order_items 是怎么关联的、字段含义是什么、索引约束有哪些,它生成的 SQL、Entity、Service 就容易「看起来对、跑起来错」。

这篇文章分享一条我在 AI 辅助开发中逐渐固化下来的实践:用 Migration 方案管理数据库结构的迭代,以 Java 生态为例,但思路适用于任何技术栈。

手动维护表结构的问题

很多团队的数据库演进路径是这样的:

  1. 开发者在 Navicat / DBeaver 里直接改表
  2. 改完本地能跑,口头或文档里记一笔
  3. 测试、生产环境靠 DBA 或同事「照着改一遍」
  4. 偶尔导出一份 schema.sql 丢进文档目录,但很快又过期

这套流程在 AI 时代会放大几个问题:

  • 文档与真实结构脱节:AI 读到的是过期的表结构说明,生成的代码自然对不上
  • 环境不一致:本地有字段、测试没有,排查成本高
  • 变更不可追溯:三个月前为什么加这个索引、谁改的,往往查不清
  • 人工同步易出错:漏改一个字段、索引名写错、字符集不一致,都是常见事故

本质上,表结构也是代码的一部分,却长期被当作「数据库那边的事」单独维护,这和把业务逻辑写在 Word 里再手工抄进 IDE 没有区别。

Migration 方案是什么

Migration(数据库迁移)的核心思想很简单:

每一次表结构变更,都是一份可版本管理的、按顺序执行的脚本文件。

典型目录结构:

code
src/main/resources/db/migration/
├── V1__create_user_table.sql
├── V2__add_user_avatar.sql
├── V3__create_order_tables.sql
└── V4__add_order_status_index.sql

应用启动时(或独立执行迁移命令),Migration 工具会:

  1. 读取已执行过的版本记录
  2. 按文件名顺序执行尚未应用的脚本
  3. 把执行结果写入版本表,避免重复执行

这样,数据库结构的演进历史就和 Git 提交历史一样可追溯

Java 实践:以 Flyway 为例

Java 生态里 Flyway 和 Liquibase 都很成熟,Spring Boot 对 Flyway 的支持尤其顺滑。下面用 Flyway 举例。

1. 引入依赖

xml
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-core</artifactId>
</dependency>
<dependency>
  <groupId>org.flywaydb</groupId>
  <artifactId>flyway-mysql</artifactId>
</dependency>

2. 配置

yaml
spring:
  flyway:
    enabled: true
    locations: classpath:db/migration
    baseline-on-migrate: true

3. 编写迁移脚本

初始版本 V1__init_schema.sql

sql
CREATE TABLE users (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  username VARCHAR(64) NOT NULL UNIQUE,
  email VARCHAR(128) NOT NULL,
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE orders (
  id BIGINT AUTO_INCREMENT PRIMARY KEY,
  user_id BIGINT NOT NULL,
  total_amount DECIMAL(10, 2) NOT NULL,
  status TINYINT NOT NULL DEFAULT 0 COMMENT '0待支付 1已支付 2已取消',
  created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  CONSTRAINT fk_orders_user FOREIGN KEY (user_id) REFERENCES users(id)
);

CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status_created ON orders(status, created_at);

后续需求变更,只新增文件,不修改已执行过的脚本,例如 V2__add_user_phone.sql

sql
ALTER TABLE users
  ADD COLUMN phone VARCHAR(20) NULL AFTER email;

CREATE INDEX idx_users_phone ON users(phone);

Flyway 会在启动时自动执行 V2,并在 flyway_schema_history 表中记录版本。

4. 团队约定

几条简单规则,长期收益很大:

规则 说明
只增不改 已上线的 migration 文件禁止修改,新变更用新文件
一条变更一个文件 便于 Code Review 和回滚定位
命名带语义 V5__add_order_refund_fields.sqlV5__update.sql 清晰
与业务 PR 同提 改表结构和改代码在同一个 Pull Request 里

Liquibase 的思路类似,只是用 XML/YAML/SQL 描述变更集,适合需要跨多种数据库的场景。选型不是关键,「结构变更即代码」 才是。

为什么这对 AI 开发特别有用

把 Migration 文件放进工作区后,AI 能直接「看到」完整的数据模型,而不依赖你每次手动粘贴表结构。

1. 生成更准确的代码

当你让 AI 写「查询用户最近 30 天已支付订单」时,它能从 migration 里看到:

  • orders.status 的含义(注释或命名)
  • 已有索引 idx_orders_status_created,可以给出更合理的查询条件
  • user_id 外键关系,不会凭空造字段

2. 减少 Entity / Mapper 与表结构不一致

MyBatis、JPA 的实体类字段如果和真实表对不上,编译不一定报错,运行时才暴雷。Migration 文件相当于单一事实来源(Single Source of Truth),AI 生成或修改 Entity 时有据可依。

3. 新功能开发时上下文更完整

一个典型场景:你要加「订单退款」功能。工作区里有从 V1V8 的完整 migration 历史,AI 能理解:

  • 订单表当前有哪些字段
  • 之前为什么加了某个索引
  • 新表应该和哪些表建立外键

这比在对话里贴一张 Navicat 截图,或者口述「我们有个 orders 表」可靠得多。

4. Code Review 时 AI 也能帮上忙

Review 某个 PR 时,AI 可以同时看到 Java 代码变更和 V9__add_refund_table.sql,判断:

  • 新增字段是否在 Entity 中同步
  • 索引是否合理
  • 是否遗漏了 NOT NULL 约束

和「导出整库 DDL」的区别

有人可能会问:我把整个库的 DDL 导出成一个 schema.sql 放进仓库,不也能给 AI 看吗?

可以,但不如 Migration 好,原因如下:

  • DDL 快照是静态的,看不出变更意图和历史
  • 容易和真实环境不同步,谁忘了更新快照,AI 就被误导
  • 无法驱动环境自动升级,还得靠人工执行

Migration 兼顾了「当前完整结构」和「演进过程」:把所有 migration 文件按顺序读一遍,就是完整 schema;单独看某个文件,又能理解某次需求改了什么。

小结

AI 辅助开发不是魔法,它和你给它的上下文质量直接相关。数据库表结构是业务模型的重要载体,却常常是上下文里最缺失、最容易过期的一块。

用 Migration 管理数据库结构迭代,好处是双重的:

  1. 工程层面:版本可追溯、环境可复现、减少人工同步出错
  2. AI 协作层面:工作区里有完整、准确的表结构,AI 对业务的理解更深,生成的代码更靠谱

以 Java + Flyway 为例,落地成本很低——Spring Boot 几行配置即可。如果你已经在用 AI 写后端代码,把 migration 目录纳入日常开发流程,会是性价比很高的一步。