在 ASP.NET MVC 專案中,若需頻繁對約 6.5 萬筆藥品資料進行名稱與拼音碼的下拉式模糊查詢,將資料載入記憶體是提升回應速度的常見策略。本文記錄一次真實的效能調優歷程,展示如何從基礎 LINQ 出發,逐步引入預處理、並行查詢(PLINQ),最終以 Parallel.For 搭配並行度控制,在高併發環境下穩定服務的完整過程。
public class DrugInfo
{
public int DrugNameId { get; set; }
public string DrugName { get; set; }
public string DrugSearchCode { get; set; }
// 優化後新增的小寫快取欄位
public string LowerDrugName { get; set; }
public string LowerDrugSearchCode { get; set; }
}
最直觀的寫法是在查詢時即時轉小寫比對:
var sw = Stopwatch.StartNew();
key = key.ToLower();
var result = cacheList
.Where(m => m.DrugName.ToLower().Contains(key)
|| m.DrugSearchCode.ToLower().Contains(key))
.ToList();
sw.Stop();
Console.WriteLine($"耗時:{sw.Elapsed.TotalMilliseconds:F2} ms");
實測平均約 35 ms。每次查詢都要對 6.5 萬筆資料執行兩次 ToLower() 並建立新字串,CPU 開銷明顯。
在資料載入記憶體時,一次性計算並儲存小寫版本:
// 資料初始化時
foreach (var item in cacheList)
{
item.LowerDrugName = item.DrugName.ToLower();
item.LowerDrugSearchCode = item.DrugSearchCode.ToLower();
}
// 查詢時
var sw = Stopwatch.StartNew();
key = key.ToLower();
var result = cacheList
.Where(m => m.LowerDrugName.Contains(key)
|| m.LowerDrugSearchCode.Contains(key))
.ToList();
sw.Stop();
實測平均降至 16 ms,效能提升超過 50%。雖然每筆資料多佔兩個字串欄位,但在 6.5 萬筆規模下記憶體增加極小,極具性價比。
.NET 4.0 引入的 PLINQ 可自動利用多核心:
var result = cacheList
.AsParallel()
.Where(m => m.LowerDrugName.Contains(key)
|| m.LowerDrugSearchCode.Contains(key))
.ToList();
單一請求平均約 10 ms。看似完美,但部署至 IIS 後發現嚴重問題:單一請求即佔用大量執行緒,200 併發下應用程式集區直接假死。
PLINQ 的 WithDegreeOfParallelism 會**強制**分配指定數量執行緒,且不動態調整;而 Parallel.For 搭配 ParallelOptions.MaxDegreeOfParallelism 則是**上限**,TPL 會視執行緒池負載彈性調度,極適合多使用者共存的 Web 環境。
var bag = new ConcurrentBag<DrugInfo>();
Parallel.For(0, cacheList.Count,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
i =>
{
var item = cacheList[i];
if (item.LowerDrugName.Contains(key)
|| item.LowerDrugSearchCode.Contains(key))
{
bag.Add(item);
}
});
var result = bag.ToList(); // 若需排序再補 OrderBy
| 特性 | PLINQ WithDegreeOfParallelism | Parallel.For MaxDegreeOfParallelism |
|---|---|---|
| 執行緒分配 | 啟動時**固定分配**指定數量 | **上限控制**,TPL 視負載動態調度 |
| 適用場景 | 獨佔運算資源、級聯查詢需同步 | Web 多使用者、共享執行緒池 |
| 風險 | 高併發易耗盡執行緒池 | 低,自動避免過度佔用 |
優化並非單純追求單請求極致速度,在生產環境中「可控的並行」遠比「無限制的並行」更有價值。本案例中,預先小寫化奠定基礎,Parallel.For + MaxDegreeOfParallelism 兼顧吞吐與穩定,最終以極小代價解決了生產事故。