制表實戰(zhàn):從虛擬表到落地副本的完整指南)
做 SAP HANA 數(shù)據(jù)集成這幾年我見過最多的一種誤會就是以為在 HANA 里建了虛擬表就等于把數(shù)據(jù)拿過來了。業(yè)務(wù)同事跑過來問為什么我在 HANA 里查表報表還是 20 秒起步一看后臺查的根本不是本地表SQL 被推到了遠端源執(zhí)行。真正想解決這類問題你得把遠端表變成一份可落地的數(shù)據(jù)副本這就是 SAP HANA 里經(jīng)常提到的 Replica Tables。這篇文章我把遠端表、虛擬表、復(fù)制表的關(guān)系講透再給一套從初始化到刷新的實操路徑適合正在做 SAP HANA 數(shù)據(jù)同步、數(shù)倉集成或者剛開始接觸 SDA/SDI 的讀者。內(nèi)容不繞彎子直接按實際干活的經(jīng)驗來。1. 先搞清楚虛擬表為什么不夠用1.1 虛擬表不只是“連接”而是一條遠程執(zhí)行的 SQL 鏈很多剛接觸 SDASmart Data Access的同學建完 Remote Source 再創(chuàng)建 Virtual Table看到 HANA Catalog 里出現(xiàn)了一張表就以為數(shù)據(jù)已經(jīng)在 HANA 里了。這個誤解是后面所有性能問題的根源。虛擬表的本質(zhì)是一段“查詢轉(zhuǎn)發(fā)規(guī)則”。你在 HANA 里執(zhí)行SELECT * FROM VT_ORDERSHANA 并不會把整張表拉回來而是把這個 SQL 拆解、改寫盡量下推給遠端的源數(shù)據(jù)庫執(zhí)行。遠端算完后把結(jié)果集通過網(wǎng)絡(luò)傳回 HANAHANA 只負責最終展示或者做二次加工。這個過程很像你電腦上放著某個網(wǎng)盤的快捷方式圖標看起來像一個文件雙擊后其實還是從遠端拉內(nèi)容。網(wǎng)盤里的文件如果很大、網(wǎng)絡(luò)又一般打開速度自然感人。虛擬表也這樣它把“計算壓力”扔給遠端源把“網(wǎng)絡(luò)壓力”留給自己。對幾百行的小表問題不大一旦源表幾百萬行報表查詢里又帶 JOIN 和 GROUP BY遠端源和網(wǎng)絡(luò)的瓶頸馬上暴露出來。還有個容易忽略的點虛擬表能不能在 HANA 端做索引、分區(qū)、壓縮都不行。因為數(shù)據(jù)根本不在 HANA 里HANA 只是“代轉(zhuǎn)”SQL 的角色。你給虛擬表建索引系統(tǒng)會直接拒絕想都不要想。1.2 復(fù)制表與虛擬表的本質(zhì)差異存儲換性能理解了虛擬表是“快捷方式”那復(fù)制表就很好解釋了復(fù)制表是把遠端的數(shù)據(jù)實際拉下來在 HANA 本地真實落盤存儲形成一份獨立的、可查詢、可建索引、可分區(qū)的數(shù)據(jù)副本。它和虛擬表最大的差異只有一個數(shù)據(jù)到底放在哪。維度虛擬表復(fù)制表/落地副本數(shù)據(jù)存儲位置遠端源數(shù)據(jù)庫HANA 本地查詢執(zhí)行位置遠端為主HANA 組裝結(jié)果全部在 HANA 本地實時性接近實時受網(wǎng)絡(luò)和源端負載影響取決于刷新策略有延遲本地索引/分區(qū)/壓縮不支持完全支持對遠端可用性的依賴每次查詢都依賴只在刷新時依賴資源消耗網(wǎng)絡(luò)帶寬、遠端 CPU/IOHANA 本地內(nèi)存和 CPU適用場景小表、低頻查詢、實時訪問大表、分析型查詢、高頻訪問一句話總結(jié)復(fù)制表是用 HANA 的存儲空間換查詢性能。副本一旦建好日常查詢完全不需要再跑去遠端拉數(shù)據(jù)也不怕源庫臨時抖動、連接斷開甚至在源庫維護窗口期報表照樣能跑。不過代價也很直白HANA 是內(nèi)存數(shù)據(jù)庫本地副本會真實占用內(nèi)存資源同步延遲需要靠刷新頻率控制運維上多了一套刷新的監(jiān)控任務(wù)。這也是為什么我標題里強調(diào)的是“可落地的數(shù)據(jù)副本”——落地的意思就是真正放進了 HANA 的內(nèi)存和磁盤體系里不再是個“虛擬映射”。2. 復(fù)制表是怎么工作的什么時候該用2.1 一條數(shù)據(jù)從遠端到本地的完整路徑復(fù)制表不是憑空出現(xiàn)的它背后是一條清晰的數(shù)據(jù)鏈路。用我實際項目里最常見的一條鏈路來走一遍第一步創(chuàng)建 Remote Source也就是配置遠端數(shù)據(jù)庫的連接信息。HANA 通過這個連接去和遠端數(shù)據(jù)庫建立會話。第二步在遠端 Source 上創(chuàng)建 Virtual Table作為取數(shù)的“管道”HANA 要能先“看到”遠端表的結(jié)構(gòu)和字段。第三步本地建一張目標表將虛擬表里的數(shù)據(jù)初始化填入這是副本誕生的關(guān)鍵一步。第四步按照設(shè)定的刷新周期把源端新增或變更的數(shù)據(jù)同步到本地表。第五步業(yè)務(wù)查詢直接打本地表。你注意看整個鏈路里虛擬表承擔的角色很微妙它不是終點而是“取數(shù)的中間層”。建復(fù)制表的時候?qū)嶋H上還是通過虛擬表去遠端取數(shù)據(jù)只不過取完之后在本地落了地。有人會問能不能直接用一張本地表和遠端建復(fù)制關(guān)系不建虛擬表在我的經(jīng)驗里不建議這樣裸操作。虛擬表充當了一個“結(jié)構(gòu)驗證器”你能提前確認字段映射、類型轉(zhuǎn)換、訪問權(quán)限都OK了再決定初始化出問題也好排查。2.2 三種同步刷新方式一次性、定時全量、持續(xù)增量講刷新方式之前先明確一件事復(fù)制表默認不會自動更新。你復(fù)制一份副本出來它就定格在那一刻后續(xù)源表怎么變和它沒關(guān)系。要讓它保持新鮮必須梳理清楚刷新策略。我習慣把刷新方式分成三檔第一檔是一次性快照。用CREATE TABLE AS SELECT把數(shù)據(jù)復(fù)制過來常用于初始化、測試環(huán)境搭建、或者遷移臨時數(shù)據(jù)。它是一次性的跑完就結(jié)束不涉及后續(xù)任務(wù)調(diào)度。第二檔是定時全量刷新。對增量控制不敏感的小表比如幾萬到幾十萬行的維度表就非常合適。每到凌晨或者每小時把目標表清空再重新從虛擬表全量拉一遍。優(yōu)點是邏輯簡單不容易出錯缺點是數(shù)據(jù)量大時耗時較長頻繁全量刷新會給源庫增加壓力。第三檔是持續(xù)增量同步。源表有明確的更新時間戳并且業(yè)務(wù)上有新增和修改就可以按時間戳做增量。更復(fù)雜的場景比如源表沒有時間戳、卻有大量 update 操作那就要靠 SAP HANA SDISmart Data Integration的復(fù)制任務(wù)基于數(shù)據(jù)庫日志做 CDC 捕獲把數(shù)據(jù)變更實時或準實時地同步到 HANA。這三檔沒有絕對的好壞我的原則是能用增量不輕易做全量能做全量不勉強設(shè)計 CDC。增量同步是省資源但設(shè)計不好容易漏數(shù)據(jù)后面第 4 部分我會專門講這個坑。2.3 怎么判斷一張表適不適合落地副本不是什么表都值得做成 Replica Table。我在項目上吃過虧一開始圖省事把業(yè)務(wù)庫里的核心大表都復(fù)制了一份結(jié)果內(nèi)存吃緊最后還得清理?,F(xiàn)在我會按一套判斷標準來篩選適合做復(fù)制表的通常是這幾類報表和 BI 場景的底層數(shù)據(jù)表分析查詢多而且 SQL 復(fù)雜跨庫 JOIN 頻繁每次查詢都要多次經(jīng)過網(wǎng)絡(luò)往返性能不可控源庫是傳統(tǒng)業(yè)務(wù)庫白天高負載不希望報表查詢再去擠占它的資源表數(shù)據(jù)量在一定可控范圍內(nèi)且 HANA 側(cè)有明確內(nèi)存預(yù)算。不適合做復(fù)制表的我也列一下偶爾才查一次一次查幾行的主數(shù)據(jù)表沒必要落地需要秒級看到遠端最新狀態(tài)的實時查詢場景復(fù)制表刷新再快也有延遲數(shù)據(jù)量極大比如幾百億行HANA 內(nèi)存根本放不下源端表結(jié)構(gòu)頻繁變更復(fù)制任務(wù)維護成本遠大于收益。我常用的做法是先在虛擬表上做一輪性能測試打開執(zhí)行計劃看算子是 Pushdown 還是本地處理。如果大部分過濾和聚合都被下推到遠端執(zhí)行而且單次查詢還要等幾秒甚至幾十秒那這張表就值得評估落地副本。反過來如果查詢本身很簡單數(shù)據(jù)量又小虛擬表的延遲可以接受就完全沒必要折騰復(fù)制表。3. 實操手把手把遠端表變成 HANA 落地副本3.1 第一步創(chuàng)建 Remote Source先把通道打通不管用 SDA 還是 SDI第一步都是先創(chuàng)建 Remote Source。HANA 的語法大同小異以最常見的 HANA 到 HANA 遠端源為例CREATE REMOTE SOURCE S4H_SOURCE ADAPTER hdb CONFIGURATION ServerNode10.10.10.20:30015 WITH CREDENTIAL TYPE PASSWORD USING userREPL_USER;password******;如果遠端是 Oracle、SQL Server、Kafka 這類系統(tǒng)需要換對應(yīng)的 Adapter 和配置參數(shù)。比如連 Oracle 要用ora適配器連 SQL Server 要用sapodbc或sqlserver具體可以在 HANA 的ADAPTERS系統(tǒng)視圖中查到。創(chuàng)建完成后先別急著建表確認連接狀態(tài)正常。可以查一下系統(tǒng)視圖SELECT * FROM M_REMOTE_SOURCES; SELECT * FROM M_REMOTE_SOURCE_STATUS;確認源連接處于正常狀態(tài)后再繼續(xù)下一步。這個步驟很多人會跳過結(jié)果后面建虛擬表時報一堆連接錯誤來回折騰。另外權(quán)限方面要注意創(chuàng)建 Remote Source 需要CREATE REMOTE SOURCE權(quán)限使用遠端連接還需要對應(yīng)的 Schema 訪問權(quán)限。如果建完以后查詢提示無權(quán)限優(yōu)先檢查源端數(shù)據(jù)庫給 REPL_USER 授權(quán)是否到位。3.2 第二步建虛擬表驗證遠端數(shù)據(jù)能查出來通道打通后創(chuàng)建虛擬表。假設(shè)遠端源里有一張CONFIG.ORDERS我習慣在 HANA 這邊的集成 Schema 下建一個對應(yīng)的虛擬表CREATE VIRTUAL TABLE INT.VT_ORDERS AT S4H_SOURCE.CONFIG.ORDERS;建完后跑幾個查詢做驗證。先看數(shù)量SELECT COUNT(*) FROM INT.VT_ORDERS;再看幾條數(shù)據(jù)長什么樣SELECT * FROM INT.VT_ORDERS LIMIT 100;這里要特別強調(diào)驗證查詢能跑通不代表性能好。我建議順手用 EXPLAIN PLAN 看一下執(zhí)行計劃EXPLAIN PLAN FOR SELECT REGION, SUM(AMOUNT) FROM INT.VT_ORDERS GROUP BY REGION;如果執(zhí)行計劃里出現(xiàn)了 Remote、Pushdown 相關(guān)算子說明查詢確實是下推到遠端執(zhí)行的。這塊內(nèi)容先記錄下來后面做性能對比時要用。3.3 第三步用 CREATE TABLE AS SELECT 初始化副本虛擬表驗證沒問題就可以初始化本地副本了。我平時最常用的方式就是這條 SQLCREATE TABLE INT.ORDERS_COPY AS (SELECT * FROM INT.VT_ORDERS);這條語句會創(chuàng)建一張本地表并把虛擬表當前的數(shù)據(jù)批量寫入。如果實例默認的表類型是列存這張副本表就是列存表直接享受 HANA 的壓縮和列式掃描優(yōu)勢。如果想更穩(wěn)妥地控制表類型可以拆成兩步用CREATE COLUMN TABLE手動建表結(jié)構(gòu)再用INSERT INTO ... SELECT ...灌數(shù)據(jù)。初始化完成后驗證一下表的數(shù)據(jù)量和字段是否和源端對齊SELECT COUNT(*) FROM INT.ORDERS_COPY; SELECT COLUMN_NAME, DATA_TYPE_NAME FROM TABLE_COLUMNS WHERE SCHEMA_NAME INT AND TABLE_NAME ORDERS_COPY;這一步我建議做一下數(shù)據(jù)比對別只看行數(shù)。選幾個關(guān)鍵字段做 SUM 或者 HASH 比對確保復(fù)制過程中沒有類型截斷或者精度丟損。3.4 第四步刷新策略增量還是全量初始化只是開始刷新策略決定副本能不能長期可用。對小的維度表我直接走全量刷新TRUNCATE TABLE INT.ORDERS_COPY; INSERT INTO INT.ORDERS_COPY SELECT * FROM INT.VT_ORDERS; COMMIT;這里要注意TRUNCATE 之后如果刷新失敗表就是空的業(yè)務(wù)查詢會直接受影響。穩(wěn)妥的做法是先 INSERT 到一張臨時表校驗通過后再切換或者至少把刷新任務(wù)放在業(yè)務(wù)低峰期并做好失敗告警。對帶時間戳的大表我做增量刷新最經(jīng)典的是這種寫法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 會漏掉修改過的老數(shù)據(jù)。這種情況我改用 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 沒辦法判斷該插入還是更新。建表的時候如果忘了定義主鍵這里會吃不少苦頭。至于調(diào)度方式可以交給 HANA 自身的調(diào)度器、外部 cron 定期調(diào) hdbsql、或 SAP BW 流程鏈觸發(fā)。我的建議是刷新任務(wù)必須帶日志至少記錄開始時間、結(jié)束時間、影響行數(shù)。否則哪天數(shù)據(jù)對不上你只能干瞪眼。3.5 第五步驗證本地副本查詢性能和容量副本建好、刷新跑通最后做兩件事性能驗證和容量監(jiān)控。性能驗證很簡單拿同一套復(fù)雜 SQL分別跑虛擬表和副本表對比執(zhí)行時間。我在項目里見過一個典型的例子一個帶三張表 JOIN 的報表查詢虛擬表要跑 25 秒副本表落地后 1.2 秒就出結(jié)果差距快 20 倍。原因很簡單本地列存表不需要網(wǎng)絡(luò)往返也不依賴源庫當時的負載。容量監(jiān)控方面HANA 是內(nèi)存數(shù)據(jù)庫我得時刻關(guān)心副本表占了多少內(nèi)存??梢赃@樣查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;如果發(fā)現(xiàn)副本表越來越多內(nèi)存快撐不住了第一件事就是清理不再需要的副本而不是盲目加內(nèi)存。我見過太多項目HANA 內(nèi)存告警排查下來全是冗余副本表。4. 常見問題與避坑經(jīng)驗4.1 類型映射不對復(fù)制出來的數(shù)據(jù)缺字段、報精度錯誤HANA 建虛擬表時會嘗試把遠端字段類型自動映射到本地類型。但這個自動映射不是萬能的尤其是遇到 Oracle 的 NUMBER、SQL Server 的 DATETIME2、或者一些自定義類型時很容易出現(xiàn)映射異常。比如源表里有個字段是VARCHAR2(4000)HANA 側(cè)映射成NVARCHAR(5000)一般沒啥問題。但如果源表是RAW、BLOB映射到 HANA 后查詢可能直接報錯。更隱蔽的是精度問題DECIMAL(20, 8)源數(shù)據(jù)復(fù)制到 HANA 變成DECIMAL(18, 4)小數(shù)位直接被截斷報表數(shù)字對不上業(yè)務(wù)方找上門才發(fā)現(xiàn)。我的規(guī)避辦法是在虛擬表和副本表之間加一層 SQL 視圖做顯式類型轉(zhuǎn)換。比如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 源表沒有時間戳增量同步怎么破這是項目里最頭疼的問題。源表設(shè)計得比較老壓根沒有CREATE_AT、MODIFIED_AT這類字段你又想只同步變化的數(shù)據(jù)怎么辦我試過的可行方案有三種。第一種是直接放棄增量全量刷新前提是表數(shù)據(jù)量不大。第二種是跟源庫開發(fā)團隊溝通在表上加維護字段由應(yīng)用層在寫入時維護MODIFIED_AT實踐中還算靠譜。第三種就是上 SDI 的基于日志的 CDC讓 HANA 直接讀取源庫的 redo log 或者歸檔日志來捕獲數(shù)據(jù)變更不依賴業(yè)務(wù)字段。我個人不太推薦“靠主鍵自增 ID 做增量”的做法。主鍵最大 ID 只能捕獲新增完全覆蓋不了 update 和 delete 場景。你以為同步很正常結(jié)果數(shù)據(jù)永遠是舊的那一版報表不準還很難發(fā)現(xiàn)。4.3 復(fù)制任務(wù)失敗先查這三個地方復(fù)制表刷新任務(wù)掛掉先別慌按順序排查。第一個查 Remote Source 本身。遠端數(shù)據(jù)庫是不是重啟了賬號密碼是不是過期了網(wǎng)絡(luò)有沒有抖動。我遇到過好幾次源庫做了一次密碼輪換復(fù)制任務(wù)直接失敗日志里還報Connection refused一看密碼早過期了。第二個查目標表狀態(tài)。副本表是不是被其他事務(wù)鎖住了或者被 DBA 意外 DROP 了又或者分區(qū)結(jié)構(gòu)被修改過。刷新腳本里如果有 TRUNCATE表結(jié)構(gòu)一變很容易報錯。第三個查數(shù)據(jù)本身。增量同步時源端突然出現(xiàn)一條違反主鍵約束的數(shù)據(jù)或者某個字段長度爆掉都可能讓整個批量 INSERT 中斷。這種情況我建議把刷新腳本拆小批次或者把異常數(shù)據(jù)先抓出來而不是讓一整批任務(wù)全部滾回去。4.4 副本表悄悄吃掉內(nèi)存容量規(guī)劃要趁早HANA 的容量規(guī)劃里副本表占比經(jīng)常被低估。很多人想的是源表庫里 500GB壓縮后放 HANA 50GB 總行了吧。實際上壓縮比取決于數(shù)據(jù)特征寬表、隨機字符串多的表壓縮比可能只有 2:1而不像數(shù)字型列存那樣能壓到 10:1。我的經(jīng)驗是每張副本表在落地前先做一個采樣壓縮比測試在 HANA 里建臨時表灌 100 萬行看M_CS_TABLES里的估算內(nèi)存再換算整體容量。千萬別等上線了再補救。另外副本表建議按業(yè)務(wù)模塊統(tǒng)一命名比如REP_ORDERS、REP_CUSTOMER集中放在一個 Schema后續(xù)做容量清理時一眼就能分辨哪張表是副本、哪張表是業(yè)務(wù)表。我見過生產(chǎn)環(huán)境里副本表和業(yè)務(wù)表混在一起DBA 想清理都不太敢下手。我的個人體會復(fù)制表聽起來只是“建個表灌數(shù)據(jù)”這么簡單實際操作里考驗人的是刷新策略和容災(zāi)意識。虛擬表是外聯(lián)副本表是內(nèi)儲兩者互補不是一個淘汰一個。我現(xiàn)在的習慣是任何遠端表接入 HANA先建虛擬表做評估確認查詢模式和性能瓶頸再決定要不要轉(zhuǎn)成落地副本。真心建議你也保持這個節(jié)奏不會過度設(shè)計也不會漏掉真正需要落地的表。