GaussDB与openGauss国产数据库:从内核到迁移实战全解析 很多人在搜 GaussDB 的时候脑子里其实是乱的一会儿是华为高斯数据库一会儿是 openGauss一会儿又蹦出个 GaussDB(DWS)还有人直接甩一句不就是套壳 PostgreSQL 吗。我一开始也是这么被绕进去的。这篇文章不打算复述官网的产品彩页而是把数据库基础、国产数据库选型、华为 GaussDB 这套东西从内核到落地完整捋一遍——它到底是什么、和 openGauss 什么关系、装的时候会卡在哪、SQL 写起来和 MySQL/Oracle 差在哪、慢 SQL 怎么查、迁移会翻什么车。适合两类人一类是刚开始接触国产数据库、想找个能跑起来的环境练手的新人另一类是手上真有项目要从 Oracle 或 MySQL 挪过来、需要一份能直接抄作业的实操参考。1. 先把产品谱系理清GaussDB、openGauss、GaussDB(DWS) 不是同一个东西1.1 三条产品线的历史来源华为做数据库不是一天两天的事早年的产品命名是按数字来的GaussDB 100、GaussDB 200、GaussDB 300。这三个编号背后是完全不同的技术栈GaussDB 100 走的是 OLTP 方向早期尝试过自研内核后来逐步收敛GaussDB 200 是 MPP 架构的分析型数据库主要是拿来打数据仓库场景GaussDB 300 定位 HTAP属于探索性质。那几年对外宣传基本是高斯数据库四个字打包导致很多人的认知一直是混的。2019 年华为把内核开源出来就是 openGauss。这一步是分水岭从此高斯这个词在技术圈里至少分裂成两层含义——一层是开源社区版的 openGauss任何人都能下载、编译、装在自己机器上另一层是华为云上和商业交付里的 GaussDB是产品化的服务带管控、带运维、带服务支持。两者内核同源但不是同一个交付物这一点必须先在脑子里分开否则后面看文档会一直对不上号。再往后云上又分化出一堆带后缀的名字GaussDB(for MySQL)、GaussDB(for Cassandra)、GaussDB(for Redis) 等等。这些其实是把不同引擎包装成统一品牌后来大部分改名叫 GeminiDB 系列跟基于 openGauss 内核的那条主线已经不是一回事了。所以你现在看到GaussDB三个字第一反应应该是问说的是哪个是云上的关系型实例还是本地部署的企业版还是开源那个提醒国产数据库的产品命名和版本迭代非常快本文讲的是技术逻辑和实操思路具体的版本号、参数默认值、命令参数请以你手上那一版的官方文档为准不要照抄数字。1.2 内核同源不等于行为完全一致很多人有个误解既然 openGauss 和 GaussDB 内核同源那在 openGauss 上验证过的 SQL 和参数搬到 GaussDB 上就一定没问题。实际上会踩坑。原因是商业版在开源内核之上做了大量增强——分布式能力、并行执行框架、安全加固、管控接口、审计能力这些在开源版里要么没有要么形态不同。举个最直观的开源 openGauss 是单机或主备架构你写 SQL 不用考虑数据分布而 GaussDB 的分布式形态下表有分布列的概念查询会拆成 CN 和 DN 两段执行执行计划里会出现 Stream、Redistribute、Broadcast 这类算子。同一句 SQL在单机上是本地扫描在分布式里可能变成跨节点数据重分布性能差出几个数量级。名称定位内核来源典型使用方式openGauss开源关系型数据库PostgreSQL 深度改造 自研本地部署、学习、验证GaussDB企业级关系型数据库与 openGauss 同源商业增强云上实例、企业本地部署GaussDB(DWS)分析型数据仓库MPP 架构报表、离线分析、大宽表GeminiDB 系列多模 NoSQL各引擎独立KV、时序、宽表场景1.3 学习路线的岔路口先定目标再选环境搞清楚谱系之后学习路径其实就两条选错了会浪费大量时间。如果你的目标是我能写 SQL、能建表、能调优、能应付面试和日常开发那就装一套 openGauss 单机版成本最低一台 4 核 8G 的虚拟机就够装完立刻能练。如果你的目标是公司要上国产化替代我得做方案和迁移那必须去摸真实的 GaussDB 环境重点放在分布策略、迁移工具、三权分立下的权限模型上光练 SQL 是没用的。我个人的建议是先用 openGauss 把基础打穿把 SQL 兼容性、执行计划、事务隔离、备份恢复这些通用能力练熟然后再去看商业版的差异点。因为差异点大部分是多出来的能力而不是完全不同的东西有基础之后再补效率高得多。反过来先啃商业版的文档很容易被一堆管控概念淹没SQL 本身反而没练扎实。2. 从内核架构看 GaussDB 为什么像 PG 但又不是 PG2.1 线程模型和 PostgreSQL 最大的分歧点PostgreSQL 是典型的多进程模型每个客户端连接对应一个后端进程连接一多进程数就爆炸内存也各管各的。openGauss 在这点上做了根本性的改动改成多线程模型。整个实例是一个进程里面跑着主线程和一堆后台线程——checkpointer 负责刷脏页和推进检查点walwriter 负责写 WALpagewriter 负责批量落盘bgwriter 做后台清理autovacuum 处理垃圾回收statistics collector 收集统计还有 syslogger、WDR snapshot、job scheduler 这些。改成线程之后带来的变化很实际内存天然共享不用再靠共享内存段去传递数据上下文切换开销也小。但代价是容错边界变了——进程模型下一个后端崩了只影响那一个连接线程模型下一个线程踩内存可能整个实例挂掉。所以 openGauss 才会把线程池thread pool做成强依赖用有限的线程去服务大量连接而不是一连接一线程。线程池相关参数里enable_thread_pool控制开关thread_pool_attr是最关键的格式是线程池大小, 分组数, (绑核配置)比如16,2,(nobind)。线程池大小不是越大越好它要跟 CPU 核数匹配一般按核数的 2 到 4 倍给。给太大反而会因为线程争抢导致 CPU 上下文切换飙升表现出来就是连接数上去了QPS 反而掉了。2.2 内存结构max_process_memory 是总闸GaussDB/openGauss 的内存参数和 PG 有个重要的组织方式差异。PG 里你调shared_buffers管共享内存调work_mem管排序哈希各自相对独立。openGauss 引入了max_process_memory作为整个实例进程的内存上限shared_buffers、cstore_buffers、work_mem这些都是在它内部划分的。这意味着一个很容易犯的错误把shared_buffers调得很大同时又没动max_process_memory结果实例启动直接报内存不足。合理的做法是先按物理内存的 60% 到 70% 定max_process_memory再从里面切shared_buffers一般给max_process_memory的 40% 左右。列存场景要额外关注cstore_buffers它专门服务列存的 CU 缓存行存为主的库可以给得很小。还有两个会话级参数值得单独提work_mem决定单个排序或哈希算子能用的内存超了就落盘慢 SQL 里经常能看到Sort Method: external merge Disk这种字样maintenance_work_mem影响 VACUUM、CREATE INDEX、ALTER TABLE ADD FOREIGN KEY 这类维护操作大表建索引慢很多时候就是它太小了。这两个参数是按会话和算子分配的不要盲目往大了调一个复杂查询里可能有十几个哈希算子同时吃内存。2.3 存储引擎astore、ustore 和列存的三条路行存这块openGauss 提供两种存储格式。astore是追加写优化的跟传统 PG 的 heap 类似更新一条记录不是原地改而是写一个新版本旧版本留给 VACUUM 回收。好处是写 WAL 少、批量插入快坏处是更新频繁的表会严重膨胀表和索引都会虚胖,查询扫描的页面数变多。ustore是原位更新加回滚段undo的方案更新直接在原位置改旧版本存到 undo 里通过回滚段可以重建历史版本。它的直接收益是表不容易膨胀同时天然支持闪回查询这类能力。代价是 WAL 写得更多undo 空间需要单独管理。所以选型逻辑很清楚更新极其频繁、表膨胀问题突出、需要闪回的场景选 ustore以插入为主、批量加载、批量分析的场景选 astore。列存用orientationcolumn指定数据按列组织成 CUCompression Unit配合压缩算法在宽表的聚合分析上能比行存快一个数量级。但列存对单行更新极不友好而且并发更新会带来 CU 锁竞争所以它适合的是大批量导入 大范围扫描聚合不适合 OLTP 的点查点改。-- 行存追加写优化适合插入为主的表 CREATE TABLE t_astore (id int, name varchar(64)) WITH (storage_typeastore); -- 行存原位更新适合高频更新、需要闪回 CREATE TABLE t_ustore (id int, name varchar(64)) WITH (storage_typeustore); -- 列存适合报表和聚合分析 CREATE TABLE t_column (id int, amount numeric, stat_date date) WITH (orientationcolumn);2.4 WAL、CSN 和事务可见性WAL 这块逻辑和其他关系库大同小异所有修改先写日志再落盘保证崩溃恢复能力。但 openGauss 在事务可见性上有自己的一套用 CSNCommit Sequence Number配合 CSN LOG 来判断某个事务对当前快照是否可见。懂这一点对排查问题很关键——当你在看一个明明提交了但读不到的诡异现象时要往快照和 CSN 推进的方向去想而不是单纯怀疑数据没落盘。事务隔离级别上默认是 READ COMMITTED支持 REPEATABLE READ 和 SERIALIZABLE。这里有个 PG 系数据库的通用特性需要记住它不支持读未提交也不像某些数据库那样靠锁来实现可重复读而是靠 MVCC 快照。所以我明明锁住了行为什么另一个会话还是能读这类问题答案往往是你混淆了读写冲突和写写冲突。3. 环境落地从零装一套能跑起来的 GaussDB/openGauss3.1 系统前置检查八成的安装失败死在这一步安装之前有一串检查项跳过任何一项都可能在后面某个环节炸掉。操作系统层面openEuler 20.03 LTS、麒麟 V10、CentOS 7.6 这些是常见选择CPU 架构上 x86 和 ARM 都支持但要注意下载对应架构的安装包拿错包会出现各种奇怪的二进制格式错误。系统配置层面必须做这几件事关闭 SELinuxsetenforce 0并改/etc/selinux/config关闭防火墙配置 NTP 时间同步集群里节点时间不同步会导致主备建不起来配置/etc/hosts做主机名解析安装脚本会用主机名互访DNS 不靠谱的时候 hosts 是最稳的。内核参数是最容易漏的。下面这张表里的值是我实际用下来比较通用的起点具体请按官方文档和你机器的内存调整参数建议值作用kernel.sem250 32000 100 128信号量装库必备kernel.shmmax物理内存一半以上单段共享内存上限kernel.shmall按 shmmax/页大小算共享内存总页数vm.swappiness10尽量别用 swap避免抖动vm.overcommit_memory0内存分配策略net.ipv4.ip_local_port_range26000 65535本地端口范围net.core.somaxconn65535监听队列长度fs.aio-max-nr1048576异步 IO 请求数fs.file-max6815744系统级文件句柄除此之外还有用户级的ulimitnofile给到 1000000nproc给到 unlimitedstack给到 unlimited。这几个值如果没配好表现是安装成功但一跑并发就报错特别隐蔽。3.2 用户、目录与集群配置文件的字段解读openGauss/GaussDB 的安装必须用专用的操作系统用户不能拿 root 直接跑。标准做法是建一个dbgrp组和一个omm用户把omm加进dbgrp然后所有安装和运维命令都切到omm用户执行。groupadd dbgrp useradd -g dbgrp -m -d /home/omm omm echo omm:你的密码 | chpasswd mkdir -p /opt/huawei/install /opt/huawei/data /opt/huawei/log chown -R omm:dbgrp /opt/huawei chmod -R 750 /opt/huawei # omm 用户下配置环境变量 cat /home/omm/.bashrc EOF export GAUSSHOME/opt/huawei/install/app export PGDATA/opt/huawei/install/data/dn export LD_LIBRARY_PATH$GAUSSHOME/lib:$LD_LIBRARY_PATH export PATH$GAUSSHOME/bin:$PATH EOF集群配置文件通常叫clusterconfig.xml是安装的核心。里面几个字段必须理解清楚再填clusterName是集群名装完不能随便改nodeNames列出所有节点主机名必须和hostname输出严格一致dataNum是数据节点数量单机填 1dataPortBase是数据节点基础端口单机常用 15400gaussdbAppPath、gaussdbLogPath、tmpMppdbPath、gaussdbToolPath、corePath这几个路径必须提前建好并且属于omm否则预安装直接失败。注意配置文件里的路径写成什么样的实际就会在哪里建目录。我见过有人 xml 里写/opt/huawei/install/app但实际只建了/opt/huawei结果预安装报权限错误排查半天才发现是路径没建全。3.3 安装流程与初始化后的必做动作标准流程分三步预安装、安装、检查状态。# 切到 omm 用户 su - omm # 预安装校验环境、创建目录、检查内核参数 gs_preinstall -U omm -G dbgrp -X /opt/software/openGauss/clusterconfig.xml # 安装 gs_install -X /opt/software/openGauss/clusterconfig.xml \ --gsinit-parameter--encodingUTF8 \ --dn-gucmax_process_memory4GB \ --dn-gucshared_buffers1GB # 查看状态 gs_om -t status --detail # 连接 gsql -d postgres -p 15400 -r装完之后别急着走有几件事必须做。第一初始用户的密码是临时的第一次登录会强制你改而且新密码要满足复杂度要求——长度至少 8 位且必须包含大小写字母、数字、特殊字符中的至少三类连续相同字符和键盘序也会被拒绝。第二默认的密码加密方式在某些版本里是 md5安全合规场景下要改成 sha256改完需要重置所有用户密码这个动作要在业务接入前完成。第三pg_hba.conf默认只允许本地连接要远程访问得显式添加规则且推荐用sha256认证方式。3.4 failed to obtain local instance information 这类报错怎么查这个报错很多人搜过它不是一个功能性问题而是信息获取失败。排查方向有固定几条按顺序走基本能定位先确认执行命令的用户对不对。必须是omm用户用 root 或别的用户执行环境变量读不到就会拿不到实例信息。再确认环境变量GAUSSHOME和PGDATA是否指向真实路径路径写错或者目录被删了同样报这个错。第三检查实例数据目录下是否存在instance_manual_start_*这类状态文件这个文件丢了说明实例状态记录异常需要重新初始化或者从备份恢复。还有一个容易被忽略的点如果你是在容器或者虚拟化环境里装宿主机的/etc/hosts和容器内的解析不一致也会导致类似的信息获取失败。这类问题的通用排查思路就是——别盯着报错文字本身去查这个命令要读哪些文件、读哪些环境变量一个个验一遍。报错现象大概率原因处理动作failed to obtain local instance information非 omm 用户执行 / 环境变量未生效切用户、重新 source 环境变量预安装报 permission denied目录属主或权限不对chown omm:dbgrpchmod 750实例启动失败日志报内存不足max_process_memory 与 shared_buffers 冲突降低 shared_buffers 或调大总内存上限gsql 连接被拒绝pg_hba.conf 未放行 / 端口未监听检查监听地址与认证规则4. 对象与 SQL兼容模式下的写法差异才是真坑4.1 四种兼容模式选错了没法回头GaussDB/openGauss 支持通过dbcompatibility参数指定数据库兼容模式常见有 AOracle、BMySQL、CTeradata、PGPostgreSQL四种。这个参数最要命的地方在于数据库创建之后不能修改。所以建库之前必须想清楚你的存量应用是从哪个数据库迁过来的。CREATE DATABASE appdb WITH dbcompatibilityA ENCODINGUTF8;兼容模式影响的不是一两个函数而是一整套语义。比如标识符大小写A 模式下未加双引号的标识符默认被转成大写PG 模式下默认转小写B 模式跟 MySQL 保持一致。如果你从一个 MySQL 应用迁过来建库时选了 PG 模式那么所有SELECT * FROM User这种写法都可能找不到表——因为 MySQL 在 Linux 下默认表名区分大小写而 PG 模式会把它折叠成小写。空串和 NULL 的处理也是重灾区。Oracle 里空串等价于 NULLMySQL 里空串是空串两者行为完全不同。A 模式会尽量向 Oracle 靠拢B 模式向 MySQL 靠拢。如果你的代码里有WHERE name 这种条件在不同模式下结果可能一个是大量行一个是零行。4.2 数据类型、序列与伪列的实操差异A 模式下支持NUMBER(p,s)、VARCHAR2、CLOB、BLOB这些 Oracle 风格的类型DATE类型是带时分秒的这点和 Oracle 一致和 PG 的 date 只有日期不同。B 模式下则有TINYINT、MEDIUMINT、DATETIME这些 MySQL 风格类型。写迁移脚本的时候类型映射表必须逐个字段过一遍尤其是NUMBER这种不指定精度时的默认行为很容易出现精度丢失或者存储空间浪费。主键自增这块不同模式给的糖不一样。PG 风格用serial或者GENERATED ... AS IDENTITYA 模式可以用序列加触发器模拟B 模式直接给AUTO_INCREMENT。我建议统一用显式序列跨模式和跨库迁移时最省事也不依赖具体实现细节。-- 显式序列最稳的写法 CREATE SEQUENCE seq_order_id START 1 INCREMENT BY 1 CACHE 20; CREATE TABLE t_order ( id bigint DEFAULT nextval(seq_order_id) PRIMARY KEY, order_no varchar(32), amount numeric(18,2), created_at timestamp without time zone DEFAULT now() );伪列方面A 模式支持ROWNUM和SYSDATE分页可以写WHERE ROWNUM 10。但要注意执行顺序问题ROWNUM是在结果集生成过程中分配的和ORDER BY一起用的时候ORDER BY之后ROWNUM已经定好了拿到的不是排序后的前 10 条。正确写法是套一层子查询先排序再截断。这个坑和 Oracle 里一模一样从 Oracle 过来的人反而不会踩从 MySQL 过来的新手几乎必踩。4.3 分区表语法很全但索引要选对分区表在国产数据库里算是重点能力因为政务、金融类系统动辄几亿行的大表不分区根本没法运维。支持的策略包括 RANGE、LIST、HASH 以及二级分区。RANGE 最常用按时间切月份或年份LIST 适合按地区、机构这类离散值切HASH 用于打散热点。CREATE TABLE t_trade ( id bigint, trade_date date, amount numeric(18,2) ) PARTITION BY RANGE (trade_date) ( PARTITION p202401 VALUES LESS THAN (2024-02-01), PARTITION p202402 VALUES LESS THAN (2024-03-01), PARTITION p202403 VALUES LESS THAN (2024-04-01) );建分区表的时候有个必须提前决定的事索引是建 LOCAL 还是 GLOBAL。LOCAL 索引每个分区一份维护成本低分区裁剪后走索引快但如果查询条件里没有分区键就得扫描所有分区的索引。GLOBAL 索引全局唯一任意分区键的查询都能用但分区 DROP 之后索引会失效需要重建。删历史分区的场景用 LOCAL 索引 DROP PARTITION秒级完成用 GLOBAL 索引的话删完分区可能触发全局索引重建几个小时的维护窗口就没了。4.4 权限模型三权分立会卡住很多人如果从 MySQL 过来权限这块一定要提前有心理准备。openGauss/GaussDB 默认启用三权分立把权限拆成系统管理员、安全管理员、审计管理员三块互相制约。这意味着你不能像 MySQL 的 root 那样一个账号走天下有些操作必须用特定角色的账号才能做而且做完了审计管理员能看到记录。常见卡点建库建表用普通业务账号做不了得用系统管理员改审计相关配置得用审计管理员创建用户和授权得走安全管理员。另外还有 SELinux 式的强制访问控制、密码有效期、登录失败锁定这些安全策略都是默认开着的。刚开始会觉得麻烦但这就是国产化替代里被要求的能力抱怨没用把账号规划好就行。权限建议按这个思路规划给每个应用一套独立账号只授予它需要的 schema 权限不要图省事直接给ALL PRIVILEGES。参数层面像password_effect_time密码有效期这类测试环境可以调松一点方便反复验证生产环境一定要按合规要求配置。5. 性能看懂执行计划把慢 SQL 摁下去5.1 执行计划的读法先看算子形态再看代价EXPLAIN只给估算EXPLAIN ANALYZE会真跑一遍给实际值openGauss 还提供了EXPLAIN PERFORMANCE输出的信息更细包含各个执行节点的实际耗时、内存使用、以及分布式场景下各 DN 的耗时分布。排查慢 SQL 我一般先用EXPLAIN ANALYZE看形态确认问题在哪个算子再用EXPLAIN PERFORMANCE看细节。EXPLAIN ANALYZE SELECT o.order_no, o.amount FROM t_order o JOIN t_customer c ON o.cust_id c.id WHERE o.created_at 2024-01-01 ORDER BY o.amount DESC LIMIT 20;读计划的时候按这个顺序看先看代价最高的算子在哪一层再看 rows 估算值和 actual rows 差多少。如果估算 100 行实际回来 100 万行那基本可以断定是统计信息不准或者谓词里有函数导致选择性估不出来。接着看扫描方式Seq Scan出现在大表上通常不是好事Index Scan和Index Only Scan是好事Bitmap Heap Scan适合中等选择率。最后看连接方式Nested Loop适合小表驱动大表Hash Join适合大表对大表Merge Join适合两边都有序的情况。5.2 统计信息和 ANALYZE 的配合执行计划是成本优化器给的成本估算全靠统计信息。统计信息过期是慢 SQL 最廉价的原因也是最容易被忽略的。表刚导入几百万行但没 ANALYZE优化器还按几十行来估选出来的计划可能完全跑偏。-- 单表收集 ANALYZE t_order; -- 指定采样比例大表可以适当降低 ANALYZE t_order (trade_date) WITH SAMPLE 20 PERCENT; -- 查看统计信息是否新鲜 SELECT relname, last_analyze, last_autoanalyze, n_live_tup FROM pg_stat_user_tables WHERE relname t_order;自动收集autovacuum是开着的但触发阈值默认是按比例来的超大表可能几天都触发不了一次。对这种表我建议关键字段手动定时 ANALYZE或者在批量导入后立刻跑一次。另外如果查询里有多个列的相关性很强比如城市和邮编单列统计信息是估不准的需要用CREATE STATISTICS建多列扩展统计信息。5.3 索引失效的几类典型原因索引建了不代表能用上下面这几种情况特别常见字段上套了函数或表达式比如WHERE date(created_at) 2024-01-01这种要么改写成范围条件created_at 2024-01-01 AND created_at 2024-01-02要么直接建表达式索引。隐式类型转换也会让索引失效比如索引列是varchar查询传了数字数据库要做转换索引就用不上了——这种在从 MySQL 迁过来的系统里极其常见。第三种是索引列参与了计算或者用了!、NOT IN、OR这类条件优化器一算选择率太低干脆放弃索引走全表扫。第四种是分区表上建了 LOCAL 索引查询条件里没有分区键分区裁剪不了只能扫所有分区。排查索引问题我一般用EXPLAIN (ANALYZE, BUFFERS)重点看Buffers: shared hit/read读的块数远超预期说明索引没用上或者用了但回表太多。慢 SQL 典型症状根因处理方向计划里 Seq Scan 出现在大表统计信息过期 / 条件选择率低ANALYZE补合适的索引估算行数与实际差 100 倍以上统计信息不准或列相关性强提高统计目标值建多列统计Sort Method 显示 external merge Diskwork_mem 不足适度调大 work_mem 或减少排序数据量分区表扫描了全部分区谓词未包含分区键改写 SQL 或调整分区策略并发上来后响应时间暴涨线程池或连接数配置不当调整 thread_pool_attr 与 max_connections5.4 慢 SQL 的采集与复盘事后复盘得有数据。openGauss 提供了dbe_perf下的视图statement_history能查到历史 SQL 的执行时间、返回行数、扫描方式配合enable_stmt_track开关使用。更专业一点的做法是用 WDR通过gs_wdr_snapshot定期打快照然后用generate_wdr_report生成两个时间点之间的性能报告里面会列出 TOP SQL、等待事件分布、资源使用趋势。线上环境的思路应该是先通过 WDR 或 statement_history 锁定 TOP SQL再对这几条 SQL 逐个EXPLAIN ANALYZE找到算子级瓶颈然后决定是改 SQL、加索引还是调参数。最忌讳的是不看计划直接加索引加了一堆没用的索引反而拖慢写入。6. 高可用、备份恢复与日常运维的实操边界6.1 主备架构与切换逻辑单机版只适合学习和开发生产至少要一主一备。复制模式上有同步和异步之分同步复制保证主库提交时备库已经收到 WALRPO 为零但写入延迟高异步复制性能好但在主库故障时可能丢最后一段数据。一主多备的情况下还有 quorum 机制指定至少几个备库确认才算成功在可靠性和性能之间取平衡。日常切换用gs_ctl switchover主备角色互换属于计划内操作。真正的故障切换是 failover需要判断主库确实不可用否则会出现脑裂。备库坏掉重建用gs_ctl build -D $PGDATA -b full数据量大的时候重建可能跑很久这段时间备库是不可用的所以要放在低峰期做。我踩过的一个坑节点时间不同步导致备库一直连不上主库日志里报的是认证失败看着像密码问题实际是时间偏差太大触发了安全校验。教训是集群部署前一定要把 NTP 配好并验证别等出问题了才回头查。6.2 备份策略物理、逻辑、连续归档三件套备份方式分三类用途完全不同。逻辑备份用gs_dump/gs_dumpall/gs_restore导出的是 SQL 文本或自定义格式跨版本、跨库迁移方便但大库导出慢、恢复更慢。物理备份用gs_basebackup直接拷数据文件速度快恢复就是重新拉起实例但只能同版本同平台恢复。增量备份用gs_probackup支持全量加增量的组合适合数据量大、每天全备不现实的场景。连续归档是 PITR 的基础开启 WAL 归档持续把归档日志送到独立存储出问题时可以通过基础备份加 WAL 回放到任意时间点。这套机制的关键是归档日志的存储要和数据库实例物理隔离放在同一块盘上等于没做。# 逻辑全备 gs_dump -U omm -p 15400 -F c -f /backup/appdb.dmp appdb # 物理全备 gs_basebackup -D /backup/basebackup -h 127.0.0.1 -p 15400 # 恢复逻辑备份 gs_restore -U omm -p 15400 -d appdb_new -F c /backup/appdb.dmp6.3 日常巡检要盯的几个指标巡检不是看个热闹得盯住几个会致命的东西。连接数和线程池使用率接近上限时新连接会被拒绝用户看到的就是连不上。长事务跑了几小时没结束的事务会阻塞 VACUUM导致表膨胀加速还会拖住 WAL 推进。XID 回卷风险这是所有 PG 系数据库的经典问题事务 ID 是循环使用的长时间不清理会导致数据不可见甚至停机必须保证 autovacuum 正常工作。还有磁盘空间的增长速率尤其是 WAL 目录写入量大的库 WAL 生成速度可能超出预期磁盘写满会直接导致实例只读甚至宕机。表膨胀率也要定期看如果某张表实际占用空间是有效数据的好几倍就要考虑做重建VACUUM FULL或者用在线重定义的方式但要清楚VACUUM FULL会拿排他锁生产必须走维护窗口。7. 迁移实战从 Oracle 或 MySQL 搬过来最常翻车的点7.1 迁移前的评估比迁移本身重要很多迁移项目失败不是因为技术做不到而是评估做得太粗糙。评估至少要覆盖四块对象清单表、索引、视图、序列、约束的数量和复杂度、代码资产存储过程、函数、触发器、包这部分最难搞、SQL 语法特征用了哪些数据库特有的语法、数据量与时延要求决定用离线还是在线迁移。工具上UGO 这类工具能做源库评估和语法自动转换给出兼容性报告哪些对象能自动转、哪些要手工改一目了然。数据搬运用 DRS 做在线迁移配合gs_dump/gs_restore做小批量校验。不要把自动转换的脚本直接上生产转换率再高也一定会有关键对象需要人工介入。7.2 语法转换里最隐蔽的几类坑第一类是空串与 NULL 的等价性。Oracle 里 IS NULL为真迁移到不兼容 Oracle 语义的模式下会变成假所有依赖这个行为的判断逻辑全部失效。第二类是字符串长度语义GBK 库里的VARCHAR2(50)迁到 UTF8 环境下同样的50可能存不下原来那么多个汉字需要在评估阶段统一做容量放大。第三类是日期格式的隐式转换。Oracle 对TO_DATE依赖很重很多 SQL 直接写WHERE dt 2024-01-01依赖隐式转换迁到新库如果日期格式参数不同要么报错要么结果错。第四类是ROWNUM分页前面提过的执行顺序问题。第五类是包PACKAGE和自治事务这类对象基本没有工具能完整自动转换只能重写。源端特征迁移风险建议动作空串等于 NULL 的逻辑语义反转逐个业务逻辑确认并改写VARCHAR2 按字节计数长度不够按 UTF8 放大 2-3 倍隐式日期格式转换报错或结果偏差显式 TO_DATE / CASTROWNUM 分页结果集错误改写为窗口函数或 LIMIT/OFFSETPACKAGE、自治事务无法自动转换拆分为独立函数手工重写7.3 数据一致性校验怎么做才靠谱迁移完必须校验但全表逐行比对在 TB 级数据下不现实。我的做法是三层校验第一层比行数快且能发现大问题第二层比聚合值比如对金额字段做SUM、对字符串主键做哈希聚合两边对一下第三层做抽样明细比对按主键区间随机抽若干段逐行比字段值。抽样的时候要注意覆盖边界数据特别是极值、NULL 值、空串、超长字符串这些工具自动生成的样本往往集中在中间区间最脏的数据反而漏掉。校验脚本要留存业务上线后做数据同步时还能复用。8. 我在实际使用中反复踩到的几个坑第一个是连接数相关的报错。现象是应用侧时不时报连接不上或者线程池已满但数据库 CPU 和内存都不高。原因通常是应用连接池配得太大超过了数据库侧的max_connections或者线程池容量。解决方向不是无脑调大数据库参数而是先把应用连接池收敛到合理范围再根据实际并发量调整线程池大小。数据库这一侧永远应该是最后动的地方。第二个是时区问题。timestamp without time zone和timestamp with time zone混用加上 JDBC 连接串里的时区参数、服务器TimeZone参数三者不一致的时候同一批数据在不同客户端看到的时间能差好几个小时。我的经验是统一约定数据库存 UTC参数TimeZone设成 UTC应用层做展示转换。不要指望靠某一个参数解决所有时区问题那只会让排查更困难。第三个是分区表的日常维护。分区表建好了不代表一劳永逸新分区要提前建否则数据会写不进去直接报错。我一般提前建 3 到 6 个月的分区用定时任务自动生成避免节假日没人值班的时候出事。历史分区做分离DETACH再删除比直接 DROP 更安全出问题还能挂回去。第四个是参数改了没生效。GaussDB 的参数分好几级有些要重启实例有些 reload 就行还有些是会话级的。用gs_guc reload之后一定要回查pg_settings确认实际生效值context字段告诉你这个参数属于哪一级。我见过有人在会话级参数上反复调优结果每次新连接都回到默认值白折腾一整天。最后一个心得是别迷信默认值。国产数据库的默认参数很多是按通用场景给的偏保守。上线前至少要把内存相关、连接相关、WAL 相关的几组参数按实际硬件和业务特征过一遍做一次基线压测把基线数据存下来。后续出问题时有基线才能判断到底是变慢了还是本来就这样。