================================================================================ 数据库迁移指南(scripts/migrate_db.py) MySQL → PostgreSQL / SQL Server / SQLite / Oracle ================================================================================ 一、脚本说明 ================================================================================ migrate_db.py 是项目内置的跨库迁移脚本,把「源库」整体搬到「目标库」: - 目标库结构用模型 metadata 生成(Base.metadata.create_all,方言可移植,模型即 head, 含 0002 的 city.parent_code nullable 等已落地模型定义) - 数据按拓扑序逐表 SELECT ... ORDER BY id,显式带 id 批量插入,保住无外键裸列引用 (user.dept_id、role_menu.role_id/menu_id、menu.parent_id 等) - 迁移后按方言重置自增: PostgreSQL SERIAL → setval(seq, MAX(id))(空表 START 1) SQL Server IDENTITY → IDENTITY_INSERT 显式插 id + DBCC CHECKIDENT RESEED Oracle 补 sequence(START WITH MAX(id)+1)+ BEFORE INSERT 触发器 (create_all 只生成 INTEGER NOT NULL,无原生自增) MySQL/SQLite 显式插 id 后 auto_increment/rowid 自动跟随,无需处理 - 全部耗时步骤都带「缓冲提示」([·] ...请勿中断 → [✓] ...完成(耗时)),不会显得卡死 特点:源/目标连接全部由 CLI 显式参数给出,不依赖 .env,也无需先切 DB_DRIVER。 二、前置准备 ================================================================================ 1. 源库(MySQL)可正常访问。 2. 目标库所需驱动已包含在 requirements.txt,执行一次即可: pip install -r requirements.txt 各目标库对应驱动: PostgreSQL psycopg[binary](psycopg3) SQL Server pymssql SQLite 内置(无需额外驱动) Oracle oracledb(thin 模式,无需安装 Oracle Client) 3. 可用 --dry-run 先干跑核对计划(只连源、逐表数行数、打印计划,不碰目标库)。 三、驱动与默认端口(含别名) ================================================================================ 目标库 --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 四、迁移命令与输出案例 ================================================================================ 以下源库均假设: MySQL 127.0.0.1:3306,库名 djangoadmin.fastapi.elevue,root / root 本项目共 23 张表(前缀 fastapi_),全量迁移约 4843 行(其中 fastapi_city 4150 行)。 4.1 MySQL → PostgreSQL -------------------------------------------------------------------------------- 命令: 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"(WITH FORCE)... · 正在创建目标库 "djangoadmin.fastapi.elevue"(UTF8)... [✓] [1/5] 建目标库 完成(耗时 947ms) [·] [2/5] 建表结构(23 张表 create_all)...(可能耗时较长,请勿中断) [✓] [2/5] 建表结构(23 张表 create_all) 完成(耗时 1.1s),23 张表 [·] [3/5] 逐表迁移数据...(可能耗时较长,请勿中断) · [1/23] 正在迁移 fastapi_dept... [✓ fastapi_dept] 共 23 行(耗时 12ms) · [2/23] 正在迁移 fastapi_menu... [✓ fastapi_menu] 共 54 行(耗时 110ms) · [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) · [4/23] 正在迁移 fastapi_user... ... · [23/23] 正在迁移 fastapi_login_log... [✓ fastapi_login_log] 共 5 行(耗时 13ms) [✓] [3/5] 逐表迁移数据 完成(耗时 3.5s),共 4843 行 [·] [4/5] 重置自增序列...(可能耗时较长,请勿中断) [✓] [4/5] 重置自增序列 完成(耗时 412ms) ← SERIAL 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 ============================================================== 4.2 MySQL → SQL Server(mssql) -------------------------------------------------------------------------------- 命令: 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-first 说明:mssql 迁移时自动 SET IDENTITY_INSERT ON 保留原 id,迁移后 DBCC CHECKIDENT RESEED。 输出案例(真实运行): ============================================================== [迁移开始] 源: mysql:djangoadmin.fastapi.elevue → 目标: 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] 建表结构(23 张表 create_all) 完成(耗时 743ms),23 张表 [·] [3/5] 逐表迁移数据...(可能耗时较长,请勿中断) · [1/23] 正在迁移 fastapi_dept... [fastapi_dept] +23(累计 23) [✓ fastapi_dept] 共 23 行(耗时 25ms) · [2/23] 正在迁移 fastapi_menu... [fastapi_menu] +54(累计 54) [✓ fastapi_menu] 共 54 行(耗时 102ms) · [3/23] 正在迁移 fastapi_role... ... · [6/23] 正在迁移 fastapi_city... [fastapi_city] +1000(累计 1000) ... [fastapi_city] +150(累计 4150) [✓ fastapi_city] 共 4150 行(耗时 3.9s) · [7/23] 正在迁移 fastapi_category... ... · [23/23] 正在迁移 fastapi_login_log... [✓ fastapi_login_log] 共 5 行(耗时 13ms) [✓] [3/5] 逐表迁移数据 完成(耗时 5.3s),共 4843 行 [·] [4/5] 重置自增序列...(可能耗时较长,请勿中断) [✓] [4/5] 重置自增序列 完成(耗时 90ms) [·] [5/5] 逐表行数校验...(可能耗时较长,请勿中断) [fastapi_dept] source=23 target=23 PASS ... [fastapi_city] source=4150 target=4150 PASS ... [✓] [5/5] 逐表行数校验 完成(耗时 100ms),23 张表全部 PASS ============================================================== [迁移完成] 成功:4843 行 / 23 张表 / 总耗时 6.6s ============================================================== 4.3 MySQL → SQLite -------------------------------------------------------------------------------- 命令: 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-first 说明:--dst-db 即数据库文件完整路径(相对/绝对皆可);SQLite 无建库概念, --drop-target-first 时删除旧文件后重建;自增 id 由 rowid 自动跟随,无需重置。 输出案例: ============================================================== [迁移开始] 源: mysql:djangoadmin.fastapi.elevue → 目标: sqlite:D:/data/djangoadmin.fastapi.elevue.db [迁移开始] 待迁移 23 张表 ============================================================== [·] [1/5] 建目标库...(可能耗时较长,请勿中断) · 检查/准备 sqlite 文件 D:\data\djangoadmin.fastapi.elevue.db... · 删除旧 sqlite 文件 D:\data\djangoadmin.fastapi.elevue.db... [✓] [1/5] 建目标库 完成(耗时 8ms) [·] [2/5] 建表结构(23 张表 create_all)...(可能耗时较长,请勿中断) [✓] [2/5] 建表结构(23 张表 create_all) 完成(耗时 96ms),23 张表 [·] [3/5] 逐表迁移数据...(可能耗时较长,请勿中断) · [1/23] 正在迁移 fastapi_dept... [✓ fastapi_dept] 共 23 行(耗时 4ms) ... · [6/23] 正在迁移 fastapi_city... [✓ 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 ============================================================== 4.4 MySQL → Oracle -------------------------------------------------------------------------------- 命令: 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-first 说明: - Oracle 无"数据库名",--dst-db 填 SERVICE_NAME(如 ORCLPDB1、XE); schema 即 --dst-user 连接的用户(无需也无法"建库",连上即视为就绪), 该用户需有 CONNECT, RESOURCE 权限 - Oracle create_all 对 Integer 主键只生成 INTEGER NOT NULL(无 identity), 迁移后脚本会自动补 sequence(START WITH MAX(id)+1)+ BEFORE INSERT 触发器, 保证后续 save() 能取到新 id - --drop-target-first 时尽力 DROP TABLE ... PURGE 清理旧项目表 输出案例: ============================================================== [迁移开始] 源: mysql:djangoadmin.fastapi.elevue → 目标: 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] 建表结构(23 张表 create_all) 完成(耗时 2.4s),23 张表 [·] [3/5] 逐表迁移数据...(可能耗时较长,请勿中断) · [1/23] 正在迁移 fastapi_dept... [✓ fastapi_dept] 共 23 行(耗时 15ms) ... · [6/23] 正在迁移 fastapi_city... [✓ fastapi_city] 共 4150 行(耗时 2m12s) ... [✓] [3/5] 逐表迁移数据 完成(耗时 2m41s),共 4843 行 [·] [4/5] 重置自增序列...(可能耗时较长,请勿中断) [✓] [4/5] 重置自增序列 完成(耗时 6.2s) ← 补 sequence + BEFORE INSERT 触发器 [·] [5/5] 逐表行数校验...(可能耗时较长,请勿中断) [✓] [5/5] 逐表行数校验 完成(耗时 2.6s),23 张表全部 PASS ============================================================== [迁移完成] 成功:4843 行 / 23 张表 / 总耗时 2m54s ============================================================== 五、常用参数详解 ================================================================================ --drop-target-first 先删目标库再重建(Oracle 清旧表、SQLite 删旧文件)。默认关; 重跑迁移时建议开启,保证幂等。 --dry-run 只连源库、逐表数行数、打印计划,不触碰目标库。 --chunk-size N 每批插入行数,默认 1000;大表可调大(如 5000)提升吞吐。 --only-tables a,b 只迁移指定表(全名或去前缀短名,如 --only-tables user,role),续传用。 --skip-existing 目标表行数已等于源表则跳过(配合续传/增量)。 --skip-verify 跳过迁移后的逐表行数校验。 --single-transaction 全部表包进一个大事务(默认每表一个事务;大表建议保持默认, 避免单事务过大)。 口令脱敏 --src-pass/--dst-pass 留空时回落环境变量 SRC_DB_PASS/DST_DB_PASS, 避免口令进 shell 历史。 六、常见问题与注意事项 ================================================================================ 1. 目标库已存在且未加 --drop-target-first:脚本只做 create_all(IF NOT EXISTS 语义)+ 补数据, 已存在表的数据会与迁移数据叠加,可能主键冲突。重跑请加 --drop-target-first。 2. 迁移后登录报 "no such table" / 表不存在:确认 --dst-db 指向正确(Oracle 填 SERVICE_NAME, 不是库名;SQLite 填文件路径)。 3. 中文乱码:源 MySQL 连接串已带 charset=utf8mb4;PG 建库强制 UTF8/TEMPLATE template0; mssql 连接串 charset=utf8。若目标已建成非 UTF8 编码库,删除重建(--drop-target-first)。 4. Oracle 迁移后新增记录 id 异常:migrate_db.py 已自动补 sequence+触发器, 若手动建过同名 sequence 会先删后建(user_sequences 里按名查)。 5. 自增 id 未延续原值:PG 用 setval、MSSQL 用 DBCC CHECKIDENT、Oracle 用 sequence 均已自动处理; 如仍异常可手动: PostgreSQL:SELECT setval(pg_get_serial_sequence('fastapi_user','id'), (SELECT max(id) FROM fastapi_user)); SQL Server:DBCC CHECKIDENT ('fastapi_user', RESEED, ) Oracle: DROP SEQUENCE fastapi_user_id_seq; CREATE SEQUENCE fastapi_user_id_seq START WITH ; 6. 迁移过程被中断:已迁移的表按每表一个事务提交,未迁完的表重跑即可;配合 --only-tables / --skip-existing 断点续传。 ================================================================================ 文档版本:1.0 | 更新日期:2026-08-14 | 适用范围:scripts/migrate_db.py ================================================================================ 其他:整体流程 ┌─────────────────────────────────────────────────────┐ │ 1. 解析 CLI 参数 │ │ --src-* / --dst-* / --drop-target-first │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 2. create_database_if_missing() │ │ ├─ MySQL: CREATE DATABASE IF NOT EXISTS │ │ ├─ PG: CREATE DATABASE (连接 postgres) │ │ ├─ MSSQL: CREATE DATABASE (连接 master) │ │ ├─ Oracle: 不建库,只清理表 │ │ └─ SQLite: mkdir + 可选删文件 │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 3. Base.metadata.create_all(dst_engine) │ │ ├─ 方言适配:自动生成对应数据库的 DDL │ │ ├─ 按 ordered_tables() 顺序建表 │ │ └─ 包含所有模型定义(字段/索引/约束) │ └─────────────────────────────────────────────────────┘ ↓ ┌─────────────────────────────────────────────────────┐ │ 4. 后续:逐表泵数据 → 重置序列 → 校验 │ └─────────────────────────────────────────────────────┘