下單快發貨慢:一個 JOIN SQL 引發 SqlClient 讀取數據慢的奇特問題

更新:這個問題是 System.Data.SqlClient 的一個 bug 引發的,詳見 坑暗花明:又遇 .NET Core 中 System.Data.SqlClient 查詢緩慢的問題html

最近遇到一個很是奇特的問題,在一個 ASP.NET Core 項目中從 SQL Server 2008 R2 中查詢獲取 100 條記錄居然耗時 10 多秒,若是是查詢自己慢,那到不是什麼奇特的問題。數據庫

說它很是奇特是由於耗時主要發生在 SqlDataReader 讀取數據時服務器

2019-04-04 21:31:58.546 [Information] Executed DbCommand ("2,656"ms)
...
2019-04-04 21:32:10.690 [Debug] A data reader was disposed.

進一步測試發現網絡

查詢獲取 1 條數據庫記錄,耗時在 230ms 左右  
查詢獲取 10 條數據庫記錄,耗時在 1.6s-2s 之間
查詢獲取 100 條數據庫記錄,耗時在 12s-22s 之間

開始懷疑是 EF Core 的問題,經過在 EF Core 源碼中打點,定位到耗時發生在 _dataReader.ReadAsync 處post

while (await _dataReader.ReadAsync(cancellationToken))
{
    _buffer.Enqueue(_valueBufferFactory.Create(_dbDataReader));
}

_dataReader.ReadAsync 實際調用的是 System.Data.SqlClient 中的 SqlDataReader.ReadAsync 方法。測試

一次 ReadAsync 讀取一行記錄,經過在 SqlClient 的源代碼中打點記錄時間戳發現,在 100 次一行一行讀取中,其中有幾回讀取會出現延遲,好比某一次 13 秒延遲,100 次讀取中出現了 5 次讀取延遲 —— 2s + 3s + 3s + 2s + 3s = 13s 。url

通過在 System.Data.SqlClient 源代碼中無數次打點記錄時間戳最終定位到延遲發生在  SNIPacket.ReadFromStreamAsync()  方法中  stream.ReadAsync()  時spa

Console.WriteLine($"Entering stream.ReadAsync() at {DateTime.Now}");
stream.ReadAsync(_data, 0, _capacity, CancellationToken.None).ContinueWith(t =>
{
    Console.WriteLine($"stream.ReadAsync().ContinueWith at {DateTime.Now}");
    //...
}

stream 對應的是 NetworkStream ,延遲發生在網絡傳輸過程當中,與 SqlClient 不要緊。3d

TCP 抓包發現 SQL Server 服務器發送的數據到達就延遲了。code

因而只能將懷疑對象鎖定在 SQL Server 數據庫層面。

對應的 SQL 查詢語句涉及 4 張表,FROM 一張表(表A), JOIN 三張表(LEFT JOIN 表B,LEFT JOIN 表C ,INNER JOIN 表D),表A有1000多萬條記錄,表C有1000多萬條記錄,查詢時按表A的主鍵排序,表A的彙集索引建在時間字段上,沒有建在主鍵上。

SELECT ...
FROM TableA
LEFT JOIN TableB ON [TableA].[Id] = [TableB].[EntryID]
LEFT JOIN TableC ON [TableA].[Id] = [TableC].[EntryID]
INNER JOIN TableD  ON [TableA].[BlogID] = [TableD].[BlogID]
WHERE ([TableA].[Id] >= @__startId_0)

並非全部查詢都出現這個問題,當 @__startId_0 小於必定值時會出現。

後來嘗試將  LEFT JOIN TableC 改成 INNER JOIN TableC ,問題居然消失了,但進一步測試發現當  @__startId_0  再小到必定值問題又會出現。

既然問題與 JOIN TableC 有關,那乾脆不進行 JOIN ,單獨查詢 TableC ,而後將在 C# 代碼中將查詢的結果合併進行,這樣改進了,查詢獲取 100 條記錄只需 200 多毫秒。

這個奇特的問題就這樣用一個簡單粗暴有效的方法臨時解決了。

對於這個問題的根本緣由,懷疑與 TableA 沒有把彙集索引建在 Id 字段上有關,但目前無法修改彙集索引進行驗證,之後再找機會驗證。

相關文章
相關標籤/搜索