Skip to content

数据库迁移 ​

概述 ​

项目使用 Flask-Migrate(基于 Alembic)管理数据库结构迁移,同时提供跨库数据迁移脚本,支持从 MySQL 迁移到 PostgreSQL / SQL Server / SQLite / Oracle 等多种数据库。

迁移工具链

  • 结构迁移:Flask-Migrate / Alembic(自动生成 DDL 差异)
  • 数据迁移:scripts/migrate_db.py(跨库数据泵)
  • 初始化:scripts/setup_db.py(全新安装一键建库)

全新安装初始化 ​

首次部署时,使用初始化脚本一键完成建库、建表、Flask-Migrate stamp:

bash
python scripts/setup_db.py

初始化流程

  1. 检测并创建数据库(如不存在)
  2. 执行 Base.metadata.create_all() 建表
  3. 导入基础 SQL 数据(如有)
  4. Flask-Migrate stamp head(标记当前为最新版本)

Flask-Migrate 结构迁移 ​

生成迁移脚本 ​

修改模型后,执行以下命令自动生成迁移脚本:

bash
# 生成增量迁移脚本
flask db migrate -m "描述变更内容"

# 或使用 Make 命令
flask db migrate

注意事项

  • 执行前确保数据库连接正常(.env 中的数据库配置正确)
  • 自动生成的脚本需人工审查,确认无误后再执行
  • 迁移脚本位于 migrations/versions/ 目录

应用迁移 ​

bash
# 应用所有待执行的迁移
flask db upgrade

# 或使用 Make 命令
flask db upgrade

回滚迁移 ​

bash
# 回滚最近一次迁移
flask db downgrade -1

# 回滚到指定版本
flask db downgrade <revision_id>

查看迁移状态 ​

bash
# 查看当前版本
flask db current

# 查看迁移历史
flask db history

手动标记版本 ​

对于已有数据库,手动标记为最新版本:

bash
flask db stamp head

# 或使用 Make 命令
flask db stamp head

跨库数据迁移 ​

将源库(通常 MySQL)的结构和数据整体迁移到目标库(PostgreSQL / SQL Server / SQLite / Oracle)。

脚本说明 ​

scripts/migrate_db.py 是项目内置的跨库迁移脚本,核心特性:

  • 目标库结构:用模型 metadata 生成(Base.metadata.create_all),方言可移植,模型即 head
  • 数据泵:按拓扑序逐表 SELECT ... ORDER BY id,显式带 id 批量插入,保住无外键裸列引用(user.dept_id、role_menu.role_id/menu_id、menu.parent_id 等)
  • 自增序列重置:迁移后按方言自动处理(详见下文)
  • 进度提示:全部耗时步骤带缓冲提示([·] 请勿中断 → [✓] 完成),不会显得卡死
  • 连接独立:源/目标连接全部由 CLI 显式参数给出,不依赖 .env,也无需先切 DB_DRIVER

前置准备 ​

  1. 源库(MySQL)可正常访问
  2. 目标库所需驱动已包含在 requirements.txt:
目标库驱动包说明
PostgreSQLpsycopg2-binarypsycopg2
SQL Serverpymssql-
SQLite内置无需额外驱动
Oracleoracledbthin 模式,无需安装 Oracle Client
bash
pip install -r requirements.txt

驱动与默认端口 ​

目标库--dst-driver默认端口--dst-db 填什么
MySQLmysql3306数据库名
PostgreSQLpostgresql5432数据库名(含点号会自动双引号)
SQL Servermssql1433数据库名
SQLitesqlite无端口SQLite 文件路径(相对/绝对皆可)
Oracleoracle1521SERVICE_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 历史。

干跑示例 ​

迁移前建议先干跑核对计划:

bash
# 干跑:核对源库表和行数
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.flask.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.flask.elevue"

MySQL → PostgreSQL ​

bash
python scripts/migrate_db.py \
    --src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
    --src-db=djangoadmin.flask.elevue --src-user=root --src-pass=root \
    --dst-driver=postgresql --dst-host=localhost --dst-port=5432 \
    --dst-db=djangoadmin.flask.elevue --dst-user=postgres --dst-pass=你的密码 \
    --drop-target-first

输出示例:

==============================================================
[迁移开始] 源: mysql:djangoadmin.flask.elevue → 目标: postgresql:djangoadmin.flask.elevue
[迁移开始] 待迁移 23 张表
==============================================================
[·] [1/5] 建目标库...
  · 正在连接 postgres 维护库...
  · 正在创建目标库 "djangoadmin.flask.elevue"(UTF8)...
