如何在香港服务器中实现 Oracle 数据库从 Windows 迁移到 Ubuntu,避免数据丢失与停机?

第一次去香港荃湾的机房是上周2的凌晨两点,冷气口像在耳边低语,机柜门“啪”地一声合上,我在KVM前看着那台跑了七年的 Windows Server 上的 Oracle 实例,心里只剩一句话:这次迁到 Ubuntu,不能丢数据,也不能耽误业务。
项目经理把奶茶递给我:“能不停机最好。”我笑了笑,“我尽量做到几分钟级的切换。”这篇文章就是那一夜(其实是三夜)的全过程:怎么选方案、怎么搭环境、怎么一步步把 Windows 上的 Oracle 挪到 Ubuntu 上,同时把风险压扁、把停机时间卡死在最短的窗口里。
关键信息 & 风险声明(看这段能决定你要不要继续)
- 目标:将 Oracle 数据库(源:Windows) 迁移到 Ubuntu 的香港服务器上,数据零丢失、停机最小化。
- 现实情况:Oracle 官方支持重点在 Oracle Linux / RHEL / SLES。Ubuntu 不是官方支持的数据库宿主操作系统。
- 我的做法(强烈推荐):在 Ubuntu 宿主机 上使用 Oracle 官方容器镜像(基于 Oracle Linux) 跑 Oracle Database。这样既能满足“目标是 Ubuntu”,又不违背“数据库运行环境得可支持/可维护”的底线。
- 停机目标:按本文方案(可重复增量同步 + 一次短暂停机切换),实际项目把业务只读窗口+DNS切换控制在 3–12 分钟。你的数据库规模、对象类型与网络传输带宽会影响这个数字。
适用版本:下文以 Oracle 19c 为例(长期支持,生产更稳),源端可以是 11gR2/12c/18c/19c(跨平台注意端序/块大小/字符集等检查)。
1. 我们的现场:硬件、网络与目标形态
1.1 香港服务器(目标端)硬件与系统参数
| 项目 | 配置(实际项目参考) |
|---|---|
| 机房 | HK(10Gbps 国际出口,低时延回大陆) |
| 宿主机 OS | Ubuntu 22.04 LTS(裸金属) |
| CPU | AMD EPYC 7413(24C/48T),单路 |
| 内存 | 256GB ECC |
| 系统盘 | 2 × SATA SSD(RAID1,系统/日志) |
| 数据盘 | 4 × NVMe U.2 3.84TB(mdadm RAID10,或 ZFS RAID10) |
| 网卡 | 2 × 10GbE(bond,LACP) |
| 时钟 | chrony 同步(与 Stratum1/2) |
| 容器 | Docker / Podman |
| 文件系统 | XFS(数据卷),noatime,I/O 调度 none(NVMe) |
| 监控 | node_exporter + oracledb_exporter + Prometheus + Grafana |
| 备份 | 近端磁盘 + 远端对象存储(每日全量/增量策略) |
为何容器:Ubuntu 不是 Oracle DB 的官方支持宿主。用官方 Oracle Linux 基镜像的容器在 Ubuntu 跑,镜像里是 Oracle 支持的用户空间,既能发挥 Ubuntu 宿主的生态便利,也能拿到 Oracle 的已知可运行栈。
1.2 源端(Windows)参数
| 项目 | 配置(示例) |
|---|---|
| OS | Windows Server 2016 Standard |
| Oracle | 12.2.0.1 单实例(文件系统) |
| 数据量 | 1.8 TB(OLTP + 报表混合,峰值 12k TPS) |
| 字符集 | AL32UTF8 |
| DB Block Size | 8KB |
| 存储 | 本地 SSD,周末离峰期可到 1.2GB/s 顺序读 |
2. 迁移路线怎么选?(三种方案对比)
| 方案 | 停机 | 难度 | 许可/成本 | 适用场景 |
|---|---|---|---|---|
| Data Pump(expdp/impdp)全量逻辑迁移 | 高(小时级〜天) | 低 | 低 | 数据量小(<500GB)或能接受长停机 |
| 可传输表空间(TTS)+ RMAN 增量滚动(本文详解) | 低(分钟级〜十来分钟) | 中 | 低 | 大库、跨平台、可接受短窗口 |
| GoldenGate 双活 | 极低(秒级) | 高 | 高(需许可) | 对停机极敏感的核心库 |
我最终选了 TTS + RMAN 增量滚动:稳定、成本可控、停机短。
思路是:先做一次“基线拷贝”,之后周期性增量把源端变更“滚动”到目标;切换窗口把源端表空间只读,打最后一次增量并应用到目标,然后导入元数据、开放读写。
3. 迁移前的健康检查 & 演练(别省)
3.1 跨平台可迁性检查(端序、平台、块大小)
在源端(Windows)检查:
-- 端序/平台
SELECT d.PLATFORM_NAME, d.ENDIAN_FORMAT
FROM V$TRANSPORTABLE_PLATFORM d
WHERE UPPER(d.PLATFORM_NAME) LIKE '%LINUX%';
-- 数据库块大小(需与目标兼容,TTS 要一致)
SHOW PARAMETER db_block_size;
-- 字符集与国家字符集(避免导入后乱码)
SELECT parameter, value FROM NLS_DATABASE_PARAMETERS
WHERE parameter IN ('NLS_CHARACTERSET','NLS_NCHAR_CHARACTERSET');
-- 自包含性检查(可传输表空间要自包含)
EXECUTE DBMS_TTS.TRANSPORT_SET_CHECK('USERS,DATA,INDEX,...', TRUE);
SELECT * FROM TRANSPORT_SET_VIOLATIONS;
要点:
- Windows x86_64 与 Linux x86_64 端序一致(Little Endian),TTS 可行。
- db_block_size 多为 8KB,目标必须一致。
- 对象自包含是 TTS 的红线,出现 TRANSPORT_SET_VIOLATIONS 必须清理(跨表空间依赖、分区子表、物化视图日志等)。
3.2 性能基线(迁前有数,迁后好对比)
用 AWR/Statspack 拉 24h 的基线,记录如下指标(摘录):
| 指标 | 峰值 | 平均 | 备注 |
|---|---|---|---|
| TPS | 12,100 | 3,800 | 峰值来自报表并发 |
| 逻辑读(blocks/s) | 4.2M | 1.1M | |
| 平均 SQL 响应(ms) | 19 | 8 | 95p |
| 存储 IOPS(读/写) | 210k / 85k | 55k / 22k | FIO 预估上限更高 |
| 网卡吞吐(Gbps) | 6.5 | 1.8 | 备份/同步窗口更高 |
4. 在 Ubuntu 宿主搭好“可支持”的 Oracle 目标端
4.1 宿主内核与资源(Ubuntu)
# HugePages (示例按 SGA 128G 计算,8G 预留给 OS 与容器外服务)
echo 'vm.nr_hugepages=65536' | sudo tee /etc/sysctl.d/99-hugepages.conf
# NVMe I/O 调度与挂载
sudo nvme list
# udev 覆盖建议设为 none(多数新内核 NVMe 默认 none)
# XFS 推荐 noatime
echo 'UUID=<data-vol-uuid> /u02 xfs defaults,noatime 0 2' | sudo tee -a /etc/fstab
# ulimit(给 docker 守护 & oracle 容器)
echo '* soft nofile 1048576' | sudo tee -a /etc/security/limits.conf
echo '* hard nofile 1048576' | sudo tee -a /etc/security/limits.conf
4.2 拉取 Oracle 19c 容器并准备目录
注意:生产请使用 Oracle 官方镜像(需登录容器仓库),本文示例用占位名称。
# 数据与归档目录(映射为持久卷)
sudo mkdir -p /u02/oradata /u02/fast_recovery_area /u02/backup /u02/admin
sudo chown -R 54321:54321 /u02 # 让容器内 oracle 用户(54321)有权限
# 拉镜像(示例)
docker pull container-registry.oracle.com/database/enterprise:19.22.0
# 运行(示例 compose)
cat > docker-compose.yml <<'YAML'
services:
orcl19c:
image: container-registry.oracle.com/database/enterprise:19.22.0
container_name: orcl19c
environment:
- ORACLE_SID=ORCL
- ORACLE_PDB=ORCLPDB1
- ORACLE_PWD=Str0ng-Oracle!
- ENABLE_ARCHIVELOG=true
- MEMORY_TARGET=150G
ulimits:
nofile:
soft: 1048576
hard: 1048576
volumes:
- /u02/oradata:/opt/oracle/oradata
- /u02/fast_recovery_area:/opt/oracle/fast_recovery_area
- /u02/backup:/backup
- /u02/admin:/opt/oracle/admin
network_mode: host # 或独立网段绑定 1521/5500 端口
restart: always
YAML
docker compose up -d
4.3 监听/网络与目录对象
容器起来后(首次需几分钟初始化),在容器中确认:
docker exec -it orcl19c bash
lsnrctl status
sqlplus / as sysdba
-- 创建数据泵目录(用于导入元数据)
CREATE OR REPLACE DIRECTORY dpdir AS '/backup/dpdir';
GRANT READ, WRITE ON DIRECTORY dpdir TO system;
5. 迁移流程(TTS + RMAN 增量滚动)——核心步骤
核心思想:基线 + 多轮增量,最后短暂停机只读切换,导入元数据,开库读写。
5.1 规划与“干跑”(Dry Run)
在源端复制一套子集(比如 200GB 的典型业务 schema),按下面的流程先走完一遍,验证脚本、参数、带宽与时间估算。
记录每一步实际耗时,修正切换窗口计划与回滚预案。
5.2 基线数据(Level 0)
源端(Windows)——挑一个业务低谷点,不需要停写:
# 以可传输表空间为单位做 Level 0
rman target /
RUN {
ALLOCATE CHANNEL c1 DEVICE TYPE DISK;
BACKUP INCREMENTAL LEVEL=0
FORMAT 'E:\rman_tts\L0_%U.bkp'
TABLESPACE USERS, DATA, INDEX;
RELEASE CHANNEL c1;
}
把 L0 备份文件通过专线/加密隧道传输到目标 /u02/backup/L0/。
目标端(Ubuntu/容器)——转换与恢复到“滚动库”:
rman target /
RUN {
-- 指定转换平台(Linux x86-64)
CONVERT FROM PLATFORM 'Microsoft Windows x86 64-bit'
DATAFILECOPY '/u02/backup/L0_*.bkp'
TO PLATFORM 'Linux x86 64-bit'
DB_FILE_NAME_CONVERT
'E:\ORADATA\ORCL\', '/opt/oracle/oradata/ORCL/';
}
-- 恢复到滚动表空间(aux 方式,或直接将文件放到目标库的数据目录)
说明:也可以采用 RMAN CONVERT TABLESPACE 或 CONVERT DATAFILE 的写法,关键是把 Windows 格式的数据文件转换为 Linux 可用格式。
5.3 周期性增量(Level 1 滚动)
源端(业务照常读写,每隔 30–60 分钟来一轮,视变更量而定):
RUN {
ALLOCATE CHANNEL c1 DEVICE TYPE DISK;
BACKUP INCREMENTAL LEVEL=1
FORMAT 'E:\rman_tts\L1_%d_%T_%U.bkp'
TABLESPACE USERS, DATA, INDEX;
RELEASE CHANNEL c1;
}
传到目标 /u02/backup/L1/ 后,目标端应用增量:
RUN {
-- 将增量应用到已转换的数据文件上(ROLL FORWARD)
RECOVER FROM '/u02/backup/L1' TABLESPACE USERS, DATA, INDEX;
}
这一步反复执行,直到切换前一刻。每次增量都在缩小最终停机窗口。
5.4 切换窗口(只读 + 最后一增 + 元数据导入)
我们把切换安排在 HKT 02:00,提前把 DNS TTL 调到 60s。
源端(进入只读):
ALTER TABLESPACE USERS READ ONLY;
ALTER TABLESPACE DATA READ ONLY;
ALTER TABLESPACE INDEX READ ONLY;
-- 视情况停写关键队列/任务,或让应用进入只读模式
源端再打一轮 Level 1:
RUN {
BACKUP INCREMENTAL LEVEL=1
FORMAT 'E:\rman_tts\L1_FINAL_%U.bkp'
TABLESPACE USERS, DATA, INDEX;
}
传到目标,目标端应用最后一次增量:
RUN {
RECOVER FROM '/u02/backup/L1' TABLESPACE USERS, DATA, INDEX;
}
导出源端元数据(只导元数据,速度快):
# Windows 上生成 parfile,例如 C:\exp_tts.par
# DUMPFILE 放共享目录,或先落本地再传
exp_tts.par 示例(只导出 TTS 的元数据):
USERID='/ as sysdba'
TRANSPORT_TABLESPACES=USERS,DATA,INDEX
TRANSPORT_FULL_CHECK=Y
CONTENT=METADATA_ONLY
DIRECTORY=DPDIR
DUMPFILE=tts_meta_%U.dmp
LOGFILE=tts_meta.log
EXCLUDE=STATISTICS
expdp parfile=C:\exp_tts.par
把 tts_meta_*.dmp 拷到目标 /u02/backup/dpdir/。
目标端导入元数据:
-- 确认目标库已将相应数据文件挂载(路径在容器 /opt/oracle/oradata/...)
-- 确保有同名/映射的表空间(或使用 remap)
impdp system/Str0ng-Oracle@localhost:1521/ORCLPDB1 \
directory=dpdir \
dumpfile=tts_meta_01.dmp,tts_meta_02.dmp \
logfile=imp_tts.log \
transport_datafiles="
/opt/oracle/oradata/ORCL/users01.dbf,
/opt/oracle/oradata/ORCL/data01.dbf,
/opt/oracle/oradata/ORCL/index01.dbf"
导入成功后:
-- 把表空间切回读写
ALTER TABLESPACE USERS READ WRITE;
ALTER TABLESPACE DATA READ WRITE;
ALTER TABLESPACE INDEX READ WRITE;
-- 处理序列(可能要重置至当前最大值)
-- 重新编译无效对象
@?/rdbms/admin/utlrp.sql
-- 统计信息
EXEC DBMS_STATS.GATHER_DATABASE_STATS(estimate_percent=>DBMS_STATS.AUTO_SAMPLE_SIZE, cascade=>TRUE);
切流量:
切换应用连接串(或改 DNS / VIP),监测连接成功与业务指标恢复。
保留源端只读一段时间作为回退点(比如 1–2 小时)。
6. 实操脚本清单(可直接改名套用)
6.1 RMAN(Windows 源)Level 1 增量(批处理)
:: rman_l1.bat
set ORACLE_SID=ORCL
set NLS_DATE_FORMAT=YYYYMMDD_HH24MISS
rman target / log=E:\rman_tts\logs\l1_%date%_%time%.log append <<RMAN
RUN {
ALLOCATE CHANNEL c1 DEVICE TYPE DISK;
BACKUP INCREMENTAL LEVEL=1
FORMAT 'E:\rman_tts\L1_%d_%T_%U.bkp'
TABLESPACE USERS, DATA, INDEX;
RELEASE CHANNEL c1;
}
EXIT
RMAN
6.2 目标端应用增量(Linux)
# apply_l1.sh
rman target / <<'RMAN'
RUN {
CATALOG START WITH '/u02/backup/L1';
RECOVER TABLESPACE USERS, DATA, INDEX;
}
RMAN
6.3 数据泵元数据导入(Linux)
impdp '"sys/Str0ng-Oracle! as sysdba"' \
directory=dpdir \
dumpfile=tts_meta_%U.dmp \
logfile=imp_tts.log \
transport_datafiles="/opt/oracle/oradata/ORCL/users01.dbf,/opt/oracle/oradata/ORCL/data01.dbf,/opt/oracle/oradata/ORCL/index01.dbf"
7. 联调、验收与回滚策略
7.1 验收清单(摘录)
- 会话可连接、主要业务路径 CRUD 正常
- 序列不回退(关键表 select max(id) 与 sequence.nextval 比对)
- 物化视图刷新 OK;作业(DBMS_SCHEDULER)在新库按时跑
- AWR 对比关键 SQL 的执行计划一致(或更优)
- 延迟写、提交响应、锁等待指标无异常尖峰
- 备份策略在目标端已生效(全量/增量/归档清理)
7.2 回滚预案(窗口内极简)
回滚触发条件:导入失败、关键 SQL 性能不可接受、应用连接异常 > 5 分钟。
回滚操作:
- DNS/VIP 指回源端;
- 源端表空间改回读写:ALTER TABLESPACE ... READ WRITE;
- 源端继续提供服务;
- 复盘问题,修正后择期再切。
8. 现场“坑”与解决(真遇到的)
容器内 oracle 用户 UID 与宿主卷权限不一致
现象:导入/归档写入报 ORA-27040 权限错误。
处理:chown -R 54321:54321 /u02/*,并固定 compose 中的 user: "54321:54321"(如镜像支持)。
TRANSPORT_SET_VIOLATIONS 报跨表空间依赖
现象:部分分区索引、MV 日志跨到了 SYSTEM/UNDOTBS。
处理:重建到目标 TTS 范围内的表空间,或在切换前拆分对象、清理垃圾对象。
NLS 时区文件版本不一致
现象:时间函数/作业异常。
处理:对齐 $ORACLE_HOME/oracore/zoneinfo 版本,执行 ALTER DATABASE SET TIME_ZONE(如需),并重启。
XFS mount 忘了 noatime
现象:大量小读时 iowait 偶发升高。
处理:改挂载参数,配合 NVMe none 调度,降低元数据写放大。
RMAN 转换吞吐不达标
处理:开启 多通道并行 与 多段备份:
RUN {
ALLOCATE CHANNEL c1 DEVICE TYPE DISK MAXPIECESIZE 64G;
ALLOCATE CHANNEL c2 DEVICE TYPE DISK MAXPIECESIZE 64G;
BACKUP AS COPY INCREMENTAL LEVEL=1
SECTION SIZE 32G
TABLESPACE USERS,DATA,INDEX;
}
应用端连接串缓存
现象:应用容器没有刷新 DNS。
处理:用 VIP/Keepalived 或中间层(HAProxy/MaxScale 类)统一出口,避免客户端 DNS 缓存不可控。
9. 迁前后关键指标对比(实测样例)
| 指标 | 迁前(Windows 源) | 迁后(Ubuntu 宿主 + Oracle 容器) | 变化 |
|---|---|---|---|
| 主库峰值 TPS | 12,100 | 12,800 | +5.8% |
| 平均 SQL 响应 p95 | 19ms | 16ms | -15.7% |
| 存储顺序读(GB/s) | 1.2 | 2.3 | +91%(NVMe RAID10) |
| 写入 IOPS | 85k | 120k | +41% |
| 切换停机(可见层) | 11 分钟 | — | 达成目标 |
这些提升大多来自 NVMe 带宽/并发、I/O 调度优化 与 容器镜像的用户空间匹配(Oracle Linux)。
10. FAQ:你可能会问
一定要容器吗?
不硬性,但在 Ubuntu 上容器是兼顾“目标是 Ubuntu”和“Oracle 可运行栈”的最稳妥方案。纯原生在 Ubuntu 上装 Oracle 可行性差、维护风险高。
能做到零停机吗?
真正意义的“零”需要 GoldenGate 级别的双活/级联切换与许可证预算。本文 TTS+增量能把窗口压到几分钟,对多数业务已足够。
非 CDB → CDB/PDB 怎么办?
可以在目标端建 CDB,采用 TTS 到 PDB 中,或考虑 跨平台可传输 PDB(视版本而定)。逻辑要点相同:自包含、元数据导入、读写切换。
11. 完整执行顺序(Checklist 版)
- 规划对象范围(表空间级),清理跨依赖,自包含检查通过。
- 建立目标端:Ubuntu 宿主 → Oracle 容器,调内核/IO/ulimit。
- Dry Run(子集库)验证脚本与耗时。
- Level 0 基线备份 → 传输 → 转换/恢复。
- 多轮 Level 1 增量滚动,验证可应用性与耗时。
- 切换前 48h:DNS TTL 调小;冻结变更窗口;通知业务。
- 切换:源端表空间只读 → 最后一轮增量 → 目标导入元数据 → 表空间读写。
- 切流量(DNS/VIP),压测 & 验收清单全绿。
- 监控观察期,保留源端只读回退点 1–2 小时。
- 收尾:统计信息、备份策略切换、AWR 基线与文档归档。
12. 结尾:从机房出来,天色刚亮
那天清晨,机房走廊尽头的窗子透了点灰蓝色的光。
我把最后一个勾打在验收清单上,应用的交易曲线在 Grafana 上恢复成熟悉的波形。项目经理在门口问我:“就这?”
我说:“就这。数据一行没丢,业务停了 11 分钟。”
迁移不是魔法,靠的是提前演练、脚本化和细节不放过。你按这篇文章的顺序走下来,哪怕机房风再大,心也能定住。
附:风险提示与建议
生产环境请优先考虑 Oracle 19c(LTS)或官方支持的版本组合。
若业务对停机极端敏感且预算允许,评估 GoldenGate。
不要跳过 Dry Run;不要忽略 TRANSPORT_SET_VIOLATIONS;不要相信“最后再补统计信息”这种侥幸。
把 回滚预案写成脚本,像做一次“逆迁移演练”。