编辑 | blame | 历史 | 原始文档

JNPF MySQL → PostgreSQL 迁移工具包操作手册

🏁 迁移已完成,本工具包大部分已退役(2026-07-25 标注)

MySQL→PG 迁移于 2026-07-21 全部完成(数据落在腾讯云 CVM 共享 PG YOUR_DB_HOST:15432
开发与测试共用一库)。本目录内脚本的现状分三类:

脚本 现状
backup.sh 仍是主力——CVM 库每日 03:00 四库备份即由它执行
create-app-roles.sh 仍需要——新环境(含将来的正式环境)restore 前必须先建角色,否则 owner 落不对
install.sh / assemble-*.sh ⚠️ 仅"全新空库"备用——上线已改为整库 dump/restore(见 docs/release-checklist.md §1),不再现场重建 schema
migrate-data.sh / migrate-nacos-config.sh / schema-drift-check.sh 已退役,勿再执行——它们的前提是"腾讯云 MySQL 是事实源",该前提已消失(MySQL 数据停在 07-16,仅作冷备回退底牌保留)

误跑 migrate-data.sh 的后果是真实的数据事故:它会用 MySQL 旧快照 truncate 重灌目标库。
脚本已加执行护栏(启动即拒绝,须显式设 I_KNOW_MIGRATION_IS_RETIRED=1 才放行)。

当前拓扑与连接纪律:docs/db-connection-pool-guide.md

定位(2026-07-17,Backlog B 方案 (c) 合流后):本目录是**一次性 MySQL→PG 数据迁移工具包**,
只覆盖「schema 初始化 + 停机窗口迁数据 + 备份」;现场割接完成后即退役。
PG 容器的部署统一走仓库根 docker-compose.yml(Backlog A 加固基线:端口收敛 + ${VAR:?}
硬约束凭据),旧 docker-compose.prod.yml 单机部署面已删除。
整栈离线交付见 docs/docker-compose-guide.md §14(make images-offline);本目录会被收入
交付包的 migration/ 子目录。方案背景见 docs/archive/pg-migration-plan.md,踩坑见 tasks/lessons.md


0. 工具包内容

文件 说明
install.sh schema 初始化(全新空库):建四库(jnpf_init/jnpf_flow/jnpf_xxljob/nacos_config)+ 建表
migrate-data.sh 停机窗口一次性数据迁移(现场 MySQL → PG)
backup.sh pg_dump 定时备份 + 保留策略(配 cron),默认含 nacos_config
create-app-roles.sh + create_app_roles.sql 建业务专用连库角色(三库三角色,读 .env,幂等,L030)
schema-drift-check.sh 表级+列级漂移检查(远程 MySQL 事实源 vs 本地 PG);合并 main 或数据刷新**之前**先跑,阻断项直接给类型明细(SOP 见 docs/docker-compose-guide.md §18,L035)
assemble-package.sh 构建机专用,把 repo 里的 SQL 组装进 sql/(现场不用)
sql/ 全部初始化/迁移 SQL(构建机 assemble 生成,不进 git)

构建机组装(交付前,在开发机跑一次)

cd docker/postgres/migration
./assemble-package.sh          # 把 sql/ 填满(含官方 init + 自建表 DDL + pgloader 模板)

组装结果随 make images-offline 一并进整栈离线交付包,不再单独打 tar。


1. 现场前置条件

  • Linux x86_64,已装 Docker + docker compose 插件docker compose version 可用)。
  • 数据盘:/data 分区建议 ≥ 20GB(PG 数据 + 备份 + 临时 dump)。
  • 与现场 MySQL(源库)网络可达。
  • PG 容器已按根 compose 拉起(见 §2);离线环境镜像已 docker load(含 mysql:8.0
    dimitri/pgloader 迁移辅助镜像,在交付包 images/jnpf-migration-tools-*.tar.gz)。

2. 起 PG 容器与建 schema(可提前做,不需停机)