[✓] [1/5] 建目标库 完成(耗时 947ms)
[·] [2/5] 建表结构(23 张表 create_all)...
[✓] [2/5] 建表结构 完成(耗时 1.1s),23 张表
[·] [3/5] 逐表迁移数据...
  · [1/23] 正在迁移 flask_dept...
  [✓ flask_dept] 共 23 行(耗时 12ms)
  · [3/23] 正在迁移 flask_city...
  [flask_city] +1000(累计 1000)
  [flask_city] +1000(累计 2000)
  [flask_city] +1000(累计 3000)
  [flask_city] +1000(累计 4000)
  [flask_city] +150(累计 4150)
  [✓ flask_city] 共 4150 行(耗时 850ms)
  ...
[✓] [3/5] 逐表迁移数据 完成(耗时 3.5s),共 4843 行
[·] [4/5] 重置自增序列...
[✓] [4/5] 重置自增序列 完成(耗时 412ms)   ← PG setval 到 MAX(id)
[·] [5/5] 逐表行数校验...
  [flask_dept] source=23 target=23 PASS
  [flask_city] source=4150 target=4150 PASS
  ...
[✓] [5/5] 逐表行数校验 完成(耗时 103ms),23 张表全部 PASS
==============================================================
[迁移完成] 成功:4843 行 / 23 张表 / 总耗时 6.0s
==============================================================

MySQL → SQL Server ​

bash
python scripts/migrate_db.py \
    --src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
    --src-db=djangoadmin.flask.elevue --src-user=root --src-pass=root \
    --dst-driver=mssql --dst-host=localhost --dst-port=1433 \
    --dst-db=djangoadmin.flask.elevue --dst-user=sa --dst-pass=你的密码 \
    --drop-target-first

SQL Server 特殊处理

迁移时自动 SET IDENTITY_INSERT ON 保留原 id,迁移后 DBCC CHECKIDENT RESEED 重置自增。

MySQL → SQLite ​

bash
python scripts/migrate_db.py \
    --src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
    --src-db=djangoadmin.flask.elevue --src-user=root --src-pass=root \
    --dst-driver=sqlite --dst-db=D:/data/djangoadmin.flask.elevue.db \
    --drop-target-first

SQLite 说明

  • --dst-db 即数据库文件完整路径(相对/绝对皆可)
  • SQLite 无建库概念,--drop-target-first 时删除旧文件后重建
  • 自增 id 由 rowid 自动跟随,无需重置序列

MySQL → Oracle ​

bash
python scripts/migrate_db.py \
    --src-driver=mysql --src-host=127.0.0.1 --src-port=3306 \
    --src-db=djangoadmin.flask.elevue --src-user=root --src-pass=root \
    --dst-driver=oracle --dst-host=localhost --dst-port=1521 \
    --dst-db=ORCLPDB1 --dst-user=flask --dst-pass=你的密码 \
    --drop-target-first

Oracle 特殊说明

  • Oracle 无"数据库名",--dst-db 填 SERVICE_NAME(如 ORCLPDB1、XE)
  • schema 即 --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 清理旧表

幂等重跑 ​

迁移失败或需重跑时,加 --drop-target-first 可安全重跑:

bash
python scripts/migrate_db.py ... --drop-target-first

警告

--drop-target-first 会删除目标库所有表(Oracle 清旧表、SQLite 删旧文件),仅在确认目标库无生产数据时使用。

断点续传 ​

迁移过程被中断时,已迁移的表按每表一个事务提交,未迁完的表可配合 --only-tables 和 --skip-existing 断点续传:

bash
# 只迁移未完成的表
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 插入,保住引用完整性。

各驱动自增序列处理 ​

驱动处理方式说明
PostgreSQLsetval(pg_get_serial_sequence(...), MAX(id))SERIAL 序列重置到最大值
SQL ServerIDENTITY_INSERT ON + DBCC CHECKIDENT RESEED显式插 id + 重置种子
Oracle补 sequence + BEFORE INSERT 触发器create_all 无原生自增,脚本自动补建
MySQL / SQLite自动跟随显式插 id 后 auto_increment / rowid 自动跟随

如需手动修复自增:

sql
-- PostgreSQL
SELECT setval(pg_get_serial_sequence('flask_user', 'id'), (SELECT max(id) FROM flask_user));

-- SQL Server
DBCC CHECKIDENT ('flask_user', RESEED, <max_id>)

-- Oracle
DROP SEQUENCE flask_user_id_seq;
CREATE SEQUENCE flask_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 断点续传

常见操作速查 ​

场景命令
全新安装python scripts/setup_db.py
模型变更后生成迁移flask db migrate
应用迁移flask db upgrade
回滚一次迁移flask db downgrade -1
标记已有库为最新flask db stamp head
跨库迁移干跑make db-migrate-dry-run ARGS="..."
跨库迁移执行make db-migrate ARGS="..."

总结 ​

数据库迁移分为结构迁移(Flask-Migrate / Alembic)和数据迁移(migrate_db.py)两部分。全新安装用 python scripts/setup_db.py,模型变更用 Flask-Migrate 自动生成迁移脚本,跨库切换用 migrate_db.py 数据泵。建议每次迁移前先干跑确认,生产环境务必做好备份。

小蚂蚁云团队 · 提供技术支持