数据库迁移:安全管理 Dev 与生产环境
Source: Dev.to
这是 2025 年独立构建 SaaS – 设计、实现与运营倒计时日历 第 8 天。
文章中描述的方法是我通过反复试验得出的。如果你知道更好的办法,请在评论中告诉我。
常见的迁移管理方式
管理数据库迁移有多种方法。
使用 ORM 的迁移功能
像 Drizzle ORM、Prisma 和 TypeORM 这样的 ORM 都内置了迁移功能。
# For Drizzle ORM
npx drizzle-kit generate # Generate migrations from schema
npx drizzle-kit migrate # Apply migrations
当你在 TypeScript 中编写表定义时,ORM 会检测变化并自动生成诸如 ALTER TABLE 的 SQL。
好处
- 自动根据代码变更生成迁移 SQL
- 在数据库表中管理应用历史
- 可通过单条命令执行
挑战
- 难以处理复杂的数据迁移(例如转换已有数据)
- 有时难以了解将要执行的内容
管理多个环境时的挑战
ORM 的迁移功能在单一环境下使用非常方便,但在开发环境和生产环境分离的情况下会出现挑战:
- 开发与生产的模式可能不同步
- 难以追踪 “已在开发环境执行但尚未在生产环境执行” 的状态
- 难以知道何时执行了哪些迁移
我在开发 Memoreru(我的独立项目)时也遇到了这些问题,经过反复试验形成了目前的运营规则。
独立项目的迁移管理
顺序文件管理
迁移文件由 Claude Code 生成,并使用顺序编号进行管理。
database/migrations/
├── sql/
│ ├── 001_create_users_table.sql
│ ├── 002_create_posts_table.sql
│ ├── 003_add_user_profile.sql
│ ├── 004_add_status_column.sql
│ └── …
├── scripts/
│ └── migrate.sh
├── status.json
└── README.md
顺序管理的好处
- 一眼即可看出执行顺序
- 文件名显示模式对应的时间点
- 易于辨别生产与开发之间的差异
为什么直接管理 SQL 文件
我不使用 drizzle-kit generate,而是直接创建 SQL 文件。模式定义仍然交由 Drizzle ORM 管理,以保持类型安全。
直接管理 SQL 文件的原因
- 更容易处理复杂变更(涉及数据迁移的情况)
- 完全了解将要执行的内容
- 故障排查更简洁
共享的开发/生产迁移脚本
为什么使用共享脚本
# Development environment
./database/migrations/scripts/migrate.sh dev 004_add_status_column.sql
# Production environment
./database/migrations/scripts/migrate.sh pro 004_add_status_column.sql
共享脚本的好处
- 演练效果 – 在开发环境运行相同流程,可在生产前发现问题
- 统一流程 – 避免因工作流不同导致的意外
- 集中日志 – 两个环境的执行日志采用相同格式
环境特有的差异
| 项目 | 开发环境 | 生产环境 |
|---|---|---|
| 连接信息 | 自动从 .env.local 加载 | 每次手动输入 |
| 备份建议 | 无 | 显示警告;需要手动备份 |
每次输入生产环境的连接字符串虽然繁琐,但它充当了一道 安全措施,防止误操作到错误的数据库。我也避免把生产库凭证交给 Claude Code,从而消除 AI 误操作的风险。迁移前,我会在 pgAdmin 中手动备份两种环境的数据库。
安全机制
脚本包含:
- 确认流程 – 在执行迁移前提示确认
- 连接测试 – 先验证数据库连通性
- 自动日志保存 – 将日志写入
logs/migrations/以供后续审查
具体实现由 Claude Code 生成;你只需描述需求,让它产出合适的脚本即可。
使用 status.json 的集中状态管理
{
"lastUpdated": "2025-12-04",
"environments": {
"dev": {
"name": "Development",
"lastApplied": "004_add_status_column",
"appliedAt": "2025-12-04"
},
"pro": {
"name": "Production",
"lastApplied": "003_add_user_profile",
"appliedAt": "2025-11-30"
}
},
"pending": {
"pro": ["004_add_status_column"]
}
}
检查生产环境的待执行迁移
# Display pending list
jq '.pending.pro' database/migrations/status.json
# => ["004_add_status_column"]
一眼即可看出哪些迁移已在开发环境执行但尚未在生产环境执行。
自动更新
迁移执行完毕后,脚本会自动更新 status.json,避免手动编辑导致的遗漏。
实用技巧
技巧 1:逐步进行破坏性变更
-- Step 1: Add new column
ALTER TABLE contents ADD COLUMN new_name TEXT;
-- Step 2: Migrate data
UPDATE contents SET new_name = old_name;
-- Step 3: Drop old column (in a separate migration)
ALTER TABLE contents DROP COLUMN old_name;
在第 2 步和第 3 步之间检查应用行为,可在出现问题时将影响降到最低。
技巧 2:准备回滚 SQL
-- Migration
ALTER TABLE contents ADD COLUMN status TEXT DEFAULT 'draft';
-- Rollback (execute only when needed)
-- ALTER TABLE contents DROP COLUMN status;
将回滚语句保留为注释,便于快速查阅。
技巧 3:与 Claude Code 的协作规则
在 CLAUDE.md 中记录迁移操作规则:
## Migration Operations
- Don't execute SQL directly with `psql`
- Always apply via `migrate.sh` script
- Rehearse in dev environment before production
- Commit `status.json` after applying
这些规则可防止 AI 代理误执行原始 SQL。
总结
运行良好的方面
- 使用顺序文件管理实现时间线追踪
- 通过共享的 dev/production 脚本进行演练
- 通过
status.json实现集中状态管理 - 确认流程防止误操作
需要注意的地方
- 随着变更量增大,手动管理 SQL 可能变得繁琐
- 先在具有代表性的测试数据上验证复杂数据迁移
- 提前规划回滚方案
即使是独立开发,早期就建立清晰的迁移规则也能帮助后期避免许多问题。