Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,8 @@
** xref:master/getting-started/daily_maintenance.adoc[日常维护]
** xref:master/operation_guide.adoc[运维管理指南]
* 数据迁移
** xref:master/migration_guide.adoc[迁移指南]
** xref:master/migration_guide.adoc[迁移指南]
** xref:master/pg_upgrade_guide.adoc[PostgreSQL 升级指南]
* IvorySQL开发者
** xref:master/contribution/contribution_guide.adoc[贡献指南]
** xref:master/developer_guide.adoc[开发者指南]
Expand Down
333 changes: 333 additions & 0 deletions CN/modules/ROOT/pages/master/pg_upgrade_guide.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,333 @@
:sectnums:
:sectnumlevels: 5

= PostgreSQL 至 IvorySQL 升级指南

== 概述

IvorySQL 基于 PostgreSQL 最新内核构建,并在此基础上提供 Oracle 兼容能力。对于已经在使用 PostgreSQL 的业务,可以按照本指南将数据平滑升级到 IvorySQL,并按需启用 Oracle 兼容模式。

本文介绍三种逻辑迁移路径,并给出升级前后的检查清单:

[cols="2,2,3,2"]
|===
|路径 |适用场景 |优点 |限制

|pg_dump / pg_restore 逻辑迁移
|跨版本、跨平台迁移,数据量中小
|最安全,兼容性最好,支持版本跨度大
|停机窗口取决于导出导入耗时

|pg_dumpall 全库迁移
|整实例(含全局对象)一次性搬迁
|保留角色、表空间等全局对象
|同样需要停机窗口

|逻辑复制
|追求最小停机时间的在线迁移
|源库持续可用,可随时切换
|需 PostgreSQL 10+,配置相对复杂
|===

[NOTE]
如果希望采用物理升级(就地升级、停机时间更短),IvorySQL 也支持使用 `pg_upgrade` 将原生 PostgreSQL 集群升级到 IvorySQL。该路径必须使用**目标 IvorySQL 版本自带的** `pg_upgrade`,并指定 `-g`(`--using-ora-pg`)参数以表示源端为原生 PostgreSQL。升级前应先执行兼容性检查(`-c`),升级后按业务需要完成验证(统计信息、扩展、Oracle 兼容行为等)。具体步骤、参数与注意事项请参阅 xref:master/operation_guide.adoc[运维管理指南]。

本文聚焦上述三种逻辑迁移路径,适用于跨版本、跨平台以及需要精细控制迁移对象范围的场景;物理升级路径请以运维管理指南为准。

== 升级前准备

=== 确认 IvorySQL 与 PostgreSQL 版本

IvorySQL 5.x 基于 PostgreSQL 18。可通过以下命令确认两端版本:

[source,shell]
----
# 源端 PostgreSQL
psql -U postgres -c "SELECT version();"

# 目标端 IvorySQL
psql -U postgres -p 5432 -c "SELECT version();"
----

=== 安装 IvorySQL

参考 xref:master/installation_guide.adoc[安装指南] 完成安装。初始化实例时,可按业务需要选择兼容模式与大小写不敏感(CI)选项:

[source,shell]
----
/usr/local/ivorysql/ivorysql-5/bin/initdb -D /data/ivorysql -U postgres
----

初始化完成后,确认双端口监听与各自的兼容模式:

[source,shell]
----
psql -U postgres -p 5432 -c "SHOW ivorysql.compatible_mode;"
psql -U postgres -p 1521 -c "SHOW ivorysql.compatible_mode;"
----

IvorySQL 同一实例同时监听 PostgreSQL 模式端口(5432)与 Oracle 兼容模式端口(1521)。兼容行为由**连接端口**决定:`ivorysql.compatible_mode` 由系统根据端口自动设置,不允许在 `ivorysql.conf` 中手工配置。升级与校验阶段先通过 5432 端口验证 PostgreSQL 行为,割接后再切换到 1521 端口使用 Oracle 兼容特性。

=== 源端检查清单