PG 由根 compose(仓库根或交付包根)拉起——凭据经 .env 注入、基线不对外发布端口:

# 在仓库根 / 交付包根:
cp .env.example .env && vi .env        # 全部密码改现场值(角色名/用户名保持默认,SQL 已硬编码)
docker volume create postgres_pgdata   # 全新机器
docker compose up -d cx-postgres     # 只起 PG(healthcheck 过了即可)

混合形态(JNPF 服务暂留宿主进程、仅 PG 容器化):基线不发布 PG 端口,需临时 override——
yaml # docker-compose.override.yml(现场临时,切全容器后删除) services: cx-postgres: ports: ["127.0.0.1:5432:5432"]
全容器形态无需此步(业务经 compose 网络服务名 cx-postgres:5432 直连,见 release-checklist §0.10)。

然后建 schema(**只用于全新空库**;库里已有表会拒绝执行,防止 init 里的 DROP TABLE 清数据):

cd migration/          # 交付包内;仓库内则 cd docker/postgres/migration
./install.sh           # 建四库 → 官方 init → 自建表 → 数据接口修正 → Nacos 存储库 schema

结束会打印表数核对,应为:
jnpf_init BASE TABLE = 366 (官方293 + 自建73) jnpf_flow BASE TABLE = 70 jnpf_xxljob BASE TABLE = 11 nacos_config BASE TABLE = 12

此刻库是**空 schema**,业务数据在下一步停机窗口迁入。
schema 就绪后按 docs/release-checklist.md §0.3 跑 ./create-app-roles.sh 建业务角色
(顺序铁律 L030:先定稿 .env → 跑脚本 → 同步 Nacos datasource.yaml → 重启消费方)。


3. 停机窗口:数据迁移

迁移期间需**停止对源 MySQL 的写入**(停 JNPF 业务服务),保证数据一致。

3.1 时序

① 停 JNPF 业务服务(停止对源 MySQL 写入)
② cd migration/ && SRC_MYSQL_HOST=... SRC_MYSQL_PWD='***' ./migrate-data.sh all
③ 核对 migrate-data.sh 末尾抽样校验 + 跑全量 rowcount 校验(§3.3)
④ 切 Nacos datasource.yaml 到 PG(§4)
⑤ 重启 JNPF 服务,冒烟验证
⑥ 清理临时资源:./migrate-data.sh cleanup

3.2 migrate-data.sh all 做了什么

  1. dump:从现场 MySQL mysqldump--single-transaction --hex-blob,跳过 base_sys_log 大日志表)导出 jnpf_init / jnpf_flow
  2. mirror:起临时 mysql:8.0 中转容器(mysql_native_password),灌入 dump。
    —— 为何中转:pgloader 的 qmynd 库不支持 MySQL8 握手,必须经 native_password 的 mirror。
  3. load
  • pgloaderjnpf_init(排除 base_sys_logblade_visual_map)与 jnpf_flow(大写 ACT/FLW 表)。
  • blade_visual_map(巨行表,pgloader 会堆爆)走 mysqldump --tab → PG COPY(NULL=\N 天然兼容)。
  • 重放数据接口修正 data_interface_pg_fixes.sql(4 条 UPDATE)。
  • lims_biz_log.log_seq 序列 setval 到当前最大值。
  • 抽样行数校验(mirror vs PG)。

源库地址与密码**必须显式传入**(不设软默认,codex review 2026-07-17);目标 PG 密码默认读
仓库根/交付包根 .envJNPF_PG_PASSWORD
bash SRC_MYSQL_HOST=x.x.x.x SRC_MYSQL_PORT=3306 SRC_MYSQL_USER=root SRC_MYSQL_PWD='***' \ ./migrate-data.sh all

迁移产生的 dump/ 目录含完整业务数据,验收后务必 ./migrate-data.sh cleanup 清除。

3.3 全量逐表行数校验(强烈建议)

