|
### Alembic 是什么
Alembic 是 SQLAlchemy/SQLModel 项目的**数据库迁移工具**,用于管理数据库表结构的版本变更。
- 类似于 Git 管理代码版本,Alembic 管理**数据库 schema 版本**
- 支持**自动检测**模型变更并生成迁移脚本
- 支持**升级(upgrade)**和**回滚(downgrade)**
- 由 SQLAlchemy 作者开发,与 SQLModel 完美兼容
```python
# SQLModel 模型变更后,Alembic 自动生成迁移
# 模型: class User(SQLModel, table=True): ...
# ↓ alembic revision --autogenerate
# 迁移脚本: versions/xxx_add_user_table.py
# ↓ alembic upgrade head
# 数据库: CREATE TABLE user (...);
```
|
|
### 目录结构
```
backend/
├── alembic.ini # Alembic 顶层配置
└── alembic/ # 迁移脚本目录
├── env.py # 环境配置(动态获取数据库URL)
├── script.py.mako # 迁移脚本模板
├── versions/ # 迁移版本文件(自动生成)
│ └── *.py # 各版本迁移脚本
└── README.md # 使用说明
```
- `alembic.ini`:顶层配置,`script_location` 指向 alembic 目录
- `env.py`:核心配置,从 `settings` 动态读取数据库 URL,导入所有模型
- `versions/`:存放自动生成的迁移脚本,每个文件对应一次 schema 变更
|
|
### 核心概念
| 概念 | 说明 |
|------|------|
| **Migration** | 一次数据库 schema 变更,包含 upgrade 和 downgrade |
| **Revision** | 迁移版本号(如 `5e7216dac3c2`),唯一标识一个迁移 |
| **Head** | 最新的迁移版本,`upgrade head` 即升级到最新 |
| **Base** | 初始基线,`downgrade base` 回滚到最初状态 |
| **Branch** | 迁移分支,多人协作时可能产生多个 head |
| **Autogenerate** | 自动对比模型与数据库差异,生成迁移脚本 |
```
base ──> rev1 ──> rev2 ──> rev3 (head)
↑ ↑
downgrade -1 upgrade head
```
|
|
### 查看当前状态
所有 alembic 命令必须在 `alembic.ini` 所在目录(即 `backend/`)下执行。
```bash
cd backend/
# 查看当前迁移版本(对比数据库和代码模型)
alembic current
# 查看迁移历史
alembic history
# 查看最新版本(head)
alembic heads
```
`alembic current` 输出示例:
```
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
5e7216dac3c2 (head) - init all tables -> 2026_07_31_0922
```
|
|
### 自动生成迁移脚本(autogenerate)
当 SQLModel 模型发生变更后,执行:
```bash
# 自动检测模型变更并生成迁移脚本
alembic revision --autogenerate -m "描述变更内容"
# 示例:新增了一张表
alembic revision --autogenerate -m "add t_new_table"
# 示例:给 user 表增加了 avatar 字段
alembic revision --autogenerate -m "add avatar column to user"
```
生成的迁移脚本位于 `alembic/versions/` 目录,包含:
```python
def upgrade() -> None:
# 升级操作:创建表/添加列/修改列...
op.create_table('t_new_table', ...)
op.add_column('user', sa.Column('avatar', sa.String(255)))
def downgrade() -> None:
# 回滚操作:与 upgrade 相反
op.drop_column('user', 'avatar')
op.drop_table('t_new_table')
```
> ⚠️ 生成后务必检查迁移脚本内容是否正确!
|
|
### 升级(Upgrade)
```bash
# 升级到最新版本
alembic upgrade head
# 升级指定步数
alembic upgrade +1
# 升级到指定版本
alembic upgrade 5e7216dac3c2
```
### 回滚(Downgrade)
```bash
# 回滚一个版本
alembic downgrade -1
# 回滚到指定版本
alembic downgrade 5e7216dac3c2
# 回滚到基线(清空所有迁移效果)
alembic downgrade base
```
| 命令 | 作用 | 场景 |
|------|------|------|
| `upgrade head` | 升级到最新 | 日常开发,应用新迁移 |
| `upgrade +1` | 前进一个版本 | 逐步验证迁移 |
| `downgrade -1` | 回退一个版本 | 发现问题,快速回滚 |
| `downgrade base` | 回到初始 | 清空重来 |
|
|
### 手动创建空迁移
当需要执行自定义 SQL(不依赖 autogenerate)时:
```bash
alembic revision -m "custom data fix"
```
生成一个空模板,手动填充 `upgrade()` 和 `downgrade()`:
```python
def upgrade() -> None:
op.execute("UPDATE user SET status = 'active' WHERE status IS NULL")
def downgrade() -> None:
op.execute("UPDATE user SET status = NULL WHERE status = 'active'")
```
### 离线模式(生成SQL但不执行)
```bash
# 生成升级 SQL(不连接数据库)
alembic upgrade head --sql
# 生成降级 SQL
alembic downgrade -1 --sql
```
适用于:需要 DBA 审核 SQL 后再手动执行的场景。
|
|
### 动态数据库 URL
`alembic.ini` 中的 URL 是占位符,实际 URL 由 `env.py` 动态获取:
```python
# env.py
from inspectai_shared.config import settings
from alembic import context
config = context.config
# 覆盖 alembic.ini 中的占位符 URL
config.set_main_option("sqlalchemy.url", str(settings.mysql_url))
```
这样只需修改 `.env` 文件中的数据库配置即可切换目标库,无需改动 alembic 配置。
```
.env 文件
↓ settings.mysql_url 读取
↓ env.py 动态设置
↓ alembic 迁移执行
数据库
```
|
|
### 模型导入(autogenerate 的前提)
autogenerate 通过 `target_metadata` 对比模型与数据库差异。所有 `table=True` 的模型**必须被 import**,否则检测不到新表。
```python
# env.py 中导入所有模型
from sqlmodel import SQLModel
# === models/ 目录 ===
from inspectai_api.models.user import User
from inspectai_api.models.t_table_meta import TTableMeta
from inspectai_api.models.t_column_meta import TColumnMeta
# ... 更多模型
# === chat2sql 模块模型 ===
from inspectai_api.modules.chat2sql.dbconfig.models import DBConfigModel
from inspectai_api.modules.chat2sql.indicator.models import (
IndicatorModel, IndicatorCategoryModel
)
# ... 更多模块模型
# 设置 target_metadata
target_metadata = SQLModel.metadata
```
> ⚠️ 新增模型后,必须在 `env.py` 中添加 import,否则 `--autogenerate` 无法检测新表!
|
|
### 连接池配置
迁移使用 `NullPool`(每次创建新连接),避免与应用连接池冲突:
```python
from sqlalchemy import engine_from_config, pool
def run_migrations_online() -> None:
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool, # 迁移用 NullPool
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
)
with context.begin_transaction():
context.run_migrations()
```
为什么用 NullPool:
- 迁移是低频操作,不需要连接池
- 避免长连接超时问题
- 确保每次迁移使用全新连接
|
|
### 新增模型时的完整流程
```
① 创建 SQLModel 模型
└─> src/inspectai_api/models/new_model.py
② 在 env.py 中添加 import
└─> from inspectai_api.models.new_model import NewModel
③ 自动生成迁移脚本
└─> alembic revision --autogenerate -m "add new_model table"
④ 检查生成的迁移脚本
└─> alembic/versions/xxx_add_new_model_table.py
└─> 确认 upgrade() 和 downgrade() 内容正确
⑤ 执行迁移
└─> alembic upgrade head
⑥ 提交到版本控制
└─> git add alembic/versions/xxx_add_new_model_table.py
└─> git commit -m "add migration: new_model table"
```
|
|
### 日常模型变更流程
```bash
# 1. 修改 SQLModel 模型(添加字段、修改类型等)
# vim src/inspectai_api/models/user.py
# 2. 生成迁移
alembic revision --autogenerate -m "add avatar to user"
# 3. 检查迁移脚本
# cat alembic/versions/xxx_add_avatar_to_user.py
# 4. 执行迁移
alembic upgrade head
# 5. 验证
alembic current
```
**autogenerate 能检测的变更**:
- 新增表 / 删除表
- 新增列 / 删除列
- 列类型变更
- 可空约束变更
- 默认值变更
**autogenerate 不能检测的变更**:
- 列名重命名(会变成 删除旧列+新增新列)
- 数据变更(需要手动写迁移)
|
|
### 多人协作与合并
当多人同时生成迁移,可能出现多个 head:
```bash
# 检查是否有多个 head
alembic heads
# 如果有多个 head,需要合并
alembic merge_heads -m "merge migrations from alice and bob"
# 然后正常升级
alembic upgrade head
```
```
rev1 ──> rev2a (head) ← Alice 的迁移
└──> rev2b (head) ← Bob 的迁移
# 合并后:
rev1 ──> rev2a ──> merge_rev (head)
└──> rev2b ──/
```
**最佳实践**:
- 迁移脚本尽早提交,减少冲突
- 合并前沟通,避免覆盖对方变更
- CI 中运行 `alembic heads` 检测分支
|
|
### 场景:将已有迁移脚本应用到新空库
当项目已有一套完整的 alembic 迁移脚本(`versions/` 目录),需要在一个全新的空数据库上初始化所有表时,只需执行 `alembic upgrade head` 即可。
### 步骤总览
```
① 创建空库(MySQL)
② 修改 .env 指向空库
③ alembic current(确认无迁移记录)
④ alembic upgrade head(执行所有迁移,创建全部表)
⑤ alembic current(确认版本)
```
### ① 创建空库
```bash
PS> mysql -uautomng -pAutomng_123 -h"144.34.185.72" -P13301
mysql> CREATE DATABASE n2sql2 DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
mysql> exit;
```
### ② 修改 .env 指向空库
```ini
MYSQL_HOST=144.34.185.72
MYSQL_PORT=13301
MYSQL_DATABASE=n2sql2
MYSQL_USER=automng
MYSQL_PASSWORD=Automng_123
```
### ③ 确认当前状态(空库无迁移记录)
```bash
PS> cd D:\zhinengjiancha.23\n2sql_sample\backend
PS> alembic current
```
输出——无版本号,说明空库尚未执行过任何迁移:
```
[InspectAI Config] ...
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
```
### ④ 执行 upgrade head
```bash
PS> alembic upgrade head
```
输出——从 base 升级到 head,一次性执行所有迁移脚本:
```
INFO [alembic.runtime.migration] Running upgrade -> 5e7216dac3c2, init all tables
```
Alembic 会:
- 自动创建 `alembic_version` 表(版本追踪)
- 执行 `versions/` 下所有迁移脚本的 `upgrade()` 函数
- 一次性创建全部 52 张业务表
### ⑤ 确认版本
```bash
PS> alembic current
```
```
5e7216dac3c2 (head)
```
出现版本号 + `(head)` 标记,说明初始化完成。
### 验证:确认表已创建
```bash
PS> python -c "import pymysql; ..."
```
输出:
```
Tables count: 53
alembic_version ← 版本追踪表(alembic 自动创建)
ai_query_table_record
analysis
analysis_category
cache_info
chart_config
conversation
conversation_group
dashboard
...(共 52 张业务表)
```
### 回滚验证
```bash
PS> alembic downgrade -1
```
```
INFO [alembic.runtime.migration] Running downgrade 5e7216dac3c2 -> , init all tables
```
回滚后只剩 1 张表(`alembic_version`),52 张业务表全部被删除。
重新升级恢复:
```bash
PS> alembic upgrade head
```
```
INFO [alembic.runtime.migration] Running upgrade -> 5e7216dac3c2, init all tables
```
### 关键点总结
| 要点 | 说明 |
|------|------|
| 不需要重新生成迁移 | `versions/` 中已有迁移脚本,直接 `upgrade head` |
| `alembic_version` 表 | alembic 自动创建,记录当前版本号 |
| 一条命令搞定 | `alembic upgrade head` 自动执行所有 pending 迁移 |
| 切换库只改 `.env` | `env.py` 动态读取数据库 URL,无需改 alembic 配置 |
| 可随时回滚 | `downgrade -1` 或 `downgrade base` 回退 |
|
|
### 场景:新空库误用 stamp head 导致无业务表
新建空数据库 `n2sql3` 并修改 `.env` 指向它后,误执行了 `alembic stamp head`。结果 `alembic current` 显示版本已到 head,但 `alembic upgrade head` 没有执行任何迁移,数据库中也没有业务表。
### 问题复现(真实操作记录)
```bash
# ① 新建空库 n2sql3,修改 .env
MYSQL_HOST=144.34.185.72
MYSQL_PORT=13301
MYSQL_DATABASE=n2sql3
MYSQL_USER=automng
MYSQL_PASSWORD=Automng_123
# ② 误操作:用 stamp 标记 head(只写版本标记,不建表)
PS> alembic stamp head
# ③ current 显示已到最新版本
PS> alembic current
INFO [alembic.runtime.migration] Context impl MySQLImpl.
5e7216dac3c2 (head)
# ④ upgrade head 没有任何反应(因为已标记为 head)
PS> alembic upgrade head
INFO [alembic.runtime.migration] Context impl MySQLImpl.
# 没有 Running upgrade ... 输出
# ⑤ 检查数据库,业务表为空
mysql> use n2sql3; show tables;
Empty set (0.17 sec)
```
> ⚠️ 注意拼写核对:`.env` 中写的是 `n2sql3`(无 v),MySQL 命令行中可能误写成 `n2sqlv3`(带 v)。两者是不同的数据库,检查前务必确认一致。
### 问题原因
`alembic stamp head` 只写版本标记,**不执行迁移脚本**,所以空库中仍然只有 1 张 `alembic_version` 表,没有 52 张业务表。
```
alembic stamp head = 在 alembic_version 表插入 5e7216dac3c2
不执行 upgrade() 中的 CREATE TABLE
```
### 降级失败
遇到此问题时,**不要用 `downgrade -1`**,因为 `downgrade()` 函数会执行 `DROP TABLE`,而空库中根本没有这些表,会报错:
```
PS> alembic downgrade -1
sqlalchemy.exc.OperationalError: (1051, "Unknown table 'n2sql3.ws_query_template'")
[SQL: DROP TABLE ws_query_template]
```
### 正确做法:stamp base + upgrade head
`stamp base` 清除版本标记,让 alembic 认为当前库未执行过任何迁移;然后 `upgrade head` 从起点执行所有迁移脚本,真正创建业务表。
```bash
PS> alembic stamp base
PS> alembic upgrade head
PS> alembic current
```
输出:
```
INFO [alembic.runtime.migration] Running stamp_revision 5e7216dac3c2 ->
INFO [alembic.runtime.migration] Running upgrade -> 5e7216dac3c2, init all tables
5e7216dac3c2 (head)
```
### 等效方法:手动清空版本表
如果不方便用 stamp,可以手动清空 `alembic_version` 表:
```bash
PS> python -c "import pymysql; conn=pymysql.connect(
host='144.34.185.72',port=13301,
user='automng',password='Automng_123',database='n2sql3');
cur=conn.cursor();
cur.execute('DELETE FROM alembic_version');
conn.commit(); conn.close()"
PS> alembic upgrade head
PS> alembic current
```
### 修复后验证
```bash
PS> python -c "import pymysql; ..."
Tables count: 53
alembic_version ← 版本追踪表
ai_query_table_record
analysis
analysis_category
...
```
53 张表(1 张 alembic_version + 52 张业务表),初始化完成。
### 命令对照表
| 命令 | 是否建表 | 作用 | 使用场景 |
|------|---------|------|---------|
| `upgrade head` | ✅ 是 | 执行所有 pending 迁移 | 空库初始化 |
| `stamp head` | ❌ 否 | 只标记当前库为 head | 表已存在,补版本标记 |
| `stamp base` | ❌ 否 | 清除版本标记 | 版本标记与实际不一致,需重跑迁移 |
| `downgrade -1` | ❌ 否 | 执行 DROP TABLE 回退 | 表已存在且想回退 |
### 关键要点
- 新空库初始化只需要 `alembic upgrade head` 一条命令
- 不要对新空库执行 `stamp head`,否则只标记版本不建表
- 误执行后,用 `stamp base` + `upgrade head` 修复
- 切换数据库后,同时检查 `alembic current` 和 `SHOW TABLES`,并确认 `.env` 库名与 MySQL 中 `USE` 的库名一致
|
|
### 场景:表已存在但 alembic 无版本标记
切换到新数据库后,`alembic current` 无版本号,但数据库中**业务表已经存在**(由应用启动时 `SQLModel.metadata.create_all()` 或其他方式创建)。
此时直接 `upgrade head` 会报 `Table already exists` 错误。
### 问题复现
```bash
# ① 新库 n2sql3,应用已启动过,表已由 create_all() 创建
# ② alembic current 显示无版本
PS> alembic current
INFO [alembic.runtime.migration] Context impl MySQLImpl.
← 无版本号
# ③ 但数据库中已有 53 张表(含 alembic_version)
PS> python -c "import pymysql; ..."
Tables count: 53
```
### upgrade head 失败
```bash
PS> alembic upgrade head
sqlalchemy.exc.OperationalError: (1050, "Table 'ai_query_table_record' already exists")
[SQL: CREATE TABLE ai_query_table_record (...)]
```
alembic 认为需要从头建表,但表已经存在,CREATE TABLE 冲突。
### stamp base 也失败
```bash
PS> alembic stamp base ← 先回退标记
PS> alembic upgrade head ← 再升级建表
```
同样报 `Table already exists`——因为 `upgrade head` 会重新执行 `CREATE TABLE`。
### 修复:stamp head(推荐)
表已存在,不需要重新创建,只需标记版本:
```bash
PS> alembic stamp head
PS> alembic current
```
输出:
```
INFO [alembic.runtime.migration] Running stamp_revision -> 5e7216dac3c2
5e7216dac3c2 (head)
```
`stamp head` 只写入版本标记,不执行任何 SQL,不会尝试 CREATE TABLE。
### 三种场景对照
| 场景 | current | 表数 | 正确操作 |
|------|---------|------|---------|
| 空库,无表无版本 | 空 | 0 | `upgrade head` |
| 有版本标记,无表 | `(head)` | 1 | `stamp base` + `upgrade head` |
| **无版本标记,表已存在** | **空** | **53** | **`stamp head`** |
### 判断方法
切换数据库后,按以下顺序检查:
```bash
# 1. 查看 alembic 版本
PS> alembic current
# 2. 查看实际表数
PS> python -c "import pymysql; ..."
```
根据结果选择操作:
```
current = 空,Tables = 0 → upgrade head(空库初始化)
current = head,Tables = 1 → stamp base + upgrade head(版本与表不同步)
current = 空,Tables = 53 → stamp head(表已存在,只需标记版本)
```
### 关键要点
- `stamp head` 和 `stamp base` 都只写标记,**不执行任何迁移 SQL**
- `upgrade head` 会实际执行迁移脚本的 `upgrade()` 函数(CREATE TABLE 等)
- 切换数据库后**先检查再操作**,避免建表冲突或降级失败
|
|
### 场景:开发多版本,上线空库初始化
开发环境已经生成了多个迁移版本(例如 `init all tables` → `add temp_demo_field`),上线时面对的是一个全新空库,该如何操作?
### 推荐方案:直接 upgrade head(保留历史版本)
**不需要清空或合并历史版本**,直接把 `alembic/versions/` 目录下的所有迁移脚本带到线上,执行 `upgrade head` 即可。alembic 会按照版本依赖链依次执行,从 base 一直到 head。
```bash
# 生产环境:新建空库 prod_db,修改 .env 指向它
MYSQL_HOST=prod.mysql.host
MYSQL_PORT=3306
MYSQL_DATABASE=prod_db
MYSQL_USER=prod_user
MYSQL_PASSWORD=prod_password
# 直接 upgrade head
PS> alembic current # 确认无版本
PS> alembic upgrade head # 自动依次执行所有迁移
PS> alembic current # 确认到 head
```
### 实际演示(n2sql3 测试库)
当前有 2 个迁移版本:
```bash
PS> alembic history
|
|
### 场景:删除全部旧版本,重新生成 init 迁移并初始化空库
开发过程中 versions 目录积累了多个试错版本,希望推倒重来:删除所有迁移文件、重建空库、重新生成一个干净的 `init all tables` 迁移并执行。本次在 `n2sqlv2` 库上完整实测。
### ① 环境重置
```bash
# 1. 手工删除 versions 目录下的所有迁移文件
PS> Remove-Item D:\zhinengjiancha.23\n2sql_sample\backend\alembic\versions\*.py
# 2. 重建数据库
mysql> DROP DATABASE IF EXISTS n2sqlv2;
mysql> CREATE DATABASE n2sqlv2 DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
```
修改 `.env` 指向 `n2sqlv2`:
```ini
MYSQL_HOST=144.34.185.72
MYSQL_PORT=13301
MYSQL_DATABASE=n2sqlv2
MYSQL_USER=automng
MYSQL_PASSWORD=Automng_123
```
> ⚠️ 数据库名建议手工复制粘贴,避免拼写错误(如 `n2sqlv2` 与 `n2sql2` 是两个不同的库)。
### ② 重新生成迁移文件
```bash
PS> alembic revision --autogenerate -m "20260731 init all tables"
```
生成文件:
```
alembic/versions/2026_07_31_1105-9f60b9511037_20260731_init_all_tables.py
```
该步骤会自动创建 `alembic_version` 表用于记录迁移版本,但表为空——此时仅生成了迁移脚本,**尚未执行**:
```bash
mysql> select * from alembic_version;
Empty set (0.17 sec)
PS> alembic current
INFO [alembic.runtime.migration] Context impl MySQLImpl.
← 无版本号
```
### ③ 执行迁移:报错 NullType
```bash
PS> alembic upgrade head
INFO [alembic.runtime.migration] Running upgrade -> 9f60b9511037, 20260731 init all tables
Traceback (most recent call last):
...
File ".../2026_07_31_1105-9f60b9511037_20260731_init_all_tables.py", line 124, in upgrade
sa.Column('llm_id', sa.NullType(), nullable=True, comment='模型配置ID'),
^^^^^^^^^^^
AttributeError: module 'sqlalchemy' has no attribute 'NullType'. Did you mean: 'Nullable'?
```
### 问题原因:autogenerate 生成非法类型
autogenerate 对无法与数据库现有类型比对的字段会生成 `sa.NullType()` 占位,但新版 SQLAlchemy 已移除 `NullType`,执行时直接报 `AttributeError`。本次生成的脚本中共 11 处,按字段语义替换:
| 字段 | 错误生成 | 修复为 |
|------|---------|--------|
| `llm_id` / `db_id` | `sa.NullType()` | `sa.BigInteger()` |
| `is_saved_as_indicator` / `is_saved_as_analysis` / `is_saved_as_derived_table` | `sa.NullType()` | `sa.Boolean()` |
| `is_default_db` / `is_default` | `sa.NullType()` | `sa.Boolean()` |
| `use_count` | `sa.NullType()` | `sa.Integer()` |
修复示例:
```python
# 修复前
sa.Column('llm_id', sa.NullType(), nullable=True, comment='模型配置ID')
sa.Column('is_default', sa.NullType(), nullable=True, comment='是否默认')
# 修复后
sa.Column('llm_id', sa.BigInteger(), nullable=True, comment='模型配置ID')
sa.Column('is_default', sa.Boolean(), nullable=True, comment='是否默认')
```
顺带修复 autogenerate 的另外两个常见问题:
- `sqlmodel.sql.sqltypes.AutoString(length=65535)` → `sa.Text()`(MySQL 行长度超限)
- 文件头部补充 `import sqlmodel`(脚本中引用了 sqlmodel 类型)
### ④ 再次执行:报错 Table already exists
修复类型后再次 `upgrade head`,报:
```
sqlalchemy.exc.OperationalError: (1050, "Table 'ai_query_table_record' already exists")
```
原因:虽然执行过 `DROP DATABASE` / `CREATE DATABASE`,但期间应用启动过,`SQLModel.metadata.create_all()` 又自动建了表。需要清空库中所有表:
```python
import pymysql
conn = pymysql.connect(host='144.34.185.72', port=13301,
user='automng', password='Automng_123',
database='n2sqlv2')
cur = conn.cursor()
cur.execute('SET FOREIGN_KEY_CHECKS=0')
cur.execute('SHOW TABLES')
for t in cur.fetchall():
cur.execute(f'DROP TABLE IF EXISTS `{t[0]}`')
cur.execute('SET FOREIGN_KEY_CHECKS=1')
conn.commit()
conn.close()
```
> 必须先 `SET FOREIGN_KEY_CHECKS=0` 关闭外键检查,否则有外键约束的表无法删除。
### ⑤ 执行成功并验证
```bash
PS> alembic upgrade head
INFO [alembic.runtime.migration] Running upgrade -> 9f60b9511037, 20260731 init all tables
PS> alembic current
9f60b9511037 (head)
PS> python -c "import pymysql; ..."
Tables count: 53
```
53 张表(1 张 `alembic_version` + 52 张业务表),重置初始化完成。
### 关键要点
- 重置流程可行,但重新 `autogenerate` 后**必须人工审查迁移脚本**,修复 `NullType` / `AutoString(65535)` 等问题再执行
- `autogenerate` 只生成脚本不执行;`upgrade head` 才真正建表
- 执行前确认库中**无任何残留业务表**(应用启动会自动 `create_all()`)
- 重置会丢失全部历史版本,仅适合未上线的开发库;线上库禁止此操作
|
|
|
|
|
|
|
|
### autogenerate 常见问题
**问题1:新增模型检测不到**
原因:`env.py` 中未导入该模型。
解决:在 `env.py` 中添加 import:
```python
# env.py
from myapp.models.new_table import NewModel # ← 添加这行
```
**问题2:autogenerate 生成了不需要的变更**
原因:数据库中存在非模型管理的表,或模型与数据库状态不同步。
解决:
1. 检查并清理数据库中的无关表
2. 或在 `env.py` 中配置 `include_object` 过滤
```python
def include_object(object, name, type_, reflected, compare_to):
if type_ == "table" and name.startswith("tmp_"):
return False # 忽略临时表
return True
context.configure(
...
include_object=include_object,
)
```
**问题3:首次初始化空数据库报错 "table already exists"**
原因:之前手动执行过 `create_all()` 或有残留表。
解决:先清空数据库再执行 `upgrade head`。
|
|
### MySQL 兼容性问题
**问题1:NullType 导致迁移失败**
autogenerate 可能生成 `sa.NullType()`,MySQL 无法处理。
解决:手动修改迁移脚本,替换为具体类型:
```python
# ❌ 错误
sa.Column('id', sa.NullType())
# ✅ 修复
sa.Column('id', sa.BigInteger()) # ID 列
sa.Column('is_active', sa.Boolean()) # 布尔列
sa.Column('count', sa.Integer()) # 整数列
```
**问题2:AutoString(length=65535) 超过 MySQL 行限制**
SQLModel 的 `AutoString(65535)` 会生成过长的 VARCHAR。
解决:替换为 `sa.Text()`:
```python
# ❌ 错误
sqlmodel.sql.sqltypes.AutoString(length=65535)
# ✅ 修复
sa.Text()
```
**问题3:alembic.ini 含中文注释导致 GBK 解码错误**
Windows 环境下 Python 默认用 GBK 编码读文件。
解决:`alembic.ini` 中不写中文注释,仅用英文。
|
|
### 注意要点
1. **数据库 URL 来源**:`alembic.ini` 中的 URL 是占位符,实际 URL 由 `env.py` 动态读取。切换目标库只需修改 `.env`。
2. **迁移不可逆**:复杂迁移(删除列、修改列类型)无法完美回滚。`downgrade()` 需手动编写恢复逻辑,或使用数据库备份恢复。
3. **生产环境备份**:执行迁移前务必备份数据库。
4. **首次使用**:在空数据库上首次执行 `alembic upgrade head` 会自动创建 `alembic_version` 版本追踪表。
5. **迁移脚本纳入版本控制**:`alembic/versions/` 下的所有 `.py` 文件必须提交到 Git,确保团队成员同步。
6. **不要手动修改数据库**:所有 schema 变更通过 Alembic 迁移,避免模型与数据库不一致。
|
Alembic 官方文档
SQLModel 官方文档