从 Oracle 迁移到 PostgreSQL 无停机:技术详解
从 Oracle 迁移到 PostgreSQL 而无需停止服务是一项具有挑战性但可实现的任务。本文基于一个真实项目,详细分解了一个逐步迁移策略,该项目使用 Spring Boot 微服务,并将对用户零影响作为关键要求。通过 Debezium 实现的 CDC 实现了实时数据同步,避免了停机并将风险降到最低。
准备阶段:分析结构和数据
第一阶段需要对当前数据库进行深入审计。你需要了解数据量、字段类型分布、过时记录的存在,以及 Oracle 特有的结构,这些在 PostgreSQL 中没有直接等价物。清理无关数据可以减少迁移量并加速进程。
一个关键方面是数据类型映射。例如:
NUMBER(1,0)→BOOLEANVARCHAR2→TEXT或VARCHARDATE→TIMESTAMP WITHOUT TIME ZONECLOB→TEXT
用于分析,我们使用了这个 SQL 查询:
select distinct DATA_TYPE
from ALL_TAB_COLUMNS
where owner = 'schemat'
order by DATA_TYPE
基于结果,我们创建了一个数据类型映射表,这成为后续所有转换的基础。同样重要的是要考虑 NULL 和空字符串的处理差异:在 Oracle 中,空字符串被视为 NULL,但在 PostgreSQL 中不是。这需要调整业务逻辑和查询。
模式适配和 Liquibase
简单替换数据库驱动就会导致错误。迁移脚本必须针对 PostgreSQL 进行适配。为此:
- 通过 Liquibase 上下文分离 DDL 和 DML 操作——临时禁用 DML。
- 迭代测试脚本:每次更改后运行变更日志,然后进行清理和烟雾测试。
- 验证前提条件的正确性,并确保与 Oracle 的模式一致性。
序列需要特别注意。如果它们不同步,新 ID 可能会与历史数据冲突。解决方案:
- 应用启动时,检查 PostgreSQL 中的当前序列值。
- 如果它低于 Oracle 中的值,则向前推进并留出一些缓冲。
- 该逻辑是幂等的:重复运行不会导致失败。
结果,目标 PostgreSQL 模式在一个拉取请求中组装完成,准备好填充数据。
应用代码调整
在成功针对空 PostgreSQL 启动应用后,Java 层面的代码错误会浮出水面。主要变更:
- 将
@Lob注解替换为@Column(columnDefinition = "text")。 - 在原生 SQL 查询中处理 NULL,特别是
IN (...)结构。 - 从数字标志 (0/1) 切换到
boolean。 - 调整字符串比较逻辑,考虑 Oracle/PostgreSQL 的差异。
- 为 PostgreSQL 方言重写原生查询。
测试在单元测试和集成场景级别进行。特别关注边缘情况:空字符串、NULL 值、大文本字段。
通过 Debezium 和 Kafka Connect 实现 CDC
我们选择了经典技术栈:Debezium + Kafka Connect。架构:
- 源连接器 (Debezium Oracle) 读取重做日志并生成 CDC 事件。
- 事件发送到 Kafka(每个表一个主题)。
- 接收连接器 (JDBC Sink) 按相同顺序将变更应用到 PostgreSQL。
设置阶段:
- 初始快照——启动时的全量数据复制。
- 流式变更——从重做日志连续复制 INSERT/UPDATE/DELETE。
- 顺序保持——通过 LogMiner 保证变更序列。
使用的镜像:
quay.io/debezium/kafka:3.0quay.io/debezium/zookeeper:3.0quay.io/debezium/connect:3.0provectuslabs/kafka-ui:0.7.2(用于监控)
设置通过 REST API 使用 curl 完成。迁移期间 Debezium UI 不稳定,因此配置以 JSON 格式手动传递。
示例源连接器配置:
{
"name": "oracle-connector",
"config": {
"connector.class": "io.debezium.connector.oracle.OracleConnector",
"database.hostname": "oracle-host",
"database.port": "1521",
"database.user": "user",
"database.password": "password",
"database.dbname": "ORCLCDB",
"database.server.name": "server1",
"table.include.list": "schema.table1,schema.table2",
"database.history.kafka.bootstrap.servers": "kafka:9092",
"database.history.kafka.topic": "schema-changes"
}
}
示例接收连接器配置:
{
"name": "postgres-sink",
"config": {
"connector.class": "io.confluent.connect.jdbc.JdbcSinkConnector",
"connection.url": "jdbc:postgresql://postgres:5432/db",
"connection.user": "user",
"connection.password": "password",
"topics": "server1.schema.table1,server1.schema.table2",
"auto.create": "false",
"auto.evolve": "false",
"insert.mode": "upsert",
"pk.mode": "record_key",
"pk.fields": "id"
}
}
有些表(例如具有复合键的表)需要单独配置。每个连接器单独测试。
最终切换和质量控制
完成初始快照并稳定流式传输后:
- 启动连接到 PostgreSQL 的服务测试实例。
- 执行端到端测试:API、UI、后台进程。
- 在类似生产的数据上进行负载测试。
- 在负载最小时刻,切换 DNS 或入口路由到新实例。
- 监控 24–48 小时的日志、指标和警报。
回滚仅在切换点之前可能。之后,系统完全运行在 PostgreSQL 上。保留 Oracle 备份以用于紧急数据恢复。
关键经验教训
- 没有通用的解决方案——每个项目都需要自定义适配。
- 无双写 CDC——几秒延迟的可接受折衷。
- 序列同步——防止 ID 冲突的必需步骤。
- 每个阶段测试——稳定迁移的关键。
- Debezium + Kafka Connect——在线迁移的灵活可靠技术栈。
迁移过程包括准备在内共耗时 18 小时,但实际用户停机时间为 0 秒。该项目已在 PostgreSQL 上成功运行一年多,没有任何退化。
— Editorial Team
暂无评论。