docs: 更新数据迁移指南
This commit is contained in:
258
api/docs/数据迁移指南.md
Normal file
258
api/docs/数据迁移指南.md
Normal file
@@ -0,0 +1,258 @@
|
||||
# 生产环境迁移操作指南
|
||||
|
||||
本文档描述新表结构(`chapter_sections`、`memoir_images`)的完整迁移步骤,适用于生产环境。
|
||||
|
||||
---
|
||||
|
||||
## 一、迁移概览
|
||||
|
||||
| 阶段 | 内容 | 前置条件 |
|
||||
|------|------|----------|
|
||||
| 1 | 创建 `chapter_sections` 表、`chapters.cover_image` 列 | 无 |
|
||||
| 2 | 将 `chapters.content` + `chapters.images` 迁移到 `chapter_sections`,并删除旧列 | 阶段 1 完成 |
|
||||
| 3 | 创建 `memoir_images` 表 | 阶段 2 完成(依赖 `chapter_sections`) |
|
||||
| 4 | 将 `chapters.cover_image`、`chapter_sections.image` 迁移到 `memoir_images` | 阶段 3 完成 |
|
||||
| 5 | (可选)添加 `chapter_sections.image_id` 外键,删除 `chapter_sections.image` | 阶段 4 完成 |
|
||||
|
||||
---
|
||||
|
||||
## 二、前置条件检查
|
||||
|
||||
### 2.1 环境要求
|
||||
|
||||
- Python 3.x,已安装项目依赖(`uv sync` 或 `pip install -r requirements.txt`)
|
||||
- 数据库:PostgreSQL
|
||||
- 环境变量:`.env` 中配置正确的 `DATABASE_URL`
|
||||
|
||||
### 2.2 数据库状态检查
|
||||
|
||||
执行前确认:
|
||||
|
||||
```sql
|
||||
-- 1. chapters 表存在且包含 content、images 列(迁移前应有)
|
||||
SELECT column_name FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'chapters'
|
||||
AND column_name IN ('content', 'images', 'cover_image');
|
||||
-- 预期:content、images 存在;cover_image 可能不存在
|
||||
|
||||
-- 2. chapter_sections 表在阶段 1 前应不存在
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'chapter_sections'
|
||||
);
|
||||
-- 预期:false(首次迁移)
|
||||
|
||||
-- 3. memoir_images 表在阶段 3 前应不存在
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'memoir_images'
|
||||
);
|
||||
-- 预期:false(首次迁移)
|
||||
```
|
||||
|
||||
### 2.3 备份(强烈建议)
|
||||
|
||||
```bash
|
||||
# 生产环境务必先备份
|
||||
pg_dump -U <user> -d <database> -F c -f backup_$(date +%Y%m%d_%H%M%S).dump
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、迁移步骤
|
||||
|
||||
### 阶段 1:执行 SQL 建表/加列(chapter_sections)
|
||||
|
||||
**前置 SQL 条件**:无。`chapters` 表需已存在。
|
||||
|
||||
**方式 A:使用 psql 手动执行**
|
||||
|
||||
```bash
|
||||
cd /path/to/life-echo
|
||||
psql -U <user> -d <database> -f api/migrations/add_chapter_sections.sql
|
||||
```
|
||||
|
||||
**方式 B:使用一键脚本(推荐)**
|
||||
|
||||
一键脚本 `run_chapter_sections_migration` 会自动执行该 SQL,然后执行数据迁移,无需单独执行本阶段。
|
||||
|
||||
若选择方式 A,则需在阶段 2 使用 `migrate_chapters_to_sections`;若选择方式 B,可跳过阶段 1 和 2 的拆分,直接执行 `run_chapter_sections_migration`。
|
||||
|
||||
---
|
||||
|
||||
### 阶段 2:数据迁移(chapters → chapter_sections)
|
||||
|
||||
**前置 SQL 条件**:已执行 `api/migrations/add_chapter_sections.sql`,即:
|
||||
|
||||
- `chapter_sections` 表已创建
|
||||
- `chapters.cover_image` 列已添加(若不存在会自动添加)
|
||||
|
||||
**执行命令**(在 `api` 目录下):
|
||||
|
||||
```bash
|
||||
cd /path/to/life-echo/api
|
||||
uv run python -m scripts.migrate_chapters_to_sections
|
||||
```
|
||||
|
||||
或使用一键脚本(包含阶段 1 + 2):
|
||||
|
||||
```bash
|
||||
cd /path/to/life-echo/api
|
||||
uv run python -m scripts.run_chapter_sections_migration
|
||||
```
|
||||
|
||||
**脚本行为**:
|
||||
|
||||
- 若 `chapters.content` 已不存在,则跳过迁移
|
||||
- 将 `chapters.content` + `chapters.images` 解析为 sections,写入 `chapter_sections`
|
||||
- 将每章首张图写入 `chapters.cover_image`
|
||||
- 最后删除 `chapters.content`、`chapters.images`
|
||||
|
||||
---
|
||||
|
||||
### 阶段 3:执行 SQL 建表(memoir_images)
|
||||
|
||||
**前置 SQL 条件**:`chapter_sections` 表已存在(`memoir_images` 的 `section_id` 外键依赖它)。
|
||||
|
||||
**方式 A:使用 psql 手动执行**
|
||||
|
||||
```bash
|
||||
cd /path/to/life-echo
|
||||
psql -U <user> -d <database> -f api/migrations/add_memoir_images_table.sql
|
||||
```
|
||||
|
||||
**方式 B:使用 memoir_images 迁移脚本**
|
||||
|
||||
`run_memoir_images_migration` 会先执行该 SQL,再执行数据迁移。若 SQL 文件不存在会给出警告。
|
||||
|
||||
---
|
||||
|
||||
### 阶段 4:数据迁移(cover_image / section.image → memoir_images)
|
||||
|
||||
**前置 SQL 条件**:已执行 `api/migrations/add_memoir_images_table.sql`,即 `memoir_images` 表已创建。
|
||||
|
||||
**执行命令**(在项目根目录或 `api` 目录下):
|
||||
|
||||
```bash
|
||||
cd /path/to/life-echo
|
||||
uv run python -m api.scripts.run_memoir_images_migration
|
||||
```
|
||||
|
||||
或:
|
||||
|
||||
```bash
|
||||
cd /path/to/life-echo/api
|
||||
uv run python -m scripts.run_memoir_images_migration
|
||||
```
|
||||
|
||||
**脚本行为**:
|
||||
|
||||
- 先执行 `add_memoir_images_table.sql`(若存在)
|
||||
- 迁移 `chapters.cover_image` → `memoir_images`(`section_id` 为空表示封面)
|
||||
- 迁移 `chapter_sections.image` → `memoir_images`(`section_id` 非空表示段落配图)
|
||||
- 已存在的记录会跳过,支持幂等执行
|
||||
|
||||
---
|
||||
|
||||
### 阶段 5:(可选)添加 image_id 外键并删除旧 JSON 列
|
||||
|
||||
**前置 SQL 条件**:`memoir_images` 表已有数据,且 `chapter_sections` 中对应 section 的配图已迁移完成。
|
||||
|
||||
**执行**:
|
||||
|
||||
```bash
|
||||
cd /path/to/life-echo
|
||||
psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
|
||||
```
|
||||
|
||||
**该 SQL 会**:
|
||||
|
||||
1. 为 `chapter_sections` 添加 `image_id` 列(外键指向 `memoir_images`)
|
||||
2. 回填:将 `memoir_images` 中 `section_id` 指向本行的记录的 `id` 写入 `image_id`
|
||||
3. 删除 `chapter_sections.image` 列
|
||||
|
||||
---
|
||||
|
||||
## 四、推荐生产执行顺序(精简版)
|
||||
|
||||
### 方案 A:分步执行(便于排查问题)
|
||||
|
||||
```bash
|
||||
# 1. 备份
|
||||
pg_dump -U <user> -d <database> -F c -f backup_$(date +%Y%m%d).dump
|
||||
|
||||
# 2. chapter_sections 迁移(SQL + 数据)
|
||||
cd /path/to/life-echo/api
|
||||
psql -U <user> -d <database> -f migrations/add_chapter_sections.sql
|
||||
uv run python -m scripts.migrate_chapters_to_sections
|
||||
|
||||
# 3. memoir_images 迁移(SQL + 数据)
|
||||
cd /path/to/life-echo
|
||||
psql -U <user> -d <database> -f api/migrations/add_memoir_images_table.sql
|
||||
uv run python -m api.scripts.run_memoir_images_migration
|
||||
|
||||
# 4. (可选)image_id 外键
|
||||
psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
|
||||
```
|
||||
|
||||
### 方案 B:使用一键脚本(更简洁)
|
||||
|
||||
```bash
|
||||
# 1. 备份
|
||||
pg_dump -U <user> -d <database> -F c -f backup_$(date +%Y%m%d).dump
|
||||
|
||||
# 2. chapter_sections 一键迁移(内含 SQL + 数据)
|
||||
cd /path/to/life-echo/api
|
||||
uv run python -m scripts.run_chapter_sections_migration
|
||||
|
||||
# 3. memoir_images 一键迁移(内含 SQL + 数据)
|
||||
cd /path/to/life-echo
|
||||
uv run python -m api.scripts.run_memoir_images_migration
|
||||
|
||||
# 4. (可选)image_id 外键
|
||||
psql -U <user> -d <database> -f api/migrations/add_section_image_id_fk.sql
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 五、前置 SQL 条件汇总
|
||||
|
||||
| 脚本 / SQL 文件 | 前置条件 |
|
||||
|-----------------|----------|
|
||||
| `add_chapter_sections.sql` | `chapters` 表已存在 |
|
||||
| `migrate_chapters_to_sections` / `run_chapter_sections_migration` | 已执行 `add_chapter_sections.sql` |
|
||||
| `add_memoir_images_table.sql` | `chapter_sections` 表已存在 |
|
||||
| `run_memoir_images_migration` | 已执行 `add_memoir_images_table.sql`(或脚本会自动执行) |
|
||||
| `add_section_image_id_fk.sql` | `memoir_images` 表已存在且数据已迁移完成 |
|
||||
|
||||
---
|
||||
|
||||
## 六、回滚说明
|
||||
|
||||
- 迁移会删除 `chapters.content`、`chapters.images`,以及(若执行阶段 5)`chapter_sections.image`
|
||||
- 回滚需从备份恢复,或根据业务自行编写反向迁移脚本
|
||||
- 建议在测试环境完整跑通后再在生产执行
|
||||
|
||||
---
|
||||
|
||||
## 七、验证
|
||||
|
||||
```sql
|
||||
-- 1. chapter_sections 有数据
|
||||
SELECT COUNT(*) FROM chapter_sections;
|
||||
|
||||
-- 2. chapters 无 content/images
|
||||
SELECT column_name FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'chapters'
|
||||
AND column_name IN ('content', 'images');
|
||||
-- 预期:无结果
|
||||
|
||||
-- 3. memoir_images 有数据
|
||||
SELECT COUNT(*) FROM memoir_images;
|
||||
|
||||
-- 4. 封面与段落配图数量合理
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE section_id IS NULL) AS cover_count,
|
||||
COUNT(*) FILTER (WHERE section_id IS NOT NULL) AS section_count
|
||||
FROM memoir_images;
|
||||
```
|
||||
Reference in New Issue
Block a user