# mirror 仍在时,逐表对比 MySQL 源 vs PG(应输出「不一致 0」)
MYSQL_HOST=cx-mysql-mirror MYSQL_PORT=3306 MYSQL_USER=root MYSQL_PWD_V=mirror_2026 \
  MYSQL_DOCKER_NET=$(docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}}{{end}}' cx-postgres) \
  PG_CONTAINER=cx-postgres \
  bash sql/rowcount_check.sh jnpf_init
# jnpf_flow 同理:
... bash sql/rowcount_check.sh jnpf_flow

4. 切换 Nacos 数据源

datasource.yaml 改为 PG(示例值,现场按实际填):
yaml jnpf: datasource: db-type: PostgreSQL host: 127.0.0.1 # PG 容器宿主 port: 5432 db-name: jnpf_init username: postgres password: 改成强密码 # 与 install 时 PG_PASSWORD 一致
改法二选一:
- 控制台:Nacos → 配置管理 → datasource.yaml(group=DEFAULT_GROUP、对应命名空间)→ 编辑发布。
- API
bash TOKEN=$(curl -s -X POST 'http://<nacos>:8848/nacos/v1/auth/login' \ -d 'username=nacos&password=nacos' | sed 's/.*"accessToken":"\([^"]*\)".*/\1/') curl -X POST "http://<nacos>:8848/nacos/v1/cs/configs?accessToken=$TOKEN" \ --data-urlencode 'dataId=datasource.yaml' \ --data-urlencode 'group=DEFAULT_GROUP' \ --data-urlencode 'tenant=<命名空间id>' \ --data-urlencode 'type=yaml' \ --data-urlencode 'content@datasource.yaml'

⚠️ ConfigValueUtil 没有 @RefreshScope:改完 Nacos 必须重启 JNPF 服务才生效,不会热更新。

重启后冒烟:登录 → 菜单 → 打开若干 LIMS 台账列表(数据/中文正常)→ 文件上传预览(注意 L015:Nacos config.ApiDomain 要指现场网关域名)。


5. 定时备份

# 手动跑一次确认可用(产物落 volume 外)
BACKUP_DIR=/data/pg-backup ./backup.sh

# crontab -e,每天 02:00 备份,保留 14 天(路径按现场实际解包位置调整)
0 2 * * * BACKUP_DIR=/data/pg-backup RETENTION_DAYS=14 /opt/jnpf/offline/migration/backup.sh >> /var/log/jnpf-pg-backup.log 2>&1

还原(custom 格式 -Fc,可选择性还原):
bash docker exec -i cx-postgres pg_restore -U postgres -d jnpf_init --clean --if-exists < /data/pg-backup/jnpf_init_YYYYMMDD_HHMMSS.dump


5.5 附加:Nacos 与 xxl-job-admin 存储库也切 PG(可选,建议同窗口做)

本地已验证(2026-07-16):JNPF 定制 Nacos 与 xxl-job-admin 均原生支持 PG,切换后 MySQL 可完全下线。

Nacos 存储库

  1. PG 建库跑 schema:install.sh 已把 nacos_config 作为第四库一并建好(含 nacos/nacos 登录种子——
    官方 mysql-schema.sql 无 INSERT,漏这步空库无法登录)。老版本单独建库的命令保留备查:
    bash docker exec cx-postgres psql -U postgres -c "CREATE DATABASE nacos_config ENCODING 'UTF8'" docker exec -i cx-postgres psql -U postgres -d nacos_config -v ON_ERROR_STOP=1 -f - < sql/nacos_config_pg.sql
  2. (已有 Nacos 迁移时)**先经旧 Nacos API 导出**全部 namespace + 配置(逐条 GET content 落盘)。
  3. nacos/conf/application.properties(先备份;文件里自带 Postgre 注释示例段,照抄改连接串):
    properties spring.datasource.convert=postgre # platform 保持 mysql 不动 db.url.0=jdbc:postgresql://<PG宿主>:5432/nacos_config?currentSchema=public db.user=postgres db.password=<PG密码>
  4. 重启 Nacos,logs/start.out 出现 SwitchDatabase:postgre + use external storage 即成功;登录(nacos/nacos,尽快改密)→ 同 id 重建 namespace(customNamespaceId 参数)→ 重发配置 → 逐条 GET 回来与导出内容 diff。
  5. 切换顺序(重要,实测教训)建库跑 schema → 导出旧配置 → 停全部 JNPF 业务服务 → 改配置重启 Nacos → 导入配置并 diff → 按序拉起业务服务。**不要让业务服务带着旧连接硬扛 Nacos 重启**——nacos-client 在服务端不可达窗口会泄漏 grpc 重连线程(实测 1.5h 后打满每进程 4096 上限,服务假活、注册表逐个掉线,见 tasks/lessons.md L022)。
  6. 回滚:恢复 application.properties 备份 + 重启。

