Become a sponsor

使用 Alembic 管理数据库结构迁移,同时提供跨库数据迁移脚本,支持从 MySQL 迁移到 PostgreSQL / SQL Server / SQLite / Oracle 等多种数据库。
迁移工具链
scripts/migrate_db.py(跨库数据泵)scripts/init_db.py(全新安装一键建库)首次部署时,使用初始化脚本一键完成建库、建表、Alembic stamp:
# 方式一:直接运行脚本
python scripts/init_db.py
# 方式二:Make 命令
make db-init初始化流程
Base.metadata.create_all() 建表修改模型后,执行以下命令自动生成迁移脚本:
# 生成增量迁移脚本
alembic revision --autogenerate -m "描述变更内容"
# 或使用 Make 命令
make migrate-revision注意事项
.env 中的数据库配置正确)migrations/versions/ 目录# 应用所有待执行的迁移
alembic upgrade head
# 或使用 Make 命令
make migrate-upgrade# 回滚最近一次迁移
alembic downgrade -1
# 回滚到指定版本
alembic downgrade <revision_id># 查看当前版本
alembic current
# 查看迁移历史
alembic history
# 查看待执行的迁移
alembic history --indicate-current对于已有数据库,手动标记为最新版本:
alembic stamp head
# 或使用 Make 命令
make migrate-stamp将源库(通常 MySQL)的结构和数据整体迁移到目标库(PostgreSQL / SQL Server / SQLite / Oracle)。
scripts/migrate_db.py 是项目内置的跨库迁移脚本,核心特性:
Base.metadata.create_all),方言可移植,模型即 headSELECT ... ORDER BY id,显式带 id 批量插入,保住无外键裸列引用(user.dept_id、role_menu.role_id/menu_id、menu.parent_id 等)[·] 请勿中断 → [✓] 完成),不会显得卡死.env,也无需先切 DB_DRIVERrequirements.txt:| 目标库 | 驱动包 | 说明 |
|---|---|---|
| PostgreSQL | psycopg[binary] | psycopg3 |
| SQL Server | pymssql | - |
| SQLite | 内置 | 无需额外驱动 |
| Oracle | oracledb | thin 模式,无需安装 Oracle Client |
pip install -r requirements.txt| 目标库 | --dst-driver | 默认端口 | --dst-db 填什么 |
|---|---|---|---|
| MySQL | mysql | 3306 | 数据库名 |
| PostgreSQL | postgresql | 5432 | 数据库名(含点号会自动双引号) |
| SQL Server | mssql | 1433 | 数据库名 |
| SQLite | sqlite | 无端口 | SQLite 文件路径(相对/绝对皆可) |
| Oracle | oracle | 1521 | SERVICE_NAME(如 ORCLPDB1、XE,非库名) |
驱动别名:postgres / pg → postgresql;sqlserver / sql_server → mssql;sqlite3 → sqlite
迁移分 5 个阶段自动执行:
1. 建目标库(create_database_if_missing)
├─ MySQL: CREATE DATABASE IF NOT EXISTS
├─ PG: 连接 postgres 维护库 → CREATE DATABASE
├─ MSSQL: 连接 master 库 → CREATE DATABASE
├─ Oracle: 不建库,只清理旧表(schema 即连接用户)
└─ SQLite: 创建文件目录 + 可选删旧文件
2. 建表结构(Base.metadata.create_all)
└─ 方言适配,自动生成对应数据库的 DDL
3. 逐表泵数据(拓扑序,分块 executemany)
└─ 显式带 id 插入,保住引用完整性
4. 重置自增序列(按方言处理)
├─ PostgreSQL: setval(seq, MAX(id))
├─ SQL Server: IDENTITY_INSERT ON + DBCC CHECKIDENT RESEED
├─ Oracle: 补 sequence + BEFORE INSERT 触发器
└─ MySQL/SQLite: 自动跟随,无需处理
5. 逐表 COUNT(*) 校验
└─ source vs target 行数一致 → PASS| 参数 | 说明 |
|---|---|
--src-driver / --src-host / --src-port / --src-db / --src-user / --src-pass | 源库连接参数 |
--dst-driver / --dst-host / --dst-port / --dst-db / --dst-user / --dst-pass | 目标库连接参数 |
--dry-run | 干跑模式,只连源库逐表数行数,不碰目标库 |
--drop-target-first | 迁移前删除目标库所有表(幂等重跑) |
--only-tables a,b | 仅迁移指定表(续传用) |
--skip-existing | 目标表行数已等于源表则跳过 |
--skip-verify | 跳过迁移后的逐表行数校验 |
--chunk-size N | 每批插入行数,默认 1000 |
--single-transaction | 全部表包进一个大事务(默认每表一个事务) |
口令脱敏:--src-pass / --dst-pass 留空时回落环境变量 SRC_DB_PASS / DST_DB_PASS,避免口令进 shell 历史。
迁移前建议先干跑核对计划:
# 干跑:核对源库表和行数
python scripts/migrate_db.py --dry-run \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-user=root --src-pass=root --src-db=djangoadmin.fastapi.elevue
# 或使用 Make 命令
make db-migrate-dry-run ARGS="\
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-user=root --src-pass=root --src-db=djangoadmin.fastapi.elevue"python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.fastapi.elevue --src-user=root --src-pass=root \
--dst-driver=postgresql --dst-host=localhost --dst-port=5432 \
--dst-db=djangoadmin.fastapi.elevue --dst-user=postgres --dst-pass=你的密码 \
--drop-target-first输出示例:
==============================================================
[迁移开始] 源: mysql:djangoadmin.fastapi.elevue → 目标: postgresql:djangoadmin.fastapi.elevue
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 postgres 维护库...
· 正在创建目标库 "djangoadmin.fastapi.elevue"(UTF8)...
[✓] [1/5] 建目标库 完成(耗时 947ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 1.1s),23 张表
[·] [3/5] 逐表迁移数据...
· [1/23] 正在迁移 fastapi_dept...
[✓ fastapi_dept] 共 23 行(耗时 12ms)
· [3/23] 正在迁移 fastapi_city...
[fastapi_city] +1000(累计 1000)
[fastapi_city] +1000(累计 2000)
[fastapi_city] +1000(累计 3000)
[fastapi_city] +1000(累计 4000)
[fastapi_city] +150(累计 4150)
[✓ fastapi_city] 共 4150 行(耗时 850ms)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 3.5s),共 4843 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 412ms) ← PG setval 到 MAX(id)
[·] [5/5] 逐表行数校验...
[fastapi_dept] source=23 target=23 PASS
[fastapi_city] source=4150 target=4150 PASS
...
[✓] [5/5] 逐表行数校验 完成(耗时 103ms),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 6.0s
==============================================================python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.fastapi.elevue --src-user=root --src-pass=root \
--dst-driver=mssql --dst-host=localhost --dst-port=1433 \
--dst-db=djangoadmin.fastapi.elevue --dst-user=sa --dst-pass=你的密码 \
--drop-target-firstSQL Server 特殊处理
迁移时自动 SET IDENTITY_INSERT ON 保留原 id,迁移后 DBCC CHECKIDENT RESEED 重置自增。
输出示例:
==============================================================
[迁移开始] 源: mysql → 目标: mssql:djangoadmin.fastapi.elevue
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 SQL Server(master 维护库)...
· 正在删除旧目标库 [djangoadmin.fastapi.elevue](SINGLE_USER + DROP)...
· 正在创建目标库 [djangoadmin.fastapi.elevue]...
[✓] [1/5] 建目标库 完成(耗时 304ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 743ms),23 张表
[·] [3/5] 逐表迁移数据...
...
[✓ fastapi_city] 共 4150 行(耗时 3.9s)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 5.3s),共 4843 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 90ms)
[·] [5/5] 逐表行数校验...
[✓] [5/5] 逐表行数校验 完成(耗时 100ms),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 6.6s
==============================================================python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.fastapi.elevue --src-user=root --src-pass=root \
--dst-driver=sqlite --dst-db=D:/data/djangoadmin.fastapi.elevue.db \
--drop-target-firstSQLite 说明
--dst-db 即数据库文件完整路径(相对/绝对皆可)--drop-target-first 时删除旧文件后重建输出示例:
==============================================================
[迁移开始] 源: mysql → 目标: sqlite:D:/data/djangoadmin.fastapi.elevue.db
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 检查/准备 sqlite 文件...
· 删除旧 sqlite 文件...
[✓] [1/5] 建目标库 完成(耗时 8ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 96ms),23 张表
[·] [3/5] 逐表迁移数据...
[✓ fastapi_city] 共 4150 行(耗时 850ms)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 1.2s),共 4843 行
[·] [4/5] 重置自增序列...
· MySQL/SQLite 自动跟随,无需处理
[✓] [4/5] 重置自增序列 完成(耗时 0ms)
[·] [5/5] 逐表行数校验...
[✓] [5/5] 逐表行数校验 完成(耗时 100ms),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 1.4s
==============================================================python scripts/migrate_db.py \
--src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
--src-db=djangoadmin.fastapi.elevue --src-user=root --src-pass=root \
--dst-driver=oracle --dst-host=localhost --dst-port=1521 \
--dst-db=ORCLPDB1 --dst-user=fastapi --dst-pass=你的密码 \
--drop-target-firstOracle 特殊说明
--dst-db 填 SERVICE_NAME(如 ORCLPDB1、XE)--dst-user 连接的用户(无需也无法"建库",连上即视为就绪)CONNECT、RESOURCE 权限create_all 对 Integer 主键只生成 INTEGER NOT NULL(无 identity),迁移后脚本自动补 sequence(START WITH MAX(id)+1)+ BEFORE INSERT 触发器--drop-target-first 时尽力 DROP TABLE ... PURGE 清理旧表输出示例:
==============================================================
[迁移开始] 源: mysql → 目标: oracle:ORCLPDB1
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
· 正在连接 Oracle 服务 ORCLPDB1(schema=fastapi)...
· drop-first:清理 23 张旧项目表(DROP TABLE ... PURGE)...
[✓] [1/5] 建目标库 完成(耗时 4.6s)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 2.4s),23 张表
[·] [3/5] 逐表迁移数据...
[✓ fastapi_city] 共 4150 行(耗时 2m12s)
...
[✓] [3/5] 逐表迁移数据 完成(耗时 2m41s),共 4843 行
[·] [4/5] 重置自增序列... ← 补 sequence + BEFORE INSERT 触发器
[✓] [4/5] 重置自增序列 完成(耗时 6.2s)
[·] [5/5] 逐表行数校验...
[✓] [5/5] 逐表行数校验 完成(耗时 2.6s),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 2m54s
==============================================================迁移失败或需重跑时,加 --drop-target-first 可安全重跑:
python scripts/migrate_db.py ... --drop-target-first警告
--drop-target-first 会删除目标库所有表(Oracle 清旧表、SQLite 删旧文件),仅在确认目标库无生产数据时使用。
迁移过程被中断时,已迁移的表按每表一个事务提交,未迁完的表可配合 --only-tables 和 --skip-existing 断点续传:
# 只迁移未完成的表
python scripts/migrate_db.py ... --only-tables user,role,article --skip-existing数据泵按拓扑序迁移,确保引用完整性:
基础表(先迁移):dept, menu, role, level, position, city, category, dict, config, ...
依赖表(后迁移):role_menu, dict_item, config_item, user, user_role, article, notice, ...无外键设计
数据库不使用物理外键,依赖关系通过业务代码维护。迁移时显式带 id 插入,保住引用完整性。
| 驱动 | 处理方式 | 说明 |
|---|---|---|
| PostgreSQL | setval(pg_get_serial_sequence(...), MAX(id)) | SERIAL 序列重置到最大值 |
| SQL Server | IDENTITY_INSERT ON + DBCC CHECKIDENT RESEED | 显式插 id + 重置种子 |
| Oracle | 补 sequence + BEFORE INSERT 触发器 | create_all 无原生自增,脚本自动补建 |
| MySQL / SQLite | 自动跟随 | 显式插 id 后 auto_increment / rowid 自动跟随 |
如需手动修复自增:
-- PostgreSQL
SELECT setval(pg_get_serial_sequence('fastapi_user', 'id'), (SELECT max(id) FROM fastapi_user));
-- SQL Server
DBCC CHECKIDENT ('fastapi_user', RESEED, <max_id>)
-- Oracle
DROP SEQUENCE fastapi_user_id_seq;
CREATE SEQUENCE fastapi_user_id_seq START WITH <max_id+1>;| 问题 | 原因 | 解决方案 |
|---|---|---|
| 目标库数据叠加/主键冲突 | 未加 --drop-target-first,已有数据与迁移数据冲突 | 加 --drop-target-first 重跑 |
| 迁移后报 "no such table" | --dst-db 指向错误 | Oracle 填 SERVICE_NAME,SQLite 填文件路径 |
| 中文乱码 | 目标库编码非 UTF8 | 删除重建(--drop-target-first),PG 建库强制 UTF8 |
| Oracle 新增记录 id 异常 | sequence 未正确创建 | 脚本自动补建;若手动建过同名 sequence 会先删后建 |
| 自增 id 未延续原值 | 序列未重置 | 参考上表手动修复 |
| 迁移中断 | 网络或超时 | 配合 --only-tables + --skip-existing 断点续传 |
| 场景 | 命令 |
|---|---|
| 全新安装 | make db-init |
| 模型变更后生成迁移 | make migrate-revision |
| 应用迁移 | make migrate-upgrade |
| 回滚一次迁移 | alembic downgrade -1 |
| 标记已有库为最新 | make migrate-stamp |
| 跨库迁移干跑 | make db-migrate-dry-run ARGS="..." |
| 跨库迁移执行 | make db-migrate ARGS="..." |
数据库迁移分为结构迁移(Alembic)和数据迁移(migrate_db.py)两部分。全新安装用 make db-init,模型变更用 Alembic 自动生成迁移脚本,跨库切换用 migrate_db.py 数据泵。建议每次迁移前先干跑确认,生产环境务必做好备份。