Files
shaguabijia-app-server/docs/database/数据库迁移.md
T
marco 43f28d358b docs: 补全 API 与数据库文档
- 新增 device-liveness(无障碍存活监控)、internal(内部回写端点)、cps-redirect(CPS 短链落地)三族单文件文档
- API 总览补录此前缺失端点:coupon prompt 频控、report/invite、wxpay 回调、platform 客户端配置、feedback/user/ad 零散读端点
- database 新增 cps_wx_user / device_liveness / launch_confirm_sample 三表文档,更新 OVERVIEW/README/comparison_record/数据库迁移/后端技术实现

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 04:07:54 +08:00

139 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 数据库迁移指南(Alembic
> 本项目用 **Alembic** 管理数据库表结构。所有"建表/改表"都写成 `alembic/versions/` 下的迁移脚本,
> 数据库的真实结构 = 把这些迁移按顺序跑一遍的结果。**不要手动改库结构**,一律走迁移。
>
> **相关文档**:
> - 这份文档讲 **alembic 操作**(如何建表/升级/新增迁移/多 head 排查)
> - [postgres-migration.md](./postgres-migration.md) 讲 **从 SQLite 切到 PostgreSQL** 的完整步骤(本机安装/建库/`scripts/init_postgres.py` 用法)
> - 配合 [后端技术实现.md §10](../后端技术实现.md) 看待办与已知问题
---
## 一句话
**clone 项目 / 拉到新迁移后,跑一条命令即可:**
```bash
alembic upgrade head
```
它会把数据库结构升到最新(幂等,已是最新则什么都不做)。
**新 PG 环境从零开始**:跑 `python scripts/init_postgres.py`,它会建用户 + 建库 + 写 .env + 跑迁移。详见 [postgres-migration.md](./postgres-migration.md)。
---
## 1. 首次初始化(clone 后)
### 1.1 用 PostgreSQL(推荐,与生产一致)
前置:本机已装 PostgreSQL 16 + 知道 postgres 超级用户密码。
```bash
# (1) 装依赖(在你的虚拟环境里, 含 psycopg)
pip install -e .
# (2) 一键建用户 + 建库 + 写 .env + 跑迁移
python scripts/init_postgres.py
# 按提示输入 PG 超级用户密码即可。脚本会:
# - 自动建业务用户 shaguabijia_app + 业务库 shaguabijia
# - 自动生成业务用户强密码,写入 .env 的 DATABASE_URL
# - 跑完 alembic upgrade head
# (3) 启动
./run.sh
```
可用环境变量提前指定(免交互):
```bash
PG_SUPER_PASS=xxx APP_DB_PASS=yyy python scripts/init_postgres.py
```
脚本幂等,重复跑会重置业务用户密码、跳过已存在的库。
### 1.2 用 SQLite(本地开发临时用,不接生产链路时)
```bash
pip install -e .
cp .env.example .env # 不动 DATABASE_URL, 默认 sqlite:///./data/app.db
mkdir -p data
alembic upgrade head
./run.sh
```
> 也可以直接 `bash scripts/migrate.sh` 只做迁移、不启服务(部署/CI 用)。
## 2. 数据库地址从哪来
-`settings.DATABASE_URL` 决定(`app/core/config.py`),可在 `.env``DATABASE_URL=` 覆盖。
- 默认 **SQLite**`sqlite:///./data/app.db`(所以要先 `mkdir -p data`)。
-**PostgreSQL**`.env` 里设 `DATABASE_URL=postgresql+psycopg://user:pwd@host:5432/dbname`,再 `alembic upgrade head` 即可(迁移脚本用了 `render_as_batch` 兼容 SQLitePostgres 也能跑)。
- `alembic.ini``sqlalchemy.url` 故意留空,由 `alembic/env.py``settings` 注入,**别在 alembic.ini 里填库地址**。
## 3. 常用命令
| 命令 | 作用 |
|---|---|
| `alembic upgrade head` | 升到最新(最常用) |
| `alembic current` | 看当前库在哪个 revision |
| `alembic history` | 看迁移链(谁接谁) |
| `alembic heads` | 看有几个 head(正常只 1 个;出现多个 = 分叉了要 merge) |
| `alembic downgrade -1` | 回滚一步(**生产慎用**,会丢数据) |
| `alembic downgrade <revision>` | 回滚到指定版本 |
## 4. 改了 ORM model、要新增一次结构变更
```bash
# 1) 自动对比 models 与库,生成迁移草稿
alembic revision --autogenerate -m "add xxx index"
# 2) **务必打开生成的文件 review**(autogenerate 不完美,索引/默认值/数据迁移常需手改)
# 3) 应用
alembic upgrade head
```
---
## 5. 关于迁移文件名(重要)
- 文件名是 `<描述>.py`(如 `welfare_tables_coin_account_coin_txn.py`)。**早期带的 hex 前缀已去掉,只是文件名变干净**。
- **真正的版本 id 是文件内的 `revision` 变量**(如 `revision = '357520ea3015'`),迁移链靠文件内的 `down_revision` 串起来,**与文件名无关**。数据库 `alembic_version` 表里存的也是这个内部 id。
- 因此:**可以随意重命名迁移文件**(Alembic 靠扫描文件、读 `revision` 识别),但 **绝不要改文件内的 `revision` / `down_revision`**——改了会和已迁移过的库(含生产)对不上,报"找不到 revision"。
- ⚠️ `alembic revision --autogenerate` **默认仍生成带 hex 前缀的文件名**。想保持本目录"无前缀"风格,二选一:
- 生成后**手动把文件名前缀去掉**(内部 id 别动);或
-`alembic.ini``file_template = %%(slug)s`(注意 ini 里 `%` 要写成 `%%`)让以后直接生成无前缀文件名(缺点:同名 slug 会冲突,描述写具体点)。
## 6. 当前迁移链(规模与当前 head)
- `alembic/versions/` 当前约 **78 个迁移文件**(含 14 个合并迁移),从 `init_user_table`(`down_revision=None`)起一路接到当前单一 head。**别背具体链**——它随每次改表增长,看实时输出:
```bash
ls alembic/versions/*.py | wc -l # 迁移文件数
alembic history # 完整链(按 down_revision,不是文件名字母序)
alembic heads # 当前 head(正常只 1 个)
```
- **当前 head**(2026-06-23):`4dc2af7ebe74`(文件 `4dc2af7ebe74_merge_device_65_and_comparison_token_.py`)——它合并了今日两条并行链:`comparison_token_cols`(#a584e1f,`comparison_record` 加 `input_tokens`/`output_tokens`)与 #65 device 链末端(`device_liveness` 表 + `ad_feed_reward_trace_id`)。即 `down_revision = ('comparison_token_cols', 'ad_feed_reward_trace_id')`。
- 频繁多 head 是常态(多人/多分支并行改表),靠合并迁移收敛回单 head,详见下面「踩坑 → 多 head 分叉」。
## 7. 踩坑
### SQLite 找不到目录
报 `unable to open database file` → 先 `mkdir -p data`。
### 多 head 分叉(2026-05-29 撞过)
`alembic upgrade head` 报:
```
Multiple head revisions are present for given argument 'head';
please specify a specific target revision, '<branchname>@head'...
```
**原因**:跨分支并行开发,两条分支都加了新迁移且 `down_revision` 都指向同一个 commit,合并到 main 后形成两条平行链。本次实例:
- PR #4 (feedback) 加了 `d1e2f3a4b5c6`,`down_revision='c8d9e0f1a2b3'` (ad_reward)
- PR #6 (PG/JSONB) 加了 `ef96beb47b1e`,`down_revision='c8d9e0f1a2b3'`(也是 ad_reward) ← 跟 #4 同源,撞车
- 两个 PR 都合进 main 后,`alembic heads` 出现两个
> 此后这种合并是常态,每次都用 `alembic merge` 收敛。最近一次(2026-06-23):#65 device 链(`device_table` → `device_kill_alert_pending`,经 `ad_feed_reward_trace_id`)与 `comparison_token_cols`(`comparison_record` 加 token 列)并行,用 `4dc2af7ebe74_merge_device_65_and_comparison_token_.py` 合成单一 head。
**修复**:
```bash
alembic heads # 列出冲突的两个 head id
alembic merge -m "merge <description>" <head1> <head2>
# 生成一个空迁移文件,把两个 head 合成新的单一 head
git add alembic/versions/<new_merge_file>.py
# commit + push, PR review 后合并到 main
```
**预防(团队规约)**:
- 写迁移的 PR 在提交前跑 `alembic heads`,确认本分支只有一个 head
- review 别人含迁移的 PR 时,看 `down_revision` 是否还是当前 main 的最新——如果上游已经被别人合了新迁移,需要 rebase 修改 `down_revision` 指过去
- main 拉新代码后再跑一次 `alembic heads` 兜底
- **生产库**:不要随意 `downgrade`;新迁移先在本地/测试库验证过再上。生产 `DATABASE_URL` 由部署侧配置,不在本仓库 `.env`。