* 记录源库版本、`encoding`、`lc_collate`/`lc_ctype`,目标端 initdb 时保持一致。
* 使用 `pg_dump --schema-only` 提前演练建表过程,标记依赖 Oracle 兼容特性才可创建的对象。
* 检查是否存在使用 `EMPTY_STRING` 与 `NULL` 语义差异的数据:PostgreSQL 中空字符串 `''` 与 `NULL` 不同,而 IvorySQL 的 Oracle 兼容模式遵循 Oracle 语义,将空字符串视为 `NULL`。涉及此语义的查询结果会发生变化,需要业务侧评估。
* 统计大表行数,作为迁移后的数据校验基准。

== 路径一:pg_dump / pg_restore 逻辑迁移

=== 迁移全局对象与表空间(先于数据恢复)

`pg_dump` 不包含角色与表空间,目标端必须先就绪这些全局对象,再恢复数据:使用自定义表空间的表在恢复时要求对应表空间已存在,否则 `pg_restore` 直接失败。

[source,shell]
----
# 源端导出全局对象
pg_dumpall -U postgres --globals-only -f globals.sql

# 目标端导入前先编辑 globals.sql:
# 1) 去除超级用户相关语句
# 2) 核对 CREATE TABLESPACE 语句中的目录路径,并提前在 IvorySQL 主机上创建这些目录
# 3) 如需跳过已存在的角色等冲突,可删除对应 CREATE ROLE 语句
# (-v ON_ERROR_STOP=1 遇错即停,确保表空间等关键对象创建成功)
psql -U postgres -p 5432 -v ON_ERROR_STOP=1 -f globals.sql
----

[NOTE]
`psql -f` 默认在遇到 SQL 错误时不会中止执行,而是继续处理后续语句并可能返回退出码 0。若表空间目录缺失,`CREATE TABLESPACE` 会报错但脚本继续执行,最终表可能被创建到默认表空间而非预期位置。使用 `-v ON_ERROR_STOP=1` 可在遇到首个错误时立即停止,便于发现并修复问题。

若计划将所有表恢复到默认表空间(不保留源端表空间布局),可跳过表空间相关检查,并在下一步恢复时使用 `--no-tablespaces`。

=== 导出源库

[IMPORTANT]
逻辑导出基于某一时刻的一致性快照。执行最终导出前必须**停止源端业务写入**,并保持停止直到数据校验完成、业务切换到 IvorySQL;否则导出快照之后的写入不会包含在迁移数据中。

在源端 PostgreSQL 上导出指定数据库(建议使用与源库同大版本的 `pg_dump`):

[source,shell]
----
pg_dump -U postgres -Fc -d appdb -f appdb.dump
----

如需排除某些对象,可使用 `-N`/`-n` 与 `-T`/`-t` 选项精细控制。

=== 恢复到 IvorySQL

将转储文件恢复到 IvorySQL 实例(通过 5432 端口,保持 PostgreSQL 行为):

[source,shell]
----
# 先创建目标数据库
psql -U postgres -p 5432 -c "CREATE DATABASE appdb;"

# 恢复数据(默认将所有对象放入默认表空间)
pg_restore -U postgres -p 5432 -d appdb --no-owner --no-privileges --no-tablespaces appdb.dump
----

`--no-owner` 与 `--no-privileges` 用于跳过源端的角色与授权信息;如目标端已重建同名角色,可去掉这两个选项。`--no-tablespaces` 忽略转储中的表空间设置,将所有对象放入默认表空间;如需保留源端表空间布局,请确认前置步骤中的表空间已在目标端创建,再省略该选项。

== 路径二:pg_dumpall 全库迁移

如果源实例规模不大,或需要连同全局对象一起搬迁,可直接使用 `pg_dumpall`:

[source,shell]
----
# 源端全库导出(纯 SQL 文本)
pg_dumpall -U postgres -f full_cluster.sql
----

[IMPORTANT]
最终导出前必须停止源端业务写入,并保持停止直到割接完成;导出快照之后的写入不会包含在转储文件中。

=== 导入前处理角色冲突

`pg_dumpall` 的转储文件开头是全局对象定义,其中每个角色都会生成一条 `CREATE ROLE` 语句,包括源端的 `postgres` 角色。IvorySQL 目标实例在 `initdb` 时已经创建了 `postgres` 角色,因此直接导入会在这条语句处失败:

[literal]
----
ERROR: role "postgres" already exists
----

