.NET 應用程式常見的效能問題,多半不是語法錯誤,而是資源管理疏失、環境配置不當導致的「慢性自殺」。本文整理四大經典陷阱,搭配真實案例與修正模式,助你建立穩健的生產級代碼習慣。
Public Shared Function GetOEMPN(psPN As String, ByRef OEMPN As String) As BSResult
Dim clsOraDb As New clsOraClienDb
clsOraDb.Open(connStr) ' 開啟連線
' ... 查詢邏輯 ...
If dtResult.Rows.Count > 0 Then OEMPN = dtResult.Rows(0)("FRUNO").ToString()
clsResult.ResultID = 1
Return clsResult
' 缺少 clsOraDb.Close()!
Catch ex As Exception
Return clsResult ' 例外路徑也沒關閉
End Try
End Function
連線池預設上限約 100 條,Web 應用高併發下幾分鐘耗盡,拋出 Timeout expired。更嚴重者在迴圈中重複 Open:
For Each row In table.Select()
clsOraDb.Open(connStr) ' 每次迴圈新開連線,災難性後果
' ...
Next
Try
clsOraDb.Open(connStr)
' ... 業務邏輯 ...
clsOraDb.Close() ' 正常路徑
Return result
Catch ex As Exception
clsOraDb.Close() ' 例外路徑
Throw
Finally
' 雙重保險
If clsOraDb IsNot Nothing Then clsOraDb.Close()
End Try
Using db = New clsOraClienDb()
db.Open(connStr)
' ... 自動呼叫 Dispose/Close ...
End Using
DataTable、DataSet、DataReader 等 ADO.NET 物件佔用非託管資源,若僅靠 GC 回收,會造成 Gen 2 壓力、記憶體持續增長。
For Each row In table.Rows
Dim ds As New DataSet() ' 迴圈內重複 new,舊物件未釋放
ds = GetData()
' ...
Next
Dim ds As New DataSet()
Try
For Each row In table.Rows
ds.Dispose() ' 每次重用前先釋放
ds = GetData()
' ...
Next
Catch ex As Exception
Throw
Finally
ds.Dispose()
End Try
原則:在 Try 區塊外建立、Catch 區塊釋放、Finally 區塊再次確認釋放。
Visual Studio 預設 Web.config:
<compilation debug="true" />
<trace enabled="true" />
Debug 建置特性:
✅ 禁用 JIT 最佳化
✅ 包含完整 PDB 符號表
✅ 啟用編譯時期偵錯追蹤
✅ 不合併/壓縮靜態資源
實測:刀片伺服器 16GB RAM,Debug 模式運行 2 天,IIS 工作進程 w3wp.exe 記憶體突破 2.5GB,回收後降至 1.5GB。切換 Release + debug="false" 後穩定在 800MB 以下。
Web.config:debug="false"、trace enabled="false"aspnet_compiler -p "src" -v "/" "deploy"案例:產量增加後 CPU 持續 90%+,回收集區暫時緩解。微軟支援分析發現:
• 無單一進程佔用極高 CPU
• 大量 DLL 為 Debug 建置
• 資料庫請求併發過高(短連線、頻繁查詢)
<applicationPool
maxProcesses="1"
cpuLimit="80000" <!-- 80% CPU 限制 -->
requestQueueLimit="5000" <!-- 佇列緩衝 -->
idleTimeout="00:20:00"
periodicRestartTime="23:00:00" <!-- 定期重啟釋放資源 -->
periodicRestartMemory="1000000" <!-- 1GB 記憶體重啟 -->
/>
關鍵認知:
• 每個集區 = 一個 w3wp.exe 進程,用戶模式虛擬位址空間 2GB,實際安全上限約 1.5GB(碎片化)。
• 多集區隔離記憶體,但無法限制單集區 CPU 使用率(僅能監控)。
| 檢查點 | 關鍵動作 | 驗證方式 |
|---|---|---|
| 資料庫連線 | Using/Finally 保證關閉、迴圈外開啟 | 效能計數器 Connection Pool 監控 |
| 大型物件 | Try 外建立、Catch/Finally 釋放、迴圈內 Dispose 重用 | 記憶體分析器 (dotMemory/PerfView) |
| 部署模式 | Release + debug=false + trace=false | 檢查 Web.config、IIS 模組清單 |
| 集區設定 | 定期重啟、記憶體上限、佇列緩衝 | 效能日誌、CPU/記憶體趨勢圖 |
「寫出能跑的代碼」是入門,「寫出能在生產環境穩定運行三年的代碼」才是專業。這些不是「茴字幾種寫法」的風格問題,而是關係系統存亡的工程紀律。