xxl-job-admin

  1. application-dev.yml 里注释 MySQL 段、启用自带的 PostgreSQL 注释段:
    yaml datasource: url: jdbc:postgresql://<PG宿主>:5432/jnpf_xxljob username: postgres password: <PG密码> driver-class-name: org.postgresql.Driver # 驱动 jar 已内置 postgresql-42.7.8
  2. server.sh restart,登录 /xxl-job-admin(admin/123456 种子)验证列表/日志页正常。
  3. ⚠️ JNPF 商业 license:admin 启动要求 jnpf.license.licensePath(默认 /data/jnpfsoft/license.lic)存在且有效(绑定硬件),缺失即退出——本地开发机无 license 未验启动,**此步必须在现场有 license 的机器上做**。
  4. jnpf-scheduletask(30009) 服务本身走主 datasource.yaml(已是 PG),datasource-scheduletask.yaml 里只有 admin 地址无 DB 连接,无需改。

6. 回滚方案

PG 侧数据是**新写入**,源 MySQL 在停机窗口内**未被改动** → 回滚零数据损失:

  1. 把 Nacos datasource.yaml 切回 MySQL 原连接
    yaml db-type: MySQL host: <现场源 MySQL 地址> port: 3306 db-name: jnpf_init username: <原用户> password: <原密码>
  2. 重启 JNPF 服务(同样因无 @RefreshScope)。
  3. 业务恢复到 MySQL。PG 容器可保留待排查,不影响回滚。

回滚判据:切 PG 后核心链路冒烟失败、或 §3.3 校验发现数据缺失且无法快速修复。


7. 常见问题(对应 lessons.md)

  • 服务报 database "jnpf_init" does not existdocker exec psql 能看到表(L016):宿主机另有 PG 占了 5432,服务连错实例。lsof -iTCP:5432 -sTCP:LISTEN 查占用;现场应确保只有容器监听 5432。
  • 脚本化登录报"不支持此登录方式"(L017):JNPF 登录是 form-urlencoded + AES(MD5(密码)),不能用 JSON 裸调。
  • 某在线模型 List 返回空(L018):先查 base_visual_release.f_web_type,webType=1 纯表单无列表属正常,非数据丢失。
  • 文件上传成功但预览/下载 502(L015):Nacos system-config.yamlconfig.ApiDomain 要指现场网关;改后重启 jnpf-file

8. 命令速查

目的 命令
起 PG 容器(根 compose) docker volume create postgres_pgdata && docker compose up -d cx-postgres
建 schema(全新空库) ./install.sh
建业务角色(.env 定稿后) ./create-app-roles.sh
停机窗口迁数据 SRC_MYSQL_HOST=... SRC_MYSQL_PWD='***' ./migrate-data.sh all
只跑 PG 侧(mirror 已就绪) ./migrate-data.sh load
全量行数校验 见 §3.3
清理临时 mirror/dump ./migrate-data.sh cleanup
备份 BACKUP_DIR=/data/pg-backup ./backup.sh
进 PG 命令行 docker exec -it cx-postgres psql -U postgres -d jnpf_init