配合 `-v ON_ERROR_STOP=1` 时,`psql` 会在该错误处立即退出(退出码 3),后续的建库与数据恢复均不会执行。导入前请先检查转储文件,并删除或注释掉目标端已存在角色的 `CREATE ROLE` 语句:

[source,shell]
----
# 1. 列出转储中的全部角色创建语句(位于文件开头的全局对象部分)
grep -n '^CREATE ROLE' full_cluster.sql

# 2. 列出目标端已存在的角色
psql -U postgres -p 5432 -c '\du'

# 3. 对比上述结果,删除或注释目标端已有角色对应的 CREATE ROLE 行
# 例如目标端必然存在 postgres 角色:
sed -i 's/^CREATE ROLE postgres;/-- CREATE ROLE postgres;/' full_cluster.sql
----

只需处理冲突的 `CREATE ROLE` 语句:紧随其后的 `ALTER ROLE` 仍会执行,用于对齐角色属性,请保留。若目标端已存在多个源端角色,按同样方式逐一注释。

=== 导入

[source,shell]
----
# 目标端导入(-v ON_ERROR_STOP=1 遇错即停,避免错误被静默跳过)
psql -U postgres -p 5432 -v ON_ERROR_STOP=1 -f full_cluster.sql
----

[NOTE]
`pg_dumpall` 生成的纯文本 SQL 不支持并行恢复,大实例建议优先使用路径一。若源端使用了自定义表空间,请先在 IvorySQL 主机创建对应目录。`psql -f` 默认遇错不中止,`CREATE TABLESPACE` 失败后脚本会继续,相关表最终可能落在默认表空间而非预期位置;`-v ON_ERROR_STOP=1` 会让此类错误立即暴露,请在完成上述角色冲突处理后保留该选项。

== 路径三:逻辑复制在线迁移

对于不允许长时间停机的业务,可以使用逻辑复制保持源库与 IvorySQL 的数据同步,最后短暂切换。

=== 源端配置

在源端 PostgreSQL 的 `postgresql.conf` 中调整参数并重启:

[source,ini]
----
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
----

确认 `pg_hba.conf` 允许 IvorySQL 所在主机以复制方式连接。

=== 目标端预建结构

在 IvorySQL 上预先创建与源端一致的表结构(可由 `pg_dump --schema-only` 生成)。建议目标端表与源端保持相同的复制标识设置,以提升增量应用的执行效率。

=== 源端设置复制标识

逻辑复制由 publisher 端按表的复制标识查找待复制的行:无主键的表默认没有复制标识,其 UPDATE/DELETE 无法发布。请在源端为所有无主键的待复制表设置复制标识:

[source,sql]
----
-- 有主键的表无需额外操作(主键即复制标识)
-- 无主键的表在源端设置 REPLICA IDENTITY FULL
ALTER TABLE app_table REPLICA IDENTITY FULL;

-- 检查源端缺少复制标识的用户表(relreplident = 'd' 且无主键)
SELECT n.nspname AS schema_name, c.relname AS table_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND c.relreplident = 'd'
AND NOT EXISTS (
SELECT 1 FROM pg_index i
WHERE i.indrelid = c.oid AND i.indisprimary
)
AND n.nspname NOT IN ('pg_catalog', 'information_schema');
----

=== 创建发布与订阅

[source,sql]
----
-- 源端创建发布
CREATE PUBLICATION app_pub FOR ALL TABLES;

-- IvorySQL 端创建订阅
CREATE SUBSCRIPTION app_sub
CONNECTION 'host=src_host port=5432 dbname=appdb user=replica password=***'
PUBLICATION app_pub;
----

订阅创建后,IvorySQL 会先完成全量同步,随后持续应用增量变更。

=== 割接切换

1. 停止源端业务写入,此后源库不再产生新的变更。

2. 确认初始同步完成。在 IvorySQL 端检查所有订阅表的状态均为 `r`(进入正常复制):

[source,sql]
----
-- IvorySQL 端执行
SELECT s.subname AS subscription,
r.srrelid::regclass AS table_name,
r.srsubstate
FROM pg_subscription_rel r
JOIN pg_subscription s ON s.oid = r.srsubid
ORDER BY 1, 2;

-- srsubstate:'d' 初始拷贝进行中;'s' 已同步数据;
-- 'r' 拷贝完成并进入正常复制(割接前所有表必须为 'r')
----

3. 确认停写后的最终增量已全部应用:停写后源端不再产生新事务,但 CHECKPOINT 等内部操作会使 WAL 位置继续前进,因此不应要求 LSN 精确相等,而应确认订阅端已追平停写时的位置且不再前进。

[source,sql]
----
-- 源端:停写后执行 CHECKPOINT,将已提交事务全部刷入 WAL
CHECKPOINT;

-- 源端:记录当前 WAL 位置 L1
SELECT pg_current_wal_lsn() AS source_lsn;

-- IvorySQL 端:等待 latest_end_lsn >= L1(追平或超过即可)
-- 并观察一段时间 latest_end_lsn 不再前进,说明增量已全部应用
SELECT subname, received_lsn, latest_end_lsn, latest_end_time
FROM pg_stat_subscription;
----

4. 对关键表执行行数与抽样校验(见"升级后验证")。

5. 校验通过后,在 IvorySQL 端停止订阅:`ALTER SUBSCRIPTION app_sub DISABLE;`

6. 将业务连接串切换到 IvorySQL,按需使用 1521 端口启用 Oracle 兼容行为。

7. 观察一段时间稳定后,删除订阅并下线源库。

== 升级后验证

=== 基础校验

[source,sql]
----
-- 对比关键表行数(源端与目标端各执行一次)
SELECT count(*) FROM app_table;

-- 检查序列当前值,避免割接后主键冲突
SELECT last_value FROM app_id_seq;
----

逻辑复制不复制序列值,割接前必须手动同步序列:

[source,shell]
----
# 源端导出序列状态(覆盖除系统 schema 外的所有 schema,生成 schema 限定的序列名)
psql -U postgres -d appdb -Atc \
"SELECT 'SELECT setval(' || quote_literal(format('%I.%I', schemaname, sequencename)) || ',' || last_value || ');' \
FROM pg_sequences \
WHERE last_value IS NOT NULL \
AND schemaname NOT IN ('pg_catalog', 'information_schema')" > sync_sequences.sql

# IvorySQL 端执行
psql -U postgres -p 5432 -d appdb -f sync_sequences.sql
----

脚本说明:`format('%I.%I', ...)` 生成带引号限定的完整序列名,多 schema 场景同样适用;`last_value IS NOT NULL` 过滤掉尚未使用的序列(其当前值即起始值,无需同步)。

=== 启用 Oracle 兼容模式

数据校验通过后,通过 1521 端口连接验证 Oracle 兼容行为:

[source,sql]
----
-- 确认兼容模式
SHOW ivorysql.compatible_mode;

-- 验证 Oracle 风格对象
SELECT SYSDATE FROM DUAL;
SELECT NVL(NULL, 'N/A') FROM DUAL;
----

== 常见问题

* **空字符串语义差异**:Oracle 兼容模式下 `'' = NULL`。若业务依赖 PostgreSQL 中空字符串与 NULL 的区别,需改造相关查询或数据。
* **标识符大小写**:Oracle 兼容模式遵循 Oracle 的大小写折叠规则,未加双引号的标识符视为大写。从 PostgreSQL 迁移的小写对象名在 Oracle 模式下仍可通过双引号精确访问,建议在切换前统一评估命名规范。
* **扩展兼容性**:源端使用的 PostgreSQL 扩展需确认 IvorySQL 是否已适配,适配列表见 xref:master/ecosystem_components/ecosystem_overview.adoc[生态组件适配]。
* **只读对象**:`--no-owner` 恢复后对象属主为执行恢复的用户,如需保持原有权限模型,请在目标端提前创建角色。
1 change: 1 addition & 0 deletions EN/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
** xref:master/operation_guide.adoc[Operation Management]
* Data Migration
** xref:master/migration_guide.adoc[Migration]
** xref:master/pg_upgrade_guide.adoc[Upgrading from PostgreSQL]
* IvorySQL Developers
** xref:master/contribution/contribution_guide.adoc[Contribution Guide]
** xref:master/developer_guide.adoc[Developer]
Expand Down
Loading
Loading