計到PLV執(zhí)行計劃分析)
周五下午三點某數(shù)據(jù)平臺的運維群里開始有人喊系統(tǒng)卡了。我登錄 SAP HANA 看了一眼CPU 已經(jīng)沖到 85%可昂貴的語句追蹤里一條慢 SQL 都沒有——單條執(zhí)行都低于 1 秒。這個場景在 SAP HANA 的故障排查里太典型了真正拖垮系統(tǒng)的往往不是那幾條明顯的慢 SQL而是大量高頻短查詢的累積。要抓住它們得打開 Capture Request Statistics要讀懂它們?yōu)槭裁绰值冒褕?zhí)行計劃導(dǎo)成 PLV 文件用 VS Code 打開逐節(jié)點分析。這篇文章就把這套完整流程寫下來。1. 為什么默認(rèn)的昂貴語句追蹤抓不住隱形慢 SQL1.1 昂貴語句追蹤的 1 秒閾值陷阱SAP HANA 默認(rèn)并不是什么都不記錄它有一套叫 Expensive Statement Trace 的機制。這套機制看名字就知道專門記錄執(zhí)行得比較貴的語句。問題是它有一個硬性的執(zhí)行時間閾值在indexserver.ini配置文件的[expensive_statement]段里參數(shù)叫threshold_execution_time默認(rèn)值是 1000000 微秒也就是 1 秒。這意味著什么只有單次執(zhí)行時間超過 1 秒的 SQL 才會被記錄到昂貴語句日志里。很多運維同學(xué)排查慢 SQL 時習(xí)慣先看這個日志結(jié)果發(fā)現(xiàn)里面空空如也于是得出數(shù)據(jù)庫沒有慢 SQL的結(jié)論。但線上業(yè)務(wù)的真實情況往往是單次 100 毫秒的查詢每秒被調(diào) 500 次一分鐘累計占用數(shù)據(jù)庫 CPU 的時間就是 300 秒。單拿出來哪一條都不夠昂貴標(biāo)準(zhǔn)合在一起卻能把整個實例拖垮。這就引出一個非常關(guān)鍵的觀點慢 SQL 的定義不能只看單次耗時還要看執(zhí)行頻率和累計資源消耗。昂貴語句追蹤本質(zhì)上是一個事后告警工具適合人工抽查不適合做全量畫像。想看清系統(tǒng)里到底跑了什么需要的是另一套能力——全量請求捕獲。1.2 高頻短查詢?nèi)绾温峡逑到y(tǒng)高頻短查詢的最可怕之處不是每一次執(zhí)行有多重而是它的累積效應(yīng)。舉個例子一個查詢單次 50 毫秒執(zhí)行 2000 次總耗時是 100 秒。這 100 秒里包含了對 CPU 的占用、內(nèi)存的申請釋放、計劃緩存的查找和更新、結(jié)果集的序列化。如果并發(fā)高還會放大鎖等待和線程排隊。更隱蔽的是大量短查詢會不斷沖擊執(zhí)行計劃緩存導(dǎo)致真正需要復(fù)用的好計劃被頻繁換出整體命中率下降讓每條 SQL 的平均編譯開銷上升。用戶體感就是系統(tǒng)整體變卡但抓單條 SQL 又抓不到。這類問題在接口型業(yè)務(wù)里最常見。前端輪詢接口、定時任務(wù)高頻掃描、報表工具反復(fù)拉取同一段數(shù)據(jù)都會產(chǎn)生這種隱形慢 SQL。它們不會寫進昂貴語句日志也不會觸發(fā)告警只會讓 CPU 慢慢爬升直到某一天高峰期扛不住。1.3 什么場景值得開捕獲Capture Request Statistics 的核心價值在于它把所有經(jīng)過 SQL 引擎的請求都記錄下來無論快慢。這樣你拿到的不是幾個典型樣本而是一段時間內(nèi)的完整請求畫像。我一般在三類場景下會主動開啟捕獲。第一類是定期的性能巡檢不需要等出故障先抓一到兩天的全量數(shù)據(jù)做基線第二類是系統(tǒng)上線前后的對比確認(rèn)新功能或新版本有沒有引入異常的查詢模式第三類是說不清哪里慢的疑難雜癥沒有明確慢 SQL但整體 CPU 或響應(yīng)時間異常這時候必須靠全量數(shù)據(jù)還原現(xiàn)場。2. 打開 Capture Request Statistics參數(shù)、落表和性能代價2.1 三步開關(guān)命令、驗證與生效范圍打開 Capture Request Statistics 的操作非常輕量一條 ALTER SYSTEM 語句就能完成。在目標(biāo)租戶庫執(zhí)行ALTER SYSTEM ALTER CONFIGURATION (indexserver.ini, SYSTEM) SET (sql, capture_request_statistics) true WITH RECONFIGURE;注意幾個細節(jié)。第一SYSTEM表示在集群級別修改配置這個配置會廣播到所有 indexserver 節(jié)點在有多節(jié)點、多租戶的架構(gòu)下不需要逐臺登錄設(shè)置。第二WITH RECONFIGURE必不可少它讓配置立即生效否則只是寫入了配置文件要等實例重啟才生效。第三這個參數(shù)作用在 indexserver 進程上所以要在目標(biāo)租戶庫執(zhí)行而不是在 SYSTEMDB 里執(zhí)行了事。設(shè)置完成后先確認(rèn)一下生效狀態(tài)SELECT FILE_NAME, SECTION, KEY, VALUE FROM SYS.M_CONFIG WHERE FILE_NAME indexserver.ini AND KEY capture_request_statistics;看到 VALUE 為 true就說明開關(guān)已經(jīng)生效。此時系統(tǒng)里的請求會開始往統(tǒng)計表寫入數(shù)據(jù)。2.2 捕獲數(shù)據(jù)落在哪兩張統(tǒng)計表的關(guān)鍵字段開啟捕獲后數(shù)據(jù)落在_SYS_STATISTICS模式下的兩張核心表里。一張是REQUESTS_CAPTURED記錄每一次請求的執(zhí)行概況另一張是STATEMENTS_CAPTURED保存 SQL 語句文本。兩者通過STATEMENT_KEY字段關(guān)聯(lián)。REQUESTS_CAPTURED里我最常用到的字段有這些字段含義用途START_TIME請求開始時間做時間段過濾圈定業(yè)務(wù)高峰期DURATION_MICROSEC請求總耗時微秒計算平均耗時、總耗時STATEMENT_HASHSQL 文本哈希把相同文本的 SQL 聚合成一類STATEMENT_KEY語句唯一鍵關(guān)聯(lián)到 STATEMENTS_CAPTUREDUSER_NAME執(zhí)行用戶過濾系統(tǒng)請求、定向分析業(yè)務(wù)用戶APPLICATION_NAME應(yīng)用名稱定位到具體接口或客戶端TOTAL_CPU_TIME請求消耗的 CPU 時間看資源消耗比耗時更直觀這張表是典型的寬表字段很多。實際做分析時先抓住執(zhí)行次數(shù)、總耗時、平均耗時、CPU 時間這四類就足夠邁出第一步了。2.3 捕獲的代價采樣與窗口期建議先說結(jié)論Capture Request Statistics 有額外開銷但通常可控。每次請求結(jié)束后數(shù)據(jù)庫需要把統(tǒng)計信息寫入統(tǒng)計表這個動作本身會消耗一定 CPU 和 IO。特別是在每秒幾千次短查詢的高并發(fā)場景寫入放大效應(yīng)會比較明顯。所以我不建議把這個開關(guān)常年常開更推薦窗口期狩獵的做法。比如設(shè)定 30 分鐘到 2 小時的捕獲窗口覆蓋業(yè)務(wù)高峰或者做 1 到 2 天的全量捕獲用于季度巡檢。捕獲結(jié)束后立刻關(guān)閉。如果確實需要長期監(jiān)控可以研究一下采樣相關(guān)參數(shù)把捕獲比例調(diào)低但那樣會損失全量數(shù)據(jù)的完整性屬于權(quán)衡方案。我的經(jīng)驗是排查性能問題不需要 7x24 小時全量捕獲抓一個完整的業(yè)務(wù)周期比如一個工作日下午加一個早晨高峰已經(jīng)能覆蓋大多數(shù)典型問題。3. 從統(tǒng)計表里撈出真正值得優(yōu)化的 Top SQL3.1 第一刀按 SQL 指紋做總耗時聚合數(shù)據(jù)抓下來了直接一條條看肯定不現(xiàn)實。第一步一定是聚合。按什么維度聚合我建議按STATEMENT_HASH做分組這個字段相當(dāng)于 SQL 的指紋不同文本但結(jié)構(gòu)相似的語句會落在不同的哈希上歸并起來非常干凈。下面這條查詢是整個過程的核心建議直接收藏SELECT RC.STATEMENT_HASH, COUNT(*) AS EXECUTION_COUNT, SUM(RC.DURATION_MICROSEC) / 1000000 AS TOTAL_DURATION_SEC, AVG(RC.DURATION_MICROSEC) / 1000 AS AVG_DURATION_MS, MAX(RC.DURATION_MICROSEC) / 1000 AS MAX_DURATION_MS, SUM(RC.TOTAL_CPU_TIME) / 1000000 AS TOTAL_CPU_SEC FROM _SYS_STATISTICS.REQUESTS_CAPTURED RC WHERE RC.START_TIME ADD_SECONDS(CURRENT_UTCTIMESTAMP, -3600) GROUP BY RC.STATEMENT_HASH ORDER BY TOTAL_DURATION_SEC DESC LIMIT 20;為什么排序用 SUM 而不是 MAX這是很多初學(xué)者容易犯的錯。MAX只能告訴你最極端的那一次有多慢如果把所有資源都用來優(yōu)化這一條很可能是在大海撈針而SUM把執(zhí)行次數(shù)和單次耗時做了乘法直接量化了這條 SQL 在這段時間內(nèi)總共消耗了多少數(shù)據(jù)庫時間。優(yōu)化掉的每一秒都是實打?qū)嵉南到y(tǒng)吞吐量提升。3.2 第二刀關(guān)聯(lián)出 SQL 文本拿到哈希排名后下一步是把 SQL 文本撈出來。哈希只是一個代號你不知道具體是哪個查詢。通過STATEMENT_KEY關(guān)聯(lián)到STATEMENTS_CAPTURED表就能看到完整的語句內(nèi)容SELECT SC.STATEMENT_STRING, RC.STATEMENT_HASH, COUNT(*) AS EXECUTION_COUNT, SUM(RC.DURATION_MICROSEC) / 1000000 AS TOTAL_DURATION_SEC FROM _SYS_STATISTICS.REQUESTS_CAPTURED RC JOIN _SYS_STATISTICS.STATEMENTS_CAPTURED SC ON RC.STATEMENT_KEY SC.STATEMENT_KEY WHERE RC.START_TIME ADD_SECONDS(CURRENT_UTCTIMESTAMP, -3600) GROUP BY RC.STATEMENT_HASH, SC.STATEMENT_STRING ORDER BY TOTAL_DURATION_SEC DESC LIMIT 20;執(zhí)行這條查詢時我建議多等幾秒因為聚合的數(shù)據(jù)量可能很大。如果同時抓了多個節(jié)點的數(shù)據(jù)最終統(tǒng)計表里是整合后的結(jié)果不需要手動合并。拿到 SQL 文本后先肉眼掃一遍 WHERE 條件、JOIN 條件、子查詢結(jié)構(gòu)很多問題在這一步已經(jīng)能猜個七八分。真正的確認(rèn)還得靠執(zhí)行計劃。3.3 快速通道用 M_EXECUTION_STATISTICS 應(yīng)急看最近請求有一種情況不需要開 Capture Request Statistics你只想看看最近幾分鐘系統(tǒng)在跑什么比如正在處理一個實時故障。這時候可以查詢系統(tǒng)視圖SYS.M_EXECUTION_STATISTICS它記錄了最近實際執(zhí)行過的請求而且直接帶有STATEMENT_STRING字段連關(guān)聯(lián)都省了。SELECT STATEMENT_STRING, COUNT(*) AS EXECUTION_COUNT, SUM(DURATION_MICROSEC) / 1000000 AS TOTAL_DURATION_SEC, AVG(DURATION_MICROSEC) / 1000 AS AVG_DURATION_MS FROM SYS.M_EXECUTION_STATISTICS WHERE START_TIME ADD_SECONDS(CURRENT_UTCTIMESTAMP, -900) GROUP BY STATEMENT_STRING ORDER BY TOTAL_DURATION_SEC DESC LIMIT 10;但要提醒一句這個視圖是個內(nèi)存環(huán)只保留最近一段時間或最近一部分請求容量有限不適合做歷史分析和完整畫像。它的定位是應(yīng)急快照用于快速確認(rèn)現(xiàn)在到底在跑什么。真正要寫分析報告、做趨勢對比還是得靠 Capture Request Statistics 落表的數(shù)據(jù)。3.4 順手做體檢執(zhí)行次數(shù)、平均耗時、CPU 占比除了總耗時排名我通常還會順手分析三個維度因為它們能反映不同類型的性能問題。執(zhí)行次數(shù)特別高但平均耗時很低的 SQL往往是接口輪詢或者循環(huán)調(diào)用優(yōu)化思路是緩存結(jié)果、減少調(diào)用次數(shù)或者改批量查詢平均耗時高但執(zhí)行次數(shù)低的 SQL一般是復(fù)雜報表或大范圍掃描優(yōu)化思路是加過濾條件、拆分查詢、優(yōu)化 JOIN 順序CPU 時間占比異常高的 SQL即使耗時不高也可能是大量運算、函數(shù)處理或類型轉(zhuǎn)換導(dǎo)致的這類問題會在 CPU 掛高時特別明顯。這三個維度的分析不需要額外查詢上面的聚合 SQL 已經(jīng)包含了相關(guān)字段只需要改變排序和觀察角度。4. 把執(zhí)行計劃導(dǎo)出 .plv用 VS Code 打開4.1 統(tǒng)計回答是誰執(zhí)行計劃回答為什么前面做的所有工作都是在回答一個問題系統(tǒng)里哪些 SQL 消耗最大。這是一個定位的過程。但要真正解決問題必須回答下一個問題這些 SQL 為什么慢是表掃描走了全表是 JOIN 順序有問題還是 WHERE 條件里的函數(shù)導(dǎo)致索引失效回答為什么靠執(zhí)行計劃。SAP HANA 的執(zhí)行計劃可視化文件就是 PLV全稱 Plan Visualization。它把優(yōu)化器生成的算子樹轉(zhuǎn)換成圖形界面每個節(jié)點展示算子類型、消耗時間、處理行數(shù)、具體屬性。對于熟悉節(jié)點閱讀的人來說一張 PLV 比幾千行日志都直觀。4.2 產(chǎn)出一條 .plv 的兩種常見方法第一條路徑是在 SQL 控制臺里用EXPLAIN PLAN語句直接生成執(zhí)行計劃。比如EXPLAIN PLAN FOR SELECT ORDER_ID, CUSTOMER_NAME, TOTAL_AMOUNT FROM ORDER_HEADER WHERE ORDER_DATE 2024-01-01 AND ORDER_DATE 2024-02-01;執(zhí)行后控制臺會返回一個結(jié)果集里面按層級列出了算子樹。在 SAP HANA 的 Database Explorer 里可以在這個結(jié)果面板上找到導(dǎo)出計劃或Visualize Plan之類的按鈕把計劃保存成.plv文件。第二條路徑更直接在 Database Explorer 里打開某條 SQL 語句的執(zhí)行歷史找到具體某一次執(zhí)行直接下載對應(yīng)的執(zhí)行計劃文件。這種方式拿到的通常是那份實際執(zhí)行過的計劃包含真實行數(shù)和耗時比純估算計劃更有診斷價值。需要特別注意一個坑EXPLAIN PLAN FOR生成的是優(yōu)化器估算的計劃反映的是數(shù)據(jù)庫認(rèn)為應(yīng)該怎么執(zhí)行而不是實際上怎么執(zhí)行。在統(tǒng)計信息不準(zhǔn)確或查詢過于復(fù)雜時估算計劃和實際計劃可能差別很大。定位問題時優(yōu)先使用實際執(zhí)行計劃。4.3 VS Code 環(huán)境與擴展配置拿到 .plv 文件后用文本編輯器打開只能看到一堆 JSON真正舒服的打開方式是使用可視化渲染工具。VS Code 里可以直接做到這一點。先在擴展市場搜索并安裝 SAP 官方提供的 SAP HANA Database Explorer 擴展。安裝完成后VS Code 會把.plv文件關(guān)聯(lián)到該擴展的執(zhí)行計劃可視化視圖。直接點擊文件名打開就能看到圖形化的算子樹。如果擴展裝好了雙擊還是顯示 JSON不需要卸載重裝右鍵點擊文件選擇 Open With然后手動選擇 SAP HANA 相關(guān)的執(zhí)行計劃查看器即可。這種手動關(guān)聯(lián)在第一次使用時很常見。4.4 打開失敗和渲染異常的排查邏輯沒有擴展時的表現(xiàn)最直接文件會以純文本 JSON 打開一眼就能識別。有擴展但打不開可分為三種情況。第一文件后綴不是.plv可能是.json或.txt需要右鍵手動關(guān)聯(lián)第二文件內(nèi)容不完整比如從控制臺復(fù)制時只復(fù)制了一部分 JSON這種情況需要重新導(dǎo)出完整文件第三版本兼容性問題數(shù)據(jù)庫版本和 VS Code 擴展版本跨度太大時某些節(jié)點屬性可能顯示不全但通常不會完全打不開。一個比較實用的兜底方案如果 VS Code 的環(huán)境一直配置不好可以把 .plv 文件直接拖入 SAP HANA Database Explorer 的網(wǎng)頁版網(wǎng)頁版同樣內(nèi)置了 PlanViz 渲染器功能不輸 VS Code。5. 讀懂一張 PLV五個必須看的抓手5.1 先看算子樹從根到葉子的閱讀順序打開一張 PLV第一眼看到的是縱向或橫向的算子樹。根節(jié)點在最上端或最左端葉子節(jié)點在底部或最右端數(shù)據(jù)從葉子往根方向流動。每一層節(jié)點都代表一個具體的操作全表掃描、索引查找、JOIN、聚合、排序、投影等等。我最先看的是根節(jié)點??偤臅r就放在那里它旁邊的耗時條通常很夸張。如果根節(jié)點顯示 85% 的耗時都來自底下某個子樹那問題就不需要繼續(xù)猜了——所有注意力都應(yīng)該投到那個子樹里去。這個閱讀順序看起來簡單但很多人在 PLV 里迷路的唯一原因就是從葉子開始看結(jié)果被一堆細枝末節(jié)的操作帶偏。正確做法永遠是先看根再看最耗時的分支最后才收縮到具體算子。5.2 行數(shù)估算值和實際值相差一個數(shù)量級就要警惕PLV 里每個節(jié)點都標(biāo)注了行數(shù)。這可能是整張計劃里最有價值的數(shù)據(jù)??吹臅r候要區(qū)分兩種行數(shù)估算行數(shù)Estimated Rows和實際行數(shù)Actual Rows。估算行數(shù)是優(yōu)化器基于統(tǒng)計信息推算出來的實際行數(shù)是查詢真正處理的數(shù)據(jù)量。當(dāng)兩者非常接近時說明優(yōu)化器的判斷靠譜執(zhí)行計劃整體可信當(dāng)兩者相差超過一個數(shù)量級時說明統(tǒng)計信息已經(jīng)過期、直方圖不準(zhǔn)確或者存在復(fù)雜表達式讓優(yōu)化器無法準(zhǔn)確估算。這種偏差是性能問題的溫床可能直接導(dǎo)致優(yōu)化器選擇了錯誤的 JOIN 順序或掃描方式。在實際計劃里發(fā)現(xiàn)這類偏差下一步就是刷新相關(guān)表的統(tǒng)計信息比如執(zhí)行ANALYZE相關(guān)操作然后再對比新計劃。很多莫名其妙慢的查詢刷新統(tǒng)計信息后自動就好了。5.3 三類重點算子掃描、Join、聚合算子種類很多但性能診斷中需要聚焦的其實只有三類。第一類是 TABLE SCAN。看到一個大的 TABLE SCAN 節(jié)點時要立刻確認(rèn)表是列存還是行存過濾條件下推到了哪一層以及有沒有做分區(qū)裁剪。最壞的情況是整張幾億行的列存表從頭掃到尾那基本等于把查詢速度的下限拉滿了。第二類是 JOIN???JOIN 類型是 HASH JOIN 還是 NESTED LOOP JOIN然后看驅(qū)動表是哪一邊。通常驅(qū)動表應(yīng)該選小表或過濾后數(shù)據(jù)量小的一方如果順序反了耗時會成倍增長。還要確認(rèn) JOIN 條件是否完備缺了條件的笛卡爾積是性能殺手這是新手最容易踩的坑。第三類是聚合和排序。GROUP BY 的字段數(shù)量直接影響分組開銷字段越少越快排序操作如果出現(xiàn)在大數(shù)據(jù)量節(jié)點上會消耗大量內(nèi)存和臨時空間。三類算子可以整理成一張自查表分析任何執(zhí)行計劃時都可以對著過一遍算子類型關(guān)注點常見性能問題TABLE SCAN列存/行存、過濾下推、分區(qū)裁剪全表掃描、謂詞未下推JOINJOIN 類型、驅(qū)動表順序、條件完備笛卡爾積、驅(qū)動表過大AGGREGATION / SORTGROUP BY 字段數(shù)量、排序數(shù)據(jù)量分組鍵過多、內(nèi)存溢出5.4 謂詞下推、分區(qū)裁剪和隱式轉(zhuǎn)換這三個概念聽起來抽象但它們的共同點是決定了掃描階段到底碰了多少數(shù)據(jù)這是查詢性能的根基。謂詞下推就是 WHERE 條件里的過濾動作能不能提前發(fā)生。理想情況下過濾應(yīng)該在掃描葉子節(jié)點時就把大部分行擋掉而不是把大量數(shù)據(jù)一路傳到上層再過濾。在 PLV 里如果發(fā)現(xiàn)一個很大的過濾條件出現(xiàn)在樹的上層節(jié)點而不是緊貼著掃描節(jié)點就說明下推失敗要對執(zhí)行計劃做進一步分析。分區(qū)裁剪更直白如果表按日期分區(qū)WHERE 里指定了日期范圍執(zhí)行計劃應(yīng)該只掃描對應(yīng)分區(qū)。如果 PLV 顯示掃描了全部分區(qū)說明分區(qū)鍵沒有被有效利用。隱式轉(zhuǎn)換是導(dǎo)致前兩個問題失效的元兇。最典型的表現(xiàn)是 WHERE 條件里對索引列或分區(qū)列套上了函數(shù)或者字段類型不匹配導(dǎo)致數(shù)據(jù)庫悄悄做了類型轉(zhuǎn)換。比如WHERE TO_CHAR(ORDER_DATE) 2024-01-15這會讓數(shù)據(jù)庫放棄日期分區(qū)裁剪和索引掃描老老實實全表掃描。這一類問題在 PLV 上往往不是一眼能看到的名字而是掃描節(jié)點行數(shù)異常偏大這個現(xiàn)象。6. 一個真實案例高頻短查詢的完整排查鏈路6.1 現(xiàn)象每天下午三點準(zhǔn)時變慢接手過一個某數(shù)據(jù)平臺的性能問題現(xiàn)象非常規(guī)律每天下午三點半左右系統(tǒng)開始變慢持續(xù)大約四十分鐘其他時段一切正常。初期的排查方向集中在后臺批處理上因為臨近下班時間通常有匯總?cè)蝿?wù)在跑。但從應(yīng)用監(jiān)控來看批處理按時啟動了數(shù)據(jù)庫的昂貴語句日志里也沒有任何超過 1 秒的查詢監(jiān)控面板上一片祥和用戶卻切切實實在報卡頓。這個矛盾點正是我前面提到的隱形慢 SQL的典型特征。當(dāng)時決定不再猜了直接開 Capture Request Statistics 做全量捕獲。開關(guān)在三點前打開持續(xù)了一個半小時捕獲了一整個異常時段。6.2 捕獲數(shù)據(jù)給出的第一輪證據(jù)捕獲結(jié)束后用前面那套聚合查詢把數(shù)據(jù)拉出來結(jié)果完全出乎意料。Top 排名前四的 SQL 全是 SELECT 語句分別來自同一個訂單查詢接口總執(zhí)行次數(shù)驚人——四類語句加起來在 90 分鐘內(nèi)執(zhí)行了近 2 萬次平均單次 80 毫秒左右。單看單次耗時完全不差但累計總耗時排到了整個系統(tǒng)第一位。順著應(yīng)用名追過去發(fā)現(xiàn)這個接口被前端頁面設(shè)置成了每 3 秒輪詢一次而且一個頁面同時會觸發(fā)多張報表卡片每張卡片都調(diào)這個接口。輪詢頻率高本身沒問題問題在于這四類查詢內(nèi)部都藏著同一個性能隱患——它們對訂單主表的掃描方式非常低效幾億行的列存表每次查詢都從最近的分區(qū)開始掃。6.3 PLV 揭示的根因函數(shù)包裹索引列把其中耗時最高的一條 SQL 導(dǎo)出執(zhí)行計劃在 VS Code 里打開 PLV。算子樹一眼看過去葉子節(jié)點是一個巨大的 TABLE SCAN掃描行數(shù)幾乎是全表量級而根節(jié)點耗時占比高達 70%。過濾條件確實存在但它對一個時間字段做了格式化處理寫成了形如TO_CHAR(ORDER_DATE, YYYY-MM-DD) 2024-06-01的條件。這正是典型的隱式轉(zhuǎn)換問題。時間字段是分區(qū)鍵理論上只需要掃一個分區(qū)但函數(shù)包裹讓優(yōu)化器無法把等值條件轉(zhuǎn)換成范圍掃描最終選擇了掃描大量分區(qū)甚至全表。類似的問題還有把字符型字段轉(zhuǎn)成數(shù)值型再比較同樣會讓索引失效。讀 PLV 到這里根因已經(jīng)非常清楚了。SQL 本身邏輯沒問題是寫法沒有尊重字段的原始類型等于親手把優(yōu)化器的手腳綁住了。6.4 改寫 SQL 之后的效果對比修復(fù)方式是重寫 WHERE 條件把函數(shù)移到等號右側(cè)變成對常量做處理-- 優(yōu)化前 WHERE TO_CHAR(ORDER_DATE, YYYY-MM-DD) 2024-06-01 -- 優(yōu)化后 WHERE ORDER_DATE TO_TIMESTAMP(2024-06-01 00:00:00) AND ORDER_DATE TO_TIMESTAMP(2024-06-02 00:00:00)改完再看執(zhí)行計劃葉子節(jié)點的掃描行數(shù)直接下降到原來的幾十分之一分區(qū)裁剪生效了。對比捕獲數(shù)據(jù)同一接口查詢的平均耗時從 80 毫秒降到 9 毫秒由于執(zhí)行次數(shù)不變該時段的總耗時下降了接近 90%。三天后觀察 CPU 趨勢下午三點半的峰值完全消失。事后復(fù)盤給我的啟示是很多高頻短查詢的優(yōu)化收益被嚴(yán)重低估。一條查詢優(yōu)化掉 70 毫秒聽起來不刺激但乘上每小時幾千次的執(zhí)行頻率就是肉眼可見的 CPU 資源釋放。7. 捕獲與診斷中容易踩的坑7.1 坑一開關(guān)開了就不關(guān)統(tǒng)計表瘋漲Capture Request Statistics 一旦開啟統(tǒng)計表的數(shù)據(jù)量增長很快。高并發(fā)場景下一小時就可能寫入幾十萬條記錄長期不關(guān)會導(dǎo)致_SYS_STATISTICS模式下的表膨脹反過來影響系統(tǒng)性能。我的做法是把開啟操作和關(guān)閉操作寫在同一條運維記錄里并且給捕獲窗口設(shè)置一個明確的結(jié)束時間。比如在操作備注里寫15:00 開啟預(yù)計 17:00 關(guān)閉。如果確實需要長時間分析也要規(guī)劃定期清理統(tǒng)計表的策略避免磁盤和內(nèi)存被歷史數(shù)據(jù)占滿。7.2 坑二排名只看 MAX漏掉高累計消耗分析捕獲數(shù)據(jù)時如果只按 MAX 排序會把注意力引向那些偶爾慢一次的查詢比如月末跑批、定時報表。真正持續(xù)消耗系統(tǒng)資源的往往藏在 SUM 排名的前列。這兩種查詢的優(yōu)化價值完全不同。單次 2 秒、每天跑 5 次的報表查詢優(yōu)化到 1 秒每天省 5 秒單次 50 毫秒、每分鐘跑 200 次的接口查詢優(yōu)化到 25 毫秒每分鐘省 5 秒一天下來節(jié)省的時間是前者的幾十倍。所以不管看哪份報告我都習(xí)慣把 SUM 作為第一排序字段MAX 只作為參考。7.3 坑三把 EXPLAIN PLAN 的估算計劃當(dāng)成實際表現(xiàn)EXPLAIN PLAN輸出的是優(yōu)化器認(rèn)為應(yīng)該怎么跑的計劃屬于預(yù)演性質(zhì)實際執(zhí)行時因為數(shù)據(jù)分布、內(nèi)存、并發(fā)等因素計劃可能完全變樣。我曾經(jīng)犯過這個錯誤優(yōu)化好一條 SQLEXPLAIN PLAN 看起來非常完美但上線后性能沒有提升一查實際執(zhí)行計劃優(yōu)化器運行時選擇了另一條低效的路徑。原因是對某張大表的統(tǒng)計信息嚴(yán)重過期優(yōu)化器在真實環(huán)境中重新評估后放棄了預(yù)演時選定的 JOIN 順序。所以驗證優(yōu)化效果時不要停留在 EXPLAIN PLAN 層面最好拿到實際執(zhí)行計劃對比或者直接觀察捕獲數(shù)據(jù)中該 SQL 的真實耗時變化。PLV 文件如果來自實際執(zhí)行會標(biāo)注實際行數(shù)和耗時可靠性遠高于純估算計劃。7.4 我的習(xí)慣把捕獲做成季度巡檢的一部分這套方法我并不僅用于應(yīng)急排查更多時候用于例行巡檢。每季度挑一個業(yè)務(wù)周期開啟一到兩天捕獲把 Top SQL 清單導(dǎo)出來存檔再和上一輪的結(jié)果對比。哪些新查詢進入了 Top哪些舊查詢的耗時在下滑哪些查詢的執(zhí)行次數(shù)在悄悄上漲都一目了然。這個習(xí)慣幫我在故障發(fā)生前發(fā)現(xiàn)過不少隱患。比如某條老查詢本來平均 20 毫秒連續(xù)兩個季度慢慢漲到了 120 毫秒單次不慢總量卻翻了好幾倍順著統(tǒng)計表一查原來是底層表數(shù)據(jù)量膨脹現(xiàn)有索引選擇性變差。如果等到用戶報障再查可能已經(jīng)是高峰期扛不住的臨界點。我自己最深的體會是診斷 SAP HANA 性能問題Capture Request Statistics 和 PLV 是一對組合拳前者負(fù)責(zé)把真兇從海量請求里撈出來后者負(fù)責(zé)把犯罪動機從算子樹里挖出來。缺了第一步你只是在瞎猜缺了第二步你知道是誰卻不知道怎么改。按這套流程走下來大多數(shù)性能問題都能在半小時內(nèi)給出明確結(jié)論。