概述
### 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 后再手动执行的场景。
env.py配置
### 动态数据库 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 -> 5e7216dac3c2, init all tables 5e7216dac3c2 -> fedf4062cce9 (head), add temp_demo_field to user ``` 清空 `n2sql3` 后执行 `upgrade head`: ```bash PS> alembic upgrade head ``` 输出: ``` INFO [alembic.runtime.migration] Running upgrade -> 5e7216dac3c2, init all tables INFO [alembic.runtime.migration] Running upgrade 5e7216dac3c2 -> fedf4062cce9, add temp_demo_field to user ``` 验证: ```bash PS> alembic current fedf4062cce9 (head) PS> python -c "import pymysql; ..." Tables count: 53 ``` ### 为什么不建议合并成一个 init 版本? 可以合并,但通常**不推荐**,因为会丢失中间状态: | 方案 | 命令 | 优点 | 缺点 | |------|------|------|------| | **保留历史版本** | `alembic upgrade head` | 可追溯、可回滚、团队协作清晰 | 版本文件多 | | **合并为单个 init** | 删除 versions 后重新 `autogenerate` | 文件少、上线简单 | 丢失历史、回滚粒度粗、开发/测试/生产差异大 | ### 什么情况下可以合并? 如果历史版本只是开发过程中的中间试错(例如大量 `add column` / `drop column`),且尚未上线,可以在上线前做一次 "squash": ```bash # 1. 备份原 versions 目录 mv alembic/versions alembic/versions_bak # 2. 基于当前模型生成一个全新的 init 迁移 alembic revision --autogenerate -m "init all tables for production" # 3. 检查新生成的 init 迁移内容 # 4. 删除 versions_bak(确认无误后) ``` > ⚠️ 注意:squash 后,开发/测试/已有数据库需要用 `alembic stamp head` 标记新版本,**不要执行 upgrade head**(会冲突)。 ### 上线前检查清单 - [ ] 生产数据库已创建,且为空库(无业务表) - [ ] `.env` 已指向生产数据库 - [ ] `alembic/versions/` 已提交到版本控制,并随代码一起发布 - [ ] 已执行 `alembic current` 确认无版本 - [ ] 已执行 `alembic upgrade head` - [ ] 已执行 `alembic current` 确认到 head - [ ] 已检查 `SHOW TABLES` 确认所有表已创建 - [ ] 生产数据库已备份(如果是非空库迁移,必须备份) ### 关键要点 - 空库上线最简单:**保留历史版本,直接 `upgrade head`** - alembic 会自动按版本链依次执行,无需手动干预 - 历史版本是宝贵的审计记录,不要轻易 squash - 如果一定要 squash,必须确保所有环境同步处理版本标记
### 场景:删除全部旧版本,重新生成 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 官方文档