326 lines
19 KiB
Plaintext
326 lines
19 KiB
Plaintext
================================================================================
|
||
数据库迁移指南(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, <max_id>)
|
||
Oracle: DROP SEQUENCE fastapi_user_id_seq; CREATE SEQUENCE fastapi_user_id_seq START WITH <max_id+1>;
|
||
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. 后续:逐表泵数据 → 重置序列 → 校验 │
|
||
└─────────────────────────────────────────────────────┘ |