做 SAP HANA 数据集成这几年,我见过最多的一种误会,就是以为在 HANA 里建了虚拟表,就等于把数据拿过来了。业务同事跑过来问:为什么我在 HANA 里查表,报表还是 20 秒起步?一看后台,查的根本不是本地表,SQL 被推到了远端源执行。真正想解决这类问题,你得把远端表变成一份可落地的数据副本,这就是 SAP HANA 里经常提到的 Replica Tables。这篇文章我把远端表、虚拟表、复制表的关系讲透,再给一套从初始化到刷新的实操路径,适合正在做 SAP HANA 数据同步、数仓集成,或者刚开始接触 SDA/SDI 的读者。内容不绕弯子,直接按实际干活的经验来。
1. 先搞清楚:虚拟表为什么不够用
1.1 虚拟表不只是“连接”,而是一条远程执行的 SQL 链
很多刚接触 SDA(Smart Data Access)的同学,建完 Remote Source 再创建 Virtual Table,看到 HANA Catalog 里出现了一张表,就以为数据已经在 HANA 里了。这个误解是后面所有性能问题的根源。
虚拟表的本质,是一段“查询转发规则”。你在 HANA 里执行SELECT * FROM VT_ORDERS,HANA 并不会把整张表拉回来,而是把这个 SQL 拆解、改写,尽量下推给远端的源数据库执行。远端算完后,把结果集通过网络传回 HANA,HANA 只负责最终展示或者做二次加工。
这个过程很像你电脑上放着某个网盘的快捷方式:图标看起来像一个文件,双击后其实还是从远端拉内容。网盘里的文件如果很大、网络又一般,打开速度自然感人。虚拟表也这样,它把“计算压力”扔给远端源,把“网络压力”留给自己。对几百行的小表,问题不大;一旦源表几百万行,报表查询里又带 JOIN 和 GROUP BY,远端源和网络的瓶颈马上暴露出来。
还有个容易忽略的点:虚拟表能不能在 HANA 端做索引、分区、压缩?都不行。因为数据根本不在 HANA 里,HANA 只是“代转”SQL 的角色。你给虚拟表建索引,系统会直接拒绝,想都不要想。
1.2 复制表与虚拟表的本质差异:存储换性能
理解了虚拟表是“快捷方式”,那复制表就很好解释了:复制表是把远端的数据实际拉下来,在 HANA 本地真实落盘存储,形成一份独立的、可查询、可建索引、可分区的数据副本。它和虚拟表最大的差异只有一个:数据到底放在哪。
| 维度 | 虚拟表 | 复制表/落地副本 |
|---|---|---|
| 数据存储位置 | 远端源数据库 | HANA 本地 |
| 查询执行位置 | 远端为主,HANA 组装结果 | 全部在 HANA 本地 |
| 实时性 | 接近实时,受网络和源端负载影响 | 取决于刷新策略,有延迟 |
| 本地索引/分区/压缩 | 不支持 | 完全支持 |
| 对远端可用性的依赖 | 每次查询都依赖 | 只在刷新时依赖 |
| 资源消耗 | 网络带宽、远端 CPU/IO | HANA 本地内存和 CPU |
| 适用场景 | 小表、低频查询、实时访问 | 大表、分析型查询、高频访问 |
一句话总结:复制表是用 HANA 的存储空间换查询性能。副本一旦建好,日常查询完全不需要再跑去远端拉数据,也不怕源库临时抖动、连接断开,甚至在源库维护窗口期,报表照样能跑。
不过代价也很直白:HANA 是内存数据库,本地副本会真实占用内存资源,同步延迟需要靠刷新频率控制,运维上多了一套刷新的监控任务。这也是为什么我标题里强调的是“可落地的数据副本”——落地的意思就是真正放进了 HANA 的内存和磁盘体系里,不再是个“虚拟映射”。
2. 复制表是怎么工作的,什么时候该用
2.1 一条数据从远端到本地的完整路径
复制表不是凭空出现的,它背后是一条清晰的数据链路。用我实际项目里最常见的一条链路来走一遍:
第一步,创建 Remote Source,也就是配置远端数据库的连接信息。HANA 通过这个连接去和远端数据库建立会话。第二步,在远端 Source 上创建 Virtual Table,作为取数的“管道”,HANA 要能先“看到”远端表的结构和字段。第三步,本地建一张目标表,将虚拟表里的数据初始化填入,这是副本诞生的关键一步。第四步,按照设定的刷新周期,把源端新增或变更的数据同步到本地表。第五步,业务查询直接打本地表。
你注意看,整个链路里,虚拟表承担的角色很微妙:它不是终点,而是“取数的中间层”。建复制表的时候,实际上还是通过虚拟表去远端取数据,只不过取完之后在本地落了地。
有人会问:能不能直接用一张本地表和远端建复制关系,不建虚拟表?在我的经验里不建议这样裸操作。虚拟表充当了一个“结构验证器”,你能提前确认字段映射、类型转换、访问权限都OK了,再决定初始化,出问题也好排查。
2.2 三种同步刷新方式:一次性、定时全量、持续增量
讲刷新方式之前,先明确一件事:复制表默认不会自动更新。你复制一份副本出来,它就定格在那一刻,后续源表怎么变,和它没关系。要让它保持新鲜,必须梳理清楚刷新策略。
我习惯把刷新方式分成三档:
第一档是一次性快照。用CREATE TABLE AS SELECT把数据复制过来,常用于初始化、测试环境搭建、或者迁移临时数据。它是一次性的,跑完就结束,不涉及后续任务调度。
第二档是定时全量刷新。对增量控制不敏感的小表,比如几万到几十万行的维度表,就非常合适。每到凌晨或者每小时,把目标表清空,再重新从虚拟表全量拉一遍。优点是逻辑简单,不容易出错;缺点是数据量大时耗时较长,频繁全量刷新会给源库增加压力。
第三档是持续增量同步。源表有明确的更新时间戳,并且业务上有新增和修改,就可以按时间戳做增量。更复杂的场景,比如源表没有时间戳、却有大量 update 操作,那就要靠 SAP HANA SDI(Smart Data Integration)的复制任务,基于数据库日志做 CDC 捕获,把数据变更实时或准实时地同步到 HANA。
这三档没有绝对的好坏,我的原则是:能用增量,不轻易做全量;能做全量,不勉强设计 CDC。增量同步是省资源,但设计不好容易漏数据,后面第 4 部分我会专门讲这个坑。
2.3 怎么判断一张表适不适合落地副本
不是什么表都值得做成 Replica Table。我在项目上吃过亏,一开始图省事,把业务库里的核心大表都复制了一份,结果内存吃紧,最后还得清理。现在我会按一套判断标准来筛选:
适合做复制表的,通常是这几类:
- 报表和 BI 场景的底层数据表,分析查询多,而且 SQL 复杂;
- 跨库 JOIN 频繁,每次查询都要多次经过网络往返,性能不可控;
- 源库是传统业务库,白天高负载,不希望报表查询再去挤占它的资源;
- 表数据量在一定可控范围内,且 HANA 侧有明确内存预算。
不适合做复制表的,我也列一下:
- 偶尔才查一次,一次查几行的主数据表,没必要落地;
- 需要秒级看到远端最新状态的实时查询场景,复制表刷新再快也有延迟;
- 数据量极大,比如几百亿行,HANA 内存根本放不下;
- 源端表结构频繁变更,复制任务维护成本远大于收益。
我常用的做法是:先在虚拟表上做一轮性能测试,打开执行计划看算子是 Pushdown 还是本地处理。如果大部分过滤和聚合都被下推到远端执行,而且单次查询还要等几秒甚至几十秒,那这张表就值得评估落地副本。反过来,如果查询本身很简单,数据量又小,虚拟表的延迟可以接受,就完全没必要折腾复制表。
3. 实操:手把手把远端表变成 HANA 落地副本
3.1 第一步:创建 Remote Source,先把通道打通
不管用 SDA 还是 SDI,第一步都是先创建 Remote Source。HANA 的语法大同小异,以最常见的 HANA 到 HANA 远端源为例:
CREATE REMOTE SOURCE "S4H_SOURCE" ADAPTER "hdb" CONFIGURATION 'ServerNode=10.10.10.20:30015' WITH CREDENTIAL TYPE 'PASSWORD' USING 'user=REPL_USER;password=******';如果远端是 Oracle、SQL Server、Kafka 这类系统,需要换对应的 Adapter 和配置参数。比如连 Oracle 要用ora适配器,连 SQL Server 要用sapodbc或sqlserver,具体可以在 HANA 的ADAPTERS系统视图中查到。
创建完成后,先别急着建表,确认连接状态正常。可以查一下系统视图:
SELECT * FROM M_REMOTE_SOURCES; SELECT * FROM M_REMOTE_SOURCE_STATUS;确认源连接处于正常状态后,再继续下一步。这个步骤很多人会跳过,结果后面建虚拟表时报一堆连接错误,来回折腾。
另外,权限方面要注意:创建 Remote Source 需要CREATE REMOTE SOURCE权限,使用远端连接还需要对应的 Schema 访问权限。如果建完以后查询提示无权限,优先检查源端数据库给 REPL_USER 授权是否到位。
3.2 第二步:建虚拟表,验证远端数据能查出来
通道打通后,创建虚拟表。假设远端源里有一张CONFIG.ORDERS,我习惯在 HANA 这边的集成 Schema 下建一个对应的虚拟表:
CREATE VIRTUAL TABLE "INT"."VT_ORDERS" AT "S4H_SOURCE"."CONFIG"."ORDERS";建完后,跑几个查询做验证。先看数量:
SELECT COUNT(*) FROM "INT"."VT_ORDERS";再看几条数据长什么样:
SELECT * FROM "INT"."VT_ORDERS" LIMIT 100;这里要特别强调:验证查询能跑通不代表性能好。我建议顺手用 EXPLAIN PLAN 看一下执行计划:
EXPLAIN PLAN FOR SELECT REGION, SUM(AMOUNT) FROM "INT"."VT_ORDERS" GROUP BY REGION;如果执行计划里出现了 Remote、Pushdown 相关算子,说明查询确实是下推到远端执行的。这块内容先记录下来,后面做性能对比时要用。
3.3 第三步:用 CREATE TABLE AS SELECT 初始化副本
虚拟表验证没问题,就可以初始化本地副本了。我平时最常用的方式就是这条 SQL:
CREATE TABLE "INT"."ORDERS_COPY" AS (SELECT * FROM "INT"."VT_ORDERS");这条语句会创建一张本地表,并把虚拟表当前的数据批量写入。如果实例默认的表类型是列存,这张副本表就是列存表,直接享受 HANA 的压缩和列式扫描优势。如果想更稳妥地控制表类型,可以拆成两步:用CREATE COLUMN TABLE手动建表结构,再用INSERT INTO ... SELECT ...灌数据。
初始化完成后,验证一下表的数据量和字段是否和源端对齐:
SELECT COUNT(*) FROM "INT"."ORDERS_COPY"; SELECT COLUMN_NAME, DATA_TYPE_NAME FROM TABLE_COLUMNS WHERE SCHEMA_NAME = 'INT' AND TABLE_NAME = 'ORDERS_COPY';这一步我建议做一下数据比对,别只看行数。选几个关键字段做 SUM 或者 HASH 比对,确保复制过程中没有类型截断或者精度丢损。
3.4 第四步:刷新策略,增量还是全量
初始化只是开始,刷新策略决定副本能不能长期可用。
对小的维度表,我直接走全量刷新:
TRUNCATE TABLE "INT"."ORDERS_COPY"; INSERT INTO "INT"."ORDERS_COPY" SELECT * FROM "INT"."VT_ORDERS"; COMMIT;这里要注意,TRUNCATE 之后如果刷新失败,表就是空的,业务查询会直接受影响。稳妥的做法是先 INSERT 到一张临时表,校验通过后再切换,或者至少把刷新任务放在业务低峰期,并做好失败告警。
对带时间戳的大表,我做增量刷新,最经典的是这种写法:
INSERT INTO "INT"."ORDERS_COPY" SELECT * FROM "INT"."VT_ORDERS" WHERE "CREATE_AT" > ( SELECT COALESCE(MAX("CREATE_AT"), TO_TIMESTAMP('1970-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) FROM "INT"."ORDERS_COPY" ); COMMIT;如果源表存在 update 操作,而不是纯追加,那 INSERT 会漏掉修改过的老数据。这种情况我改用 UPSERT 或 MERGE 处理,HANA 里 UPSERT 的写法相对简洁:
UPSERT "INT"."ORDERS_COPY" SELECT * FROM "INT"."VT_ORDERS" WHERE "MODIFIED_AT" > ( SELECT COALESCE(MAX("MODIFIED_AT"), TO_TIMESTAMP('1970-01-01 00:00:00', 'YYYY-MM-DD HH24:MI:SS')) FROM "INT"."ORDERS_COPY" );不过 UPSERT 要求目标表必须有明确的主键,否则 HANA 没办法判断该插入还是更新。建表的时候如果忘了定义主键,这里会吃不少苦头。
至于调度方式,可以交给 HANA 自身的调度器、外部 cron 定期调 hdbsql、或 SAP BW 流程链触发。我的建议是:刷新任务必须带日志,至少记录开始时间、结束时间、影响行数。否则哪天数据对不上,你只能干瞪眼。
3.5 第五步:验证本地副本查询性能和容量
副本建好、刷新跑通,最后做两件事:性能验证和容量监控。
性能验证很简单,拿同一套复杂 SQL,分别跑虚拟表和副本表,对比执行时间。我在项目里见过一个典型的例子:一个带三张表 JOIN 的报表查询,虚拟表要跑 25 秒,副本表落地后 1.2 秒就出结果,差距快 20 倍。原因很简单,本地列存表不需要网络往返,也不依赖源库当时的负载。
容量监控方面,HANA 是内存数据库,我得时刻关心副本表占了多少内存。可以这样查:
SELECT SCHEMA_NAME, TABLE_NAME, ESTIMATED_MEMORY_SIZE_IN_TOTAL / 1024 / 1024 AS SIZE_MB FROM M_CS_TABLES WHERE SCHEMA_NAME = 'INT' AND TABLE_NAME = 'ORDERS_COPY';如果发现副本表越来越多,内存快撑不住了,第一件事就是清理不再需要的副本,而不是盲目加内存。我见过太多项目,HANA 内存告警,排查下来全是冗余副本表。
4. 常见问题与避坑经验
4.1 类型映射不对,复制出来的数据缺字段、报精度错误
HANA 建虚拟表时,会尝试把远端字段类型自动映射到本地类型。但这个自动映射不是万能的,尤其是遇到 Oracle 的 NUMBER、SQL Server 的 DATETIME2、或者一些自定义类型时,很容易出现映射异常。
比如源表里有个字段是VARCHAR2(4000),HANA 侧映射成NVARCHAR(5000),一般没啥问题。但如果源表是RAW、BLOB,映射到 HANA 后查询可能直接报错。更隐蔽的是精度问题:DECIMAL(20, 8)源数据,复制到 HANA 变成DECIMAL(18, 4),小数位直接被截断,报表数字对不上,业务方找上门才发现。
我的规避办法是:在虚拟表和副本表之间,加一层 SQL 视图做显式类型转换。比如:
CREATE VIEW "INT"."V_ORDERS_CAST" AS SELECT ORDER_ID, CAST(AMOUNT AS DECIMAL(20, 8)) AS AMOUNT, TO_TIMESTAMP(CREATE_AT) AS CREATE_AT FROM "INT"."VT_ORDERS";然后基于视图去初始化副本表。这样类型、精度都在自己的掌控里,不会出莫名其妙的截断问题。
4.2 源表没有时间戳,增量同步怎么破
这是项目里最头疼的问题。源表设计得比较老,压根没有CREATE_AT、MODIFIED_AT这类字段,你又想只同步变化的数据,怎么办?
我试过的可行方案有三种。第一种是直接放弃增量,全量刷新,前提是表数据量不大。第二种是跟源库开发团队沟通,在表上加维护字段,由应用层在写入时维护MODIFIED_AT,实践中还算靠谱。第三种就是上 SDI 的基于日志的 CDC,让 HANA 直接读取源库的 redo log 或者归档日志来捕获数据变更,不依赖业务字段。
我个人不太推荐“靠主键自增 ID 做增量”的做法。主键最大 ID 只能捕获新增,完全覆盖不了 update 和 delete 场景。你以为同步很正常,结果数据永远是旧的那一版,报表不准还很难发现。
4.3 复制任务失败,先查这三个地方
复制表刷新任务挂掉,先别慌,按顺序排查。
第一个查 Remote Source 本身。远端数据库是不是重启了,账号密码是不是过期了,网络有没有抖动。我遇到过好几次,源库做了一次密码轮换,复制任务直接失败,日志里还报Connection refused,一看密码早过期了。
第二个查目标表状态。副本表是不是被其他事务锁住了,或者被 DBA 意外 DROP 了,又或者分区结构被修改过。刷新脚本里如果有 TRUNCATE,表结构一变很容易报错。
第三个查数据本身。增量同步时,源端突然出现一条违反主键约束的数据,或者某个字段长度爆掉,都可能让整个批量 INSERT 中断。这种情况我建议把刷新脚本拆小批次,或者把异常数据先抓出来,而不是让一整批任务全部滚回去。
4.4 副本表悄悄吃掉内存,容量规划要趁早
HANA 的容量规划里,副本表占比经常被低估。很多人想的是:源表库里 500GB,压缩后放 HANA 50GB 总行了吧。实际上压缩比取决于数据特征,宽表、随机字符串多的表,压缩比可能只有 2:1,而不像数字型列存那样能压到 10:1。
我的经验是:每张副本表在落地前,先做一个采样压缩比测试,在 HANA 里建临时表灌 100 万行,看M_CS_TABLES里的估算内存,再换算整体容量。千万别等上线了再补救。
另外,副本表建议按业务模块统一命名,比如REP_ORDERS、REP_CUSTOMER,集中放在一个 Schema,后续做容量清理时一眼就能分辨哪张表是副本、哪张表是业务表。我见过生产环境里副本表和业务表混在一起,DBA 想清理都不太敢下手。
我的个人体会
复制表听起来只是“建个表灌数据”这么简单,实际操作里考验人的是刷新策略和容灾意识。虚拟表是外联,副本表是内储,两者互补,不是一个淘汰一个。我现在的习惯是,任何远端表接入 HANA,先建虚拟表做评估,确认查询模式和性能瓶颈,再决定要不要转成落地副本。真心建议你也保持这个节奏,不会过度设计,也不会漏掉真正需要落地的表。