# 迁移操作手册
这份手册按执行顺序排列,每一步都写清楚三件事:在哪台机器上、开什么窗口、敲什么命令。不用批处理,全部手工执行,遇到问题随时停下来问。
## 环境速查
| 角色 | 地址 | 系统 | PG 工具路径 |
| --- | --- | --- | --- |
| 源库 | `10.62.204.32:5432` | Windows Server | `D:\pushServer\postgresql\bin` |
| 目标库 | `10.62.210.67:5432` | Windows Server | `E:\swtools\PostgreSQL\12\bin` |
| Java 应用 | `10.62.210.40` | 麒麟 Linux | 与数据库分离部署 |
**盘符注意:** 目标机 `10.62.210.67` **只有 E 盘,没有 D 盘**。所以本手册里所有工作目录都在 `E:\`(`E:\db_backup`、`E:\migration`)。上面表格里那个 `D:\pushServer\postgresql\bin` 是源机 `10.62.204.32` 的 PG 路径,那台机有 D 盘,不要跟着改。
库名对照,这里最容易搞错,每条命令的 `-d` 参数都要看准:
```
源机 10.62.204.32
gansueqwin 4248 MB 本次要迁的就是它
gansueqwin_new 5189 MB 老版本,不迁,别碰
目标机 10.62.210.67
gansueq 6667 MB 现在线上用的旧库,第 2 步改名成 gansueq_old 保留
gansueqwin 5519 MB 历史数据,第 2 步改名成 gansueqwin_hist,只保留不用
gansueq 迁移后的新库,第 4 步新建
```
数据库口令统一是 `postgres2024`,用户名 `postgres`。
## 全流程一览
```
不停机(提前做,能做完全部做完)
阶段一 第 1 步 备份目标机旧库
第 2 步 从源库导出
第 3 步 本地用 pro 启动一次(可选但建议)
停机窗口开始
阶段二 第 4 步 停应用
第 5 步 旧库改名让位
第 6 步 建新库
第 7 步 还原
第 8 步 修序列
第 9 步 数据校验
第 10 步 改 profile 并打包部署
第 11 步 启动与验收
停机窗口结束
出问题 第 12 步 回滚
```
阶段一全部不需要停服,`pg_dump` 是在线操作。把这些提前做完,实际停机只从第 4 步开始。
## 阶段一:不停机准备
### 第 1 步:备份目标机旧库
**在哪:** 目标机 `10.62.210.67`,开一个 CMD 窗口。
**为什么先做:** 备份 6667 MB 要十几分钟到半小时。此刻应用还在跑、库还叫 `gansueq`,先把备份导好,停机时间就不用算这一段。
设置工具路径。**这个变量每开一个新 CMD 窗口都要重设一次**,后面每一步都在用:
```bat
set PGBIN=E:\swtools\PostgreSQL\12\bin
set PGPASSWORD=postgres2024
```
确认工具版本,应显示 `12.x`:
```bat
"%PGBIN%\pg_dump" --version
```
建目录并导出:
```bat
mkdir E:\db_backup
"%PGBIN%\pg_dump" -h 10.62.210.67 -U postgres -d gansueq -Fc -f E:\db_backup\gansueq_backup.dump
```
这条会跑十几分钟,没有进度显示,等它回到命令提示符即可。
验证备份文件没损坏。这步不要省,备份坏了而不自知是很常见的事故:
```bat
"%PGBIN%\pg_restore" -l E:\db_backup\gansueq_backup.dump | more
dir E:\db_backup
certutil -hashfile E:\db_backup\gansueq_backup.dump SHA256
```
`pg_restore -l` 能列出一大串对象清单就说明文件是好的。按 `Q` 退出 `more`。把那个 SHA256 值记下来。
**做完这步告诉我一声**,我帮你确认输出是否正常。
### 第 2 步:从源库导出
**在哪:** 还是目标机 `10.62.210.67` 上的 CMD 窗口,远程连源库拉数据。
**为什么在目标机做:** 省一次文件传输,而且导出和还原用同一套 PG 12 工具,版本天然匹配。
先确认要导的库对不对。源实例里有两个名字相近的库:
```bat
"%PGBIN%\psql" -h 10.62.204.32 -U postgres -d postgres -c "SELECT datname, pg_size_pretty(pg_database_size(datname)) FROM pg_database WHERE datname LIKE 'gansueq%';"
```
应该看到 `gansueqwin` 4248 MB 和 `gansueqwin_new` 5189 MB。**要导的是 `gansueqwin`**,没有 `_new` 后缀那个。
再核一次表数量:
```bat
"%PGBIN%\psql" -h 10.62.204.32 -U postgres -d gansueqwin -c "SELECT table_schema, count(*) FROM information_schema.tables WHERE table_schema IN ('public','mapserver_data') AND table_type='BASE TABLE' GROUP BY 1;"
```
应返回 `mapserver_data 88` 和 `public 210`。数字对得上再往下走,对不上先停下来问我。
`table_type='BASE TABLE'` 这个条件不能省。不加的话视图也会被算进去,`public` 会返回 214 而不是 210,看着像凭空多了 4 张表。采集基线用的就是带这个过滤的口径。
开始导出。先导结构,快,几秒钟:
```bat
mkdir E:\migration
"%PGBIN%\pg_dump" -h 10.62.204.32 -p 5432 -U postgres -d gansueqwin --schema-only --no-owner --no-privileges -f E:\migration\schema.sql
```
再导全量,这条要十几分钟:
```bat
"%PGBIN%\pg_dump" -h 10.62.204.32 -p 5432 -U postgres -d gansueqwin --no-owner --no-privileges -Fc -f E:\migration\full.dump
```
`--no-owner --no-privileges` 是刻意加的,源库的用户和权限在目标机上未必存在,去掉能避免还原时一堆 role 报错。
验证:
```bat
dir E:\migration
"%PGBIN%\pg_restore" -l E:\migration\full.dump | more
```
**重要提醒:** 这一步导完之后,源库若还有新数据写入,那部分不会进新库。记下导出完成的时刻。如果源系统数据变化不频繁(从行数看是这样,`t_100_real_earthquake` 才 17475 行),影响很小;否则就把这一步挪到第 4 步停应用之后再做。
### 第 3 步:本地用 pro 启动一次(建议做)
**在哪:** 你自己的开发机,IDE 里。
**做什么:** 把 `application.yml` 的 `active` 临时改成 `pro`,启动一次看能不能起来。
已经实测过 `pro` 的配置是完整的,所以这步大概率一次通过。做它的意义是提前暴露意外,别在停机窗口里试错。
起不来的话把报错发我。起来了就把 `active` 改回去,等第 10 步再正式改。
## 阶段二:停机窗口
从这里开始系统不可用。前面阶段一做完的话,这一段大约 40 分钟到 1 小时。
### 第 4 步:停应用
**在哪:** 麒麟 `10.62.210.40`,SSH 登录。
**应用目录:** `/opt/server-jar/ganEarthquake`
**先读这段再动手:应用是用看门狗脚本启动的,直接 kill Java 进程会被自动拉起。**
`start_gansuEq.sh` 的主体是这样:
```bash
while true; do
java $JAVA_OPTS -jar $APP_NAME
...
sleep 5
done
```
Java 一退出,脚本睡 5 秒就重新拉起。所以必须**先停脚本、再停 Java**,否则会出现库改到一半、新进程连上不存在的库的情况。
先看清两个进程:
```bash
ps -ef | grep -E "earthquake|start_gansuEq" | grep -v grep
```
会看到两行(PID 每次不同,按实际的来):
```
root 1359659 ... /bin/bash /opt/server-jar/ganEarthquake/start_gansuEq.sh ← 父,看门狗脚本
root 1726708 1359659 ... java -Dfile.encoding=UTF-8 ... -jar earthquake... ← 子,Java 进程
```
**把这两个 PID 记下来。** 然后先父后子地停:
```bash
kill <脚本的PID>
kill <Java的PID>
```
确认两个都没了:
```bash
ps -ef | grep -E "earthquake|start_gansuEq" | grep -v grep
```
**必须没有任何输出。** 如果 Java 进程又冒出来了,说明脚本没停干净,先确认脚本进程:
```bash
ps -ef | grep start_gansuEq | grep -v grep
```
把它 `kill -9` 掉,再处理 Java。
顺带说明:平时你是用 Ctrl+C 停的,那样能同时结束脚本和 Java(同一进程组)。但当前进程挂在 8 月 20 日就断开的 `pts/5` 上,回不到那个终端,所以这次用 kill。
**记录停机开始时间。**
**记录停机开始时间。**
### 第 5 步:旧库改名让位
**在哪:** 目标机 `10.62.210.67`,CMD 窗口。
改名是元数据操作,秒级完成,数据一行都不动。改完旧库依然完好,只是换了名字。
```bat
set PGBIN=E:\swtools\PostgreSQL\12\bin
set PGPASSWORD=postgres2024
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d postgres
```
进入 psql 后,提示符变成 `postgres=#`。下面的 SQL 一条一条执行:
```sql
\l+
```
先看清现有库名和体积。确认 `gansueq` 和 `gansueqwin` 都在。
```sql
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'gansueq' AND pid <> pg_backend_pid();
```
踢掉旧库的活动连接,改名要求无连接。如果第 4 步应用真停干净了,这条通常返回空。
```sql
ALTER DATABASE gansueq RENAME TO gansueq_old;
```
这就是你的回滚退路,别删它。
```sql
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'gansueqwin' AND pid <> pg_backend_pid();
ALTER DATABASE gansueqwin RENAME TO gansueqwin_hist;
```
目标机这个 `gansueqwin` 是历史数据,你说过可改名不可删。改名是因为它和源库同名,后面命令里两个 `gansueqwin` 太容易搞混。
```sql
\l
```
此时应该看到 `gansueq_old` 和 `gansueqwin_hist`,**没有 `gansueq`**。确认后退出:
```sql
\q
```
### 第 6 步:建新库
**在哪:** 同一个 CMD 窗口。
```bat
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d postgres
```
```sql
CREATE DATABASE gansueq
WITH ENCODING 'UTF8'
LC_COLLATE 'Chinese (Simplified)_China.936'
LC_CTYPE 'Chinese (Simplified)_China.936'
TEMPLATE template0;
```
那两个 `LC_` 参数必须显式写,不是可选项。采集确认两边 collate 都是这个值,默认 template1 未必是,不写会导致中文排序和源库不一致。
```sql
\c gansueq
```
切到新库,提示符会变成 `gansueq=#`。注意后面几条必须在新库里执行。
```sql
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_raster;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
```
三个都要装。后两个是采集查出来的:源库有、目标库只装了 `postgis`。目标机这三个都是「可用未安装」状态,直接建就行,不用装软件。
```sql
SELECT postgis_full_version();
```
应显示 `3.4.2`,与源库一致。
```sql
SELECT extname, extversion FROM pg_extension ORDER BY extname;
```
应返回正好这四行:
```
plpgsql 1.0
postgis 3.4.2
postgis_raster 3.4.2
postgis_topology 3.4.2
```
```sql
CREATE SCHEMA IF NOT EXISTS mapserver_data;
\q
```
### 第 7 步:还原
**在哪:** 同一个 CMD 窗口。
```bat
"%PGBIN%\pg_restore" -h 10.62.210.67 -U postgres -d gansueq --no-owner --no-privileges --jobs=4 --verbose E:\migration\full.dump > E:\migration\restore.log 2>&1
```
这条是整个流程最慢的一步,十几到几十分钟。输出全部写进日志文件,屏幕上不会有东西,等它回到提示符。
`--jobs=4` 按目标机 CPU 核数调,核多可以加大。
还原完了看错误。**会有报错,而且大部分是正常的**:
```bat
findstr /c:"error" E:\migration\restore.log | find /c /v ""
```
这条给出错误总数。然后过滤掉预期内的,看剩下什么:
```bat
findstr /c:"error" E:\migration\restore.log | findstr /v "already exists" | more
```
可以忽略的报错:
```
extension "postgis" already exists 已手动装了
type "geometry" already exists PostGIS 自带
relation "spatial_ref_sys" already exists PostGIS 自带
schema "mapserver_data" already exists 已手动建了
role "xxx" does not exist 加了 --no-owner
could not access file "$libdir/pgrouting" 源库残留野生扩展,业务零引用
could not access file "$libdir/ogr_fdw" 同上
function _pgr_xxx already exists 同上
```
那几个 `$libdir` 报错解释一下:源库里残留了一批 C 函数,来自从未正式登记的扩展(pgRouting 132 个、fuzzystrmatch、address_standardizer、ogr_fdw)。我在全项目 1177 个文件里搜过这些函数名,零引用,业务完全不依赖,放心忽略。
需要停下来排查的:
```
function xxx does not exist 可能 PostGIS 版本不匹配
column xxx does not exist 结构还原不完整
no space left 磁盘不足
```
**过滤后如果还剩不少错误,先别继续,把日志发我。**
### 第 8 步:修序列
**在哪:** 同一个 CMD 窗口。
**为什么重要:** 项目里 106 处 `IdType.AUTO` 全靠序列生成主键。序列没跟过来,新增数据就会主键冲突。这是最容易漏、漏了必出事的一步。
两边各导一份序列值:
```bat
"%PGBIN%\psql" -h 10.62.204.32 -U postgres -d gansueqwin -t -A -F "," -c "SELECT schemaname||'.'||sequencename, last_value FROM pg_sequences ORDER BY 1" > E:\migration\seq_source.csv
```
```bat
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d gansueq -t -A -F "," -c "SELECT schemaname||'.'||sequencename, last_value FROM pg_sequences ORDER BY 1" > E:\migration\seq_target.csv
```
比对,**没有输出就是一致**,这步就过了:
```bat
fc E:\migration\seq_source.csv E:\migration\seq_target.csv
```
顺手核对几个数值最大的,肉眼看一下:
```bat
findstr "t_100_distance_info_id_seq t_100_economic_loss_result_id_seq_a sm_seq_datasource" E:\migration\seq_target.csv
```
对照基线:
```
t_100_distance_info_id_seq 298292
t_100_economic_loss_result_id_seq_a 279160
t_100_assessment_yxc_town_id_seq0 106216
t_100_report_record_seq_3 48941
t_100_five_influence_field_id_seq 25157
sm_seq_datasource 268
```
`sm_seq_datasource` 是 SuperMap 自己的序列,最容易被忽略,漏了会导致新建数据集时 ID 冲突。
有三个序列 `last_value` 是空的(从未使用过),保持默认就行不用管:`hibernate_sequence`、`t_100_influence_param_id_sequence`、`t_100_person_dept_id_sequence`。
**如果 `fc` 报出差异**,进新库执行下面这条。它会按每张表实际最大 id 生成修复语句,比照搬源库值更稳妥:
```bat
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d gansueq
```
```sql
SELECT 'SELECT setval(''' || quote_ident(s.schemaname) || '.' || quote_ident(s.sequencename) || ''', '
|| 'COALESCE((SELECT MAX(' || quote_ident(a.attname) || ') FROM '
|| quote_ident(n.nspname) || '.' || quote_ident(c.relname) || '), 1), true);' AS fix_sql
FROM pg_sequences s
JOIN pg_class sc ON sc.relname = s.sequencename
JOIN pg_depend d ON d.objid = sc.oid AND d.deptype = 'a'
JOIN pg_class c ON c.oid = d.refobjid
JOIN pg_namespace n ON n.oid = c.relnamespace
JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = d.refobjsubid
ORDER BY 1;
```
它输出的是一堆 `SELECT setval(...)` 语句。把结果整段复制出来,粘回 psql 执行一遍即可。
### 第 9 步:数据校验
**在哪:** 同一个 CMD 窗口。
**9.1 表清单比对**
```bat
"%PGBIN%\psql" -h 10.62.204.32 -U postgres -d gansueqwin -t -A -c "SELECT n.nspname||'.'||c.relname FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE c.relkind='r' AND n.nspname IN ('public','mapserver_data') ORDER BY 1" > E:\migration\tables_source.txt
```
```bat
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d gansueq -t -A -c "SELECT n.nspname||'.'||c.relname FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE c.relkind='r' AND n.nspname IN ('public','mapserver_data') ORDER BY 1" > E:\migration\tables_target.txt
```
```bat
fc E:\migration\tables_source.txt E:\migration\tables_target.txt
```
无差异即通过。源库 public 有 205 张表、mapserver_data 88 张。
**9.2 大写表名确认**
这个必须查。PG 会把未加引号的大写标识符折叠成小写,一旦折叠了业务查询会报表不存在。
```bat
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d gansueq -c "SELECT relname FROM pg_class WHERE relname IN ('mapData_gansu','mapData_qgdc','mapData_qgdc_1','mapdata_rkwgNEW','mapdata_qgxjDZM','t_100_personnel_Loss_param');"
```
**应返回 6 行。** 少了就说明大小写被折叠了,需要改回来,例如:
```sql
ALTER TABLE mapdata_gansu RENAME TO "mapData_gansu";
```
**9.3 实测那条依赖大写表名的查询**
这条 SQL 决定「震中是否在甘肃省内」,是核心业务判断,必须实测。用兰州坐标,**应返回 `t`**:
```bat
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d gansueq -c "SELECT ST_DWithin(ST_SetSRID(ST_MakePoint(103.8, 36.06), 4326), (SELECT geom FROM \"mapData_gansu\" WHERE province='甘肃省'), 0);"
```
返回 `f` 或报错都要停下来查。
**9.4 关键表行数**
```bat
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d gansueq -c "SELECT 't_100_real_earthquake' t, count(*) FROM t_100_real_earthquake UNION ALL SELECT 't_100_report_record', count(*) FROM t_100_report_record UNION ALL SELECT 't_100_influence_field', count(*) FROM t_100_influence_field UNION ALL SELECT 't_100_assessment_record', count(*) FROM t_100_assessment_record UNION ALL SELECT 't_100_user', count(*) FROM t_100_user UNION ALL SELECT 't_100_dict_data', count(*) FROM t_100_dict_data UNION ALL SELECT 't_100_setting_items', count(*) FROM t_100_setting_items;"
```
对照基线(采集于 2026-08-26 12:06):
```
t_100_real_earthquake 17475
t_100_report_record 45544
t_100_influence_field 8456
t_100_assessment_record 2082
t_100_user 123
t_100_setting_items 157
t_100_dict_data 16
```
略多于基线是正常的(导出前源库仍有写入)。**关键是不能少于基线。**
### 第 10 步:改 profile 并打包部署
**在哪:** 先在你的开发机,然后传到麒麟。
**10.1 改 active(开发机)**
编辑 `earthquakeEemergencyGSServerAutoProcess\src\main\resources\application.yml` 第 20 行:
```yaml
spring:
profiles:
active: pro
```
原来是 `prowin`。**这个文件会被打进 jar,所以必须打包前改。**
`application-pro.yml` 不用动,已经核实过里面的配置是对的。
**10.2 打包(开发机)**
```bat
cd /d E:\code\earthquakeEemergencyGS\earthquakeEemergencyGSServerAutoProcess
mvn clean package -DskipTests
```
产物是 `target\earthquakeEemergencyGS-1.0-SNAPSHOT.jar`,约 289 MB。
确认打进去的 profile 是对的:
```bat
cd /d E:\code\earthquakeEemergencyGS\earthquakeEemergencyGSServerAutoProcess\target
jar xf earthquakeEemergencyGS-1.0-SNAPSHOT.jar BOOT-INF/classes/application.yml
powershell -NoProfile -Command "Get-Content -Encoding UTF8 'BOOT-INF\classes\application.yml' | Select-String 'active'"
```
应该看到 `active: pro` 那行**前面没有 `#`**,其余三行(dev / test / prowin)都是注释状态。
这里不要用 `findstr`。该文件里有 UTF-8 中文注释,CMD 按 GBK 解析会报 `FINDSTR: 写入错误` 并把输出截断,看起来像 profile 没改对,其实是控制台编码问题。
算校验和,传输后要比对:
```bat
certutil -hashfile earthquakeEemergencyGS-1.0-SNAPSHOT.jar SHA256
```
**10.3 备份麒麟上的旧 jar(麒麟)**
SSH 登录麒麟 `10.62.210.40`。应用目录是 `/opt/server-jar/ganEarthquake`:
```bash
cd /opt/server-jar/ganEarthquake
ls -lh *.jar
```
这个目录里有九个同名 jar 靠后缀区分,当前跑的是没有后缀的 `earthquakeEemergencyGS-1.0-SNAPSHOT.jar`(1 月 12 日打的)。**先确认它没被动过,再改名。**
旧 jar **原地保留别删**,按目录里现有的命名习惯加个后缀:
```bash
mv earthquakeEemergencyGS-1.0-SNAPSHOT.jar earthquakeEemergencyGS-1.0-SNAPSHOT-bk-20260827.jar
ls -lh *.jar
```
确认改名后目录里已经没有无后缀的那个 jar 了,避免新 jar 传上来时覆盖掉旧的。
**10.4 传新 jar 并校验(麒麟)**
用你惯用的方式传(WinSCP、scp 都行),目标目录 `/opt/server-jar/ganEarthquake`。传完在麒麟上算校验和:
```bash
cd /opt/server-jar/ganEarthquake
sha256sum earthquakeEemergencyGS-1.0-SNAPSHOT.jar
```
**和 10.2 里 `certutil` 输出的值比对,必须完全一致。** 289 MB 的文件传输出错不算罕见,这步别省。
### 第 11 步:启动与验收
**在哪:** 麒麟 `10.62.210.40`。
还是用原来的 `start_gansuEq.sh`,但这次把输出重定向到文件。以前脚本没重定向,日志跟着终端走,断开就没了,出问题查不了:
```bash
cd /opt/server-jar/ganEarthquake
nohup ./start_gansuEq.sh > app_20260827.log 2>&1 &
```
盯着日志看启动过程:
```bash
tail -f app_20260827.log
```
**看这几个关键点:**
```
The following profiles are active: pro profile 对了
HikariPool-1 - Start completed 数据库连上了
Tomcat started on port(s): 2023 端口起来了
Started EarthquakeEmergencyGS in xx seconds 启动成功
```
看到最后一行就是起来了,按 `Ctrl+C` 退出 tail(这只是退出看日志,不会停掉应用,因为是 nohup 起的)。
**如果启动失败,先停脚本再排查。** 脚本是 `while true` 循环,Java 退出后它会等 5 秒再拉一次,日志里会反复出现:
```
Service exited, exit code: 1
Restarting in 5 seconds...
```
看到这个别让它转下去,几分钟就能把日志刷到几百兆。先按 `Ctrl+C` 退出 tail,然后:
```bash
ps -ef | grep -E "earthquake|start_gansuEq" | grep -v grep
kill <脚本的PID>
kill <Java的PID>
```
停干净之后再抓报错。日志已经被重试刷了很多遍,只看第一轮就够:
```bash
grep -i "could not resolve placeholder" app_20260827.log | head -5
grep -i "APPLICATION FAILED TO START" -A 20 app_20260827.log | head -40
```
把输出发我。
**验收检查:**
```bash
ss -lntp | grep 2023
```
端口在监听。然后确认连的是新库:
```bash
grep -i "gansueq" app_20260827.log | head
```
应该能看到 `10.62.210.67:5432/gansueq`,不是 `gansueqwin`。
接下来在浏览器里走一遍业务,这几项最能覆盖风险:
```
1. 登录(验证 t_100_user 数据和 token 配置)
2. 打开地震列表(验证 t_100_real_earthquake,17475 条)
3. 触发一次评估(验证大写表名那条 ST_DWithin 查询、序列自增)
4. 生成一份报告 PDF(验证 SuperMap 出图、Aspose 转 PDF、中文字体)
5. 看地图页面(验证 SuperMap 工作空间数据源指向)
```
第 3 项和第 4 项最关键。第 3 项会同时踩到序列和大写表名两个风险点;第 4 项能验证 SuperMap 和 PDF 字体。
**第 5 项如果地图不出图**,大概率是 SuperMap 工作空间 `.smwu` 里的 PostGIS 数据源还指向旧库。这是已知风险点,处理办法见 `迁移方案.md` 第 8 步。
**记录停机结束时间。**
## 阶段三:出问题时回滚
### 第 12 步:回滚
**什么时候回滚:** 启动失败且十几分钟内定位不到原因,或者核心业务(登录、地震列表、评估)不可用。数据细节问题不用回滚,可以上线后修。
**关键原则:代码和数据库必须一起退。** 新 jar 配旧库、旧 jar 配新库这两种组合都可能起不来。
回滚一共两件事,都很快:
**12.1 数据库改回来(目标机 CMD)**
```bat
set PGBIN=E:\swtools\PostgreSQL\12\bin
set PGPASSWORD=postgres2024
"%PGBIN%\psql" -h 10.62.210.67 -U postgres -d postgres
```
```sql
SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IN ('gansueq','gansueq_old') AND pid <> pg_backend_pid();
ALTER DATABASE gansueq RENAME TO gansueq_new_failed;
ALTER DATABASE gansueq_old RENAME TO gansueq;
\l
\q
```
这样旧库又叫回 `gansueq` 了。**新库改名保留,别删** —— 失败原因还要查。
**12.2 换回旧 jar(麒麟)**
先把看门狗脚本和 Java 都停掉,父进程在前:
```bash
ps -ef | grep -E "earthquake|start_gansuEq" | grep -v grep
kill <脚本的PID>
kill <Java的PID>
ps -ef | grep -E "earthquake|start_gansuEq" | grep -v grep
```
最后一条必须没有输出。脚本还活着的话,你换完 jar 它会自动把新 jar 又拉起来。
然后换回旧 jar:
```bash
cd /opt/server-jar/ganEarthquake
mv earthquakeEemergencyGS-1.0-SNAPSHOT.jar earthquakeEemergencyGS-1.0-SNAPSHOT-new-failed-20260827.jar
mv earthquakeEemergencyGS-1.0-SNAPSHOT-bk-20260827.jar earthquakeEemergencyGS-1.0-SNAPSHOT.jar
ls -lh earthquakeEemergencyGS-1.0-SNAPSHOT.jar
```
确认它是 1 月 12 日那个 290M 的文件,再启动:
```bash
nohup ./start_gansuEq.sh > app_rollback_20260827.log 2>&1 &
tail -f app_rollback_20260827.log
```
旧 jar 里 `active` 是 `prowin`,指向 `10.62.204.32/gansueqwin`(源库),源库一直没动过,所以能起来。
注意这里有个细节:旧 jar 配的是**源库**而不是目标机的库。如果你希望旧 jar 连回目标机那个刚改名回来的 `gansueq`,得用命令行参数覆盖:
这种情况不能用 `start_gansuEq.sh`,脚本里的启动命令是写死的,加不了参数。直接手起,把脚本的 JAVA_OPTS 照抄过来:
```bash
cd /opt/server-jar/ganEarthquake
nohup java -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 -Xms16g -Xmx16g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -jar earthquakeEemergencyGS-1.0-SNAPSHOT.jar --spring.datasource.url=jdbc:postgresql://10.62.210.67:5432/gansueq > app_rollback_20260827.log 2>&1 &
```
这样起来没有自动重启保护,回滚稳定后记得改回脚本方式。
**回滚后确认:**
```bash
grep "profiles are active" app_rollback_20260827.log
ss -lntp | grep 2023
```
然后浏览器里登录一下,确认旧系统恢复。
## 观察期
上线后一周内别删任何东西:
```
目标机 gansueq_old 旧库,随时可回滚
gansueqwin_hist 历史数据,永久保留
E:\db_backup\gansueq_backup.dump 备份文件
E:\migration\full.dump 源库导出
麒麟 earthquakeEemergencyGS-1.0-SNAPSHOT-bk-20260827.jar 旧 jar
app_20260827.log 本次启动日志
源机 gansueqwin 源库,本次只读不改
```
一周后系统稳定,可以清理 `E:\migration\full.dump` 释放空间,其余建议长期保留。
## 命令速查卡
打印出来放手边。
**每开一个新 CMD 窗口先跑这两条:**
```bat
set PGBIN=E:\swtools\PostgreSQL\12\bin
set PGPASSWORD=postgres2024
```
**psql 里常用的元命令:**
```
\l 列出所有库(带体积用 \l+)
\c 库名 切换库
\dt 列出当前库的表
\q 退出
```
**关键数字对照:**
```
源库 gansueqwin 4248 MB public 210 表 mapserver_data 88 表
目标旧库 gansueq 6667 MB
PostGIS 版本 3.4.2(两边一致)
PG 版本 12.17(两边一致)
大写表名 应查到 6 个
```
**三个库名别搞混:**
```
gansueqwin 源机,要迁的
gansueqwin_new 源机,不迁
gansueqwin_hist 目标机,历史数据
```
## 遇到问题怎么问我
把这三样发我,我能最快定位:
```
1. 你执行的命令原文
2. 完整报错信息(别截断)
3. 当前在第几步
```
还原那步如果报错多,直接把 `E:\migration\restore.log` 里过滤后的内容发我。启动失败就发 `app_20260827.log` 里 `APPLICATION FAILED` 往后 20 行。
浙公网安备 33010602011771号