← 回首頁 WHEN EVERYONE HAS A THEORY, WHAT SETTLES IT?

當每個人都有一套說法的時候,誰說了算?

系統治理 · 2026.05 – 08 · 根因分析、優化排程、實測驗收

系統升級到 .NET 9 之後,資料庫 CPU 開始間歇性飆高,結帳偶爾逾時。團隊很快提出幾種合理解釋:可能是新框架、流量成長,也可能是結帳彼此鎖住。更棘手的是,其中兩條效能問題已經拖了一年以上,各自優化過一到兩次;每次都有改善,卻始終沒有根治。

我要先回答一個更具體的問題:到底是哪幾支查詢在吃 CPU? 我用資料庫證據逐條定位根因,再把 21 項優化依風險與效益排成三波,交由工程團隊執行;每一波都以前後實測驗收,再決定是否往下走。

0× 問卷查詢加速(153 秒 → 7 秒)
0× 結帳 SQL 加速(3,000ms → 0.13ms)
0.0× 批量列印加速(9.3 分 → 1.3 分)
0 結帳逾時
PERFORMANCE TRIAGE MEASURED, NOT CLAIMED 問卷查詢 153s 7s 結帳 payload SQL 3,000ms 0.13ms 批量列印物流單 9.3min 1.3min 來源 —— Azure Query Store / Mongo 列印 log
EVERYONE HAD A THEORY DB CPU AFTER THE .NET 9 UPGRADE 新框架? 流量? 互卡? 到底哪幾支查詢在吃 CPU?不是猜,是查。

MY ROLE

先說清楚我的角色

程式碼由團隊的工程師負責,我沒有把他們的成果算在自己身上。 我的責任是找出根因、排定修復順序,並在上線後用實測證明問題確實改善。這個案例記錄的是:一位沒有工程背景的 PM,如何在沒有資深 DBA 的團隊裡,建立一套足以推動技術決策的證據。

WHO DID WHAT SAID UP FRONT 工程師 寫了程式碼 我 根因 · 優先序 · 實測驗收 沒有工程背景的 PM、沒有資深 DBA 的公司 —— 靠證據說話

CHALLENGE 01

如何證明一個大家都相信的原因,其實不存在?

當時最主流的假設是「結帳互卡」:兩張訂單同時進來,彼此鎖住。它合理,也很難反駁,因為任何反證都可以用「只是剛好沒發生」解釋。我因此不用單次測試下結論,而是找了三層互相獨立的證據:

  1. 程式碼:grep 整條結帳路徑,BeginTransaction 出現 0 次。沒有交易就沒有互卡的條件。
  2. 資料庫即時狀態:查當下的阻塞情形,0 個 blocking、0 個鎖等待。
  3. 歷史紀錄:Query Store 拉七天,結帳相關的鎖等待累計為 0。

三層結果都不支持互卡假設。要排除一個原因,不能只說「我測不到」;必須用不同來源的證據交叉確認。先把最受相信的假設排除,團隊才願意把注意力移向真正消耗 CPU 的查詢。

DEADLOCK: RULED OUT ×3 THREE INDEPENDENT LAYERS 程式碼 · grep 結帳路徑 BeginTransaction: 0 即時 DB · 阻塞 + 鎖等待 當下: 0 Query Store · 7 天 結帳鎖等待: 0 架構上 不可能發生 證明不存在要三種獨立方法 ——「我測不到」不是結論

CHALLENGE 02

慢,要慢到什麼程度才值得動手術?

我一開始想用自己的秒表證明「結帳很慢」,最後把這份數據丟掉。 辦公室裡量到的秒數,只代表當下的網路與設備,不足以支撐「修改付款路徑」這種高風險決策。

我改為逐筆整理一段期間內客服收到的結帳異常事件,再對照資料庫 CPU 時間軸。六起客訴全都落在 CPU 飆高的時段。這份證據一端是客人的真實問題,另一端是系統指標;兩者在時間上吻合,至少足以證明這不是辦公室裡的主觀體感。

挑證據跟挑查法一樣重要。我要證明的不是「我覺得慢」,而是「客人遇到問題的時間,確實與系統指標吻合」。

CUSTOMER PAIN × CPU TIMELINE 6 OF 6 INSIDE SPIKE WINDOWS ● 客訴 不是我的碼表 —— 是真實客訴,落在真實的資源飆高上

ROOT CAUSES

逐條定位根因

問卷查詢(153 秒):瓶頸不在 SQL 寫法,而在資料結構。 舊做法用 WHERE Reply LIKE '%分數%',每次查詢都要掃過完整評論文字,因為分數埋在 JSON 裡,直到讀取時才計算。這也解釋了前兩次「加 view 欄位」與「加快取」為何只能治標。這次改成寫入時計算,將分數拆成獨立欄位與資料表,再用 dual-write 與 backfill 分階段切換,才真正處理到底層問題。

結帳 payload SQL(3,000ms):一個 OR 造成兩萬倍差距。 LINQ 寫成 Where(w => w.OrdersId == X || w.AddOrderId == Y),讓資料庫無法使用任一索引,只能全表掃描。修正本身很小:拆成兩個能各自走索引的查詢,再合併結果。執行時間從 3,000ms 降到 0.13ms。

批量列印物流單(9.3 分):驗收直接用真實的列印紀錄。 比對 Mongo 的列印 log,改版前 721 次、改版後 486 次真實列印,中位數從 4.8 秒降到 1.1 秒。

THREE ROOT CAUSES AND WHY EARLIER FIXES COULDN'T STICK 問卷 · 153 秒 LIKE '%分數%' —— 每次讀取全掃 → 寫入時算好 · 獨立欄位 結帳 · 3,000ms OR 跨兩個欄位 —— 走不到索引 → 兩個走索引的查詢 + 合併 批量列印 · 9.3 分 批次管線瓶頸 → 以 Mongo 列印 log 驗證

CHALLENGE 03

21 項優化,怎麼改才知道是哪項有效?

完整問題清單有 21 項,但一次全改,結果就無法歸因。 系統即使變快,也分不出哪項修正真正有效、哪項只是同時上線。我因此按風險與預期效益分成三波;每波上線後先量測,確認效果再推下一波。

結帳這條動到付款路徑,另外走了完整的測試矩陣:五條付款路徑(網頁綁卡、網頁 LinePay、網頁重新付款、App LinePay、App 綠界)逐一驗過才上正式站。

21 FIXES, THREE WAVES CHANGE EVERYTHING AT ONCE = ATTRIBUTE NOTHING 第一波 量測 第二波 量測 第三波 結帳改動:完整 UAT 矩陣 · 5 條付款路徑逐一驗過 每一波上線後量一次 —— 因果成立才推下一波

RESULTS

結果

項目改善前改善後
問卷查詢最慢單次153 秒7 秒
問卷查詢月總 CPU推估約 44,000 CPU 秒39.8 CPU 秒(實測)
結帳 payload SQL約 3,000ms0.13ms
批量列印(最慢一次)9.3 分1.3 分
單筆列印中位數4.8 秒1.1 秒
結帳逾時偶發0

上線後實測 8 筆綁卡結帳,平均 5.06 秒、零逾時。修法前同類測試會直接撞上 30 秒逾時。

每個數字都標明來源:效能數據來自 Azure Query Store,列印數據來自 Mongo 列印 log。唯一的推估值是「改善前月總 CPU」,因為原始紀錄已超過 30 天保留期。我在報告裡將它明確標為推估,只用來判斷量級,不與實測值混在一起。

RESULTS MEASURED, NOT CLAIMED 22× 問卷查詢 ~20,000× 結帳 SQL 7.2× 批量列印 0 逾時 來源:Azure Query Store · Mongo 列印 log · 唯一的推估值有標明

AUTOMATION

把判斷寫進自動化

專案收尾後,我把這半年累積的診斷方法寫成規則,交給一支常駐的監控機器人。 資料庫 CPU 告警一進來,它就會抓出當下最耗 CPU 的查詢、完成初步診斷,再把分析報告寄到我的信箱。

其中最重要的一條規則,是我自己踩過的坑:先看 CPU 消耗,不要只看執行時間。 查詢跑得久,可能是在等鎖或等 IO,它可能是受害者,不是兇手;真正需要優先處理的是 CPU 時間高的查詢。把這個判斷寫進機器人後,下一次問題發生時,診斷不必等我到場才開始。對我來說,這才是效能治理:不只修掉眼前的查詢,也讓之後的排查更快啟動。

JUDGMENT → AUTOMATION THE NEXT INCIDENT DIAGNOSES ITSELF CPU 告警 機器人抓出最耗 CPU 的查詢 初步 診斷 寄信 報告 規則一:看 CPU 消耗,不看執行時間 跑得久的查詢可能是受害者,真兇是吃 CPU 的那支。 我的判斷不用我在場也會執行

ENCORE

同樣的症狀,不一樣的兇手

兩個月後,「結帳慢」再次被回報。如果直接沿用上次的答案,認定又是 SQL 寫法,就會走錯方向。重新拆解後,這次是兩個不同原因疊加:結帳中繼頁面寫死了一段固定秒數的空等,同時資料庫統計值過期,又放大了 IO 的延遲。

症狀會重複,根因不一定相同。 上一次的結論,只能成為下一次排查的假設之一。

SAME SYMPTOM, DIFFERENT CULPRIT TWO MONTHS APART 「結帳 很慢」 5 月 · OR 寫法 → 走不到索引 → 全表掃描 8 月 · 寫死的空等 + 統計值過期 症狀會重複,根因不會忠誠 —— 上次的答案只是這次的第一個假設

LESSONS

學到的東西

  • 「優化過」不等於「解決了」。 前兩次優化確實有效,也是當時可行的選擇;但真正瓶頸在資料結構,修改風險高、估時超過一個月,因此一直被保留。把這段脈絡寫清楚,比只宣告這次成功更重要。
  • 報告必須標示可信度。 我把每個數字分成實測或推估,並附上口徑。清楚承認資料限制,反而讓其餘實測結果更可信。
  • 分清楚團隊成果與自己的貢獻。 程式由工程師完成;我負責讓躺了一年以上的三條問題有明確起點、修復順序與驗收證據。定位問題本身,就是這個角色的產出。
THREE RULES I KEEP 「已優化」 ≠「已解決」 標明你的 可信度 定位問題 就是產出 每個數字標明實測或推估 —— 這讓實測的更有份量

STACK

技術架構

Azure Query Store、T-SQL、SQL Statistics、MongoDB、Azure CLI、Application Insights。診斷方法與判斷規則已沉澱為常駐監控機器人與內部 playbook。

EVIDENCE SOURCES QUERY STORE T-SQL · STATISTICS MONGO 列印 log AZURE CLI APP INSIGHTS 方法沉澱為監控機器人 + 內部 playbook