ocean



「當我們只想確認某人是否出現過,沒必要翻遍整本通訊錄。」

在資料庫的世界裡,我們經常會遇到一個簡單卻重要的問題:資料是否存在?

不論是確認某個用戶是否曾下單、某篇文章是否已發佈,這些判斷存在與否的查詢看似簡單,卻往往決定了系統的效能與延遲。尤其當資料表龐大、條件複雜時,使用錯誤的寫法可能導致嚴重的效能問題。

今天,我們就從 SQL 與 EF Core 的角度,來聊聊「資料是否存在」這件事。


🌊 SQL 寫法:COUNT vs EXISTS

如果只是想知道「是否有訂單」,許多人會這樣寫

1
2
3
4
IF (SELECT COUNT(*) FROM Orders WHERE CustomerID = 'ALFKI') > 0
BEGIN
PRINT '有訂單'
END

但其實效能更佳的寫法是使用 EXISTS

1
2
3
4
IF EXISTS (SELECT 1 FROM Orders WHERE CustomerID = 'ALFKI')
BEGIN
PRINT '有訂單'
END

SELECT 1 是慣用寫法,實際上裡面選什麼都不重要,重點是條件判斷。

📈 效能差異

  • COUNT(*) 會掃描整個資料表,計算總筆數,即使只想知道「有沒有資料」。
  • EXISTS 一旦找到第一筆符合條件的資料就會停止搜尋,大幅減少 CPU 與 I/O 成本。

這種差異在資料量大或查詢條件複雜時,會更加明顯。



🌊 指標

指標 COUNT(*) EXISTS
Actual Rows 可能掃到數萬筆 可能只需 1 筆
Logical Reads 高,掃描整個資料頁面 少,只讀到一筆就停
Elapsed Time 隨資料量增加而增加 幾乎固定且低

1️⃣ Actual Rows(實際資料列數)

🔍 定義:查詢執行過程中,實際傳遞或處理的資料列(rows)數量。

在 Execution Plan 中,與 Estimated Rows 比對,能看出是否估算準確。如果 Estimated Rows 和 Actual Rows 相差很大,表示 SQL Server 預估錯誤,可能導致選錯執行計畫。可以驗證查詢是否處理了大量不必要的資料(如 COUNT(*))。分析是否有「過度資料處理」的問題。


2️⃣ Logical Reads(邏輯讀取)

🔍 定義:SQL Server 讀取資料頁面(data pages)的次數,不管這些頁面來自記憶體(Buffer Pool)還是磁碟。

1 個資料頁(Data Page)= 8KB。是衡量查詢I/O 成本最關鍵的指標之一。使用 SET STATISTICS IO ON 可以查詢。測試哪種語法能減少資料頁掃描次數。判斷是否有索引支援查詢條件。觀察是否使用 Index Seek vs Scan。


3️⃣ Elapsed Time(耗時)

🔍 定義:從查詢開始執行到結束所花費的時間(以毫秒為單位)。

使用 SET STATISTICS TIME ON 可取得。包含 CPU Time(實際運算時間)與 IO Wait、Network 等延遲。真正觀察查詢「體感」效能是否良好。用於比較不同寫法在實際環境的速度差異。



🌊 EF Core 實作:Count vs Any

1
2
3
4
5
6
7
8
public async Task AnyTest()
{
Console.WriteLine("== Any ===");
await _adventureWorks2022DbContext.Rds.AnyAsync();

Console.WriteLine("== COUNT ===");
await _adventureWorks2022DbContext.Rds.CountAsync();
}

若你只是想知道「有沒有資料」,建議使用 AnyAsync(),因為:

  • Any() 對應 SQL 的 EXISTS
  • Count() 對應 SQL 的 COUNT(*)

MarkExists

複雜查詢範例

即便是有 JOIN 的複雜條件查詢,只要不關心實際內容,只想知道「有沒有資料」,也可以用 Any()

1
2
3
4
5
6
7
8
9
var query = (from shopCategorySalepage in _webStoreDbReadOnlyContext.ShopCategorySalePage
join Salepage in _webStoreDbReadOnlyContext.SalePage
on shopCategorySalepage.ShopCategorySalePage_SalePageId equals Salepage.SalePage_Id
where shopCategorySalepage.ShopCategorySalePage_ValidFlag == true
&& shopCategorySalepage.ShopCategorySalePage_CategoryId == shopCategoryId
&& Salepage.SalePage_ValidFlag == true
select 1);

return query.Any();

這樣就能轉換成效能優化的 EXISTS 查詢,而不需實際撈取資料內容。

🌊 結語

有時,我們不是要看清楚每一筆資料的模樣,而只是想知道——它在不在?

在資料量龐大的現實世界中,「存在」這個問題,該用最輕巧的方式來確認。EXISTS 與 Any() 就是為了這樣的需求而生的工具。