首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

.NET 深度學習筆記 (1):DataSet vs SqlDataReader 效能差異深度剖析與實測數據

     編輯:關於.NET

DataSet 與 SqlDataReader 的效能比較是經典面試題,但網路多為理論推論缺乏實測。本文建立百萬筆測試資料庫,實測不同資料量下的查詢耗時,並反編譯 .NET Framework 原始碼分析底層實作差異,還原真相。

主流觀點快速回顧

實驗環境

特性SqlDataReaderDataSet
連線模式連線導向 (Connected)離線模式
記憶體佔用單行緩衝全量載入記憶體
讀取方向唯讀、順向隨機存取、可修改
協作物件SqlCommandSqlDataAdapter
項目規格
CPUIntel T2300 1.66GHz
記憶體1GB DDR2
OSWindows Server 2003
DBSQL Server 2005 Enterprise
資料量1,000,000 筆測試資料
表結構id (PK), name, birthday, height, sex, address, lastlogintime

測試代碼核心邏輯

DataSet 版本

using (var conn = new SqlConnection(connStr))
{
    var sw = Stopwatch.StartNew();
    conn.Open();
    var da = new SqlDataAdapter(query, conn);
    sw.Stop(); // 連線+建立Adapter時間
    
    sw.Restart();
    var ds = new DataSet("test");
    da.Fill(ds);  // 核心:填充DataSet
    sw.Stop();
    Console.WriteLine($"DataSet Fill: {sw.ElapsedMilliseconds}ms");
}

SqlDataReader 版本

using (var conn = new SqlConnection(connStr))
{
    var sw = Stopwatch.StartNew();
    conn.Open();
    var cmd = new SqlCommand(query, conn);
    sw.Stop();
    
    sw.Restart();
    using (var reader = cmd.ExecuteReader())
    {
        while (reader.Read()) { /* 僅讀取不處理 */ }
    }
    sw.Stop();
    Console.WriteLine($"DataReader Read: {sw.ElapsedMilliseconds}ms");
}

實測結果 (毫秒)

關鍵發現:DataSet 填充階段非線性增長

  • 小資料量 (<1,000 筆):**差異可忽略**
  • 中大資料量 (>10,000 筆):**DataSet 顯著劣於 DataReader**
  • 原因:DataSet 需建立 DataTable、DataColumn、DataRow 物件樹、維護關聯與條件約束、計算索引

原始碼層面深度剖析 (Reflector 反編譯)

SqlDataAdapter.Fill() 核心流程

protected virtual int Fill(DataSet dataSet, string srcTable, IDataReader dataReader, ...)
{
    // 參數驗證...
    var container = DataReaderContainer.Create(dataReader, ...);
    // 關鍵:委託給 FillFromReader
    return this.FillFromReader(dataSet, null, srcTable, container, ...);
}

發現:SqlDataAdapter.Fill 內部依賴 IDataReader (SqlDataReader) 讀取資料,再將每行資料轉換為 DataRow 加入 DataTable。DataSet 效能較差的根因在於資料轉換與物件建構開銷,而非讀取本身。

SqlCommand.ExecuteReader() → TDS 協議處理

internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, ...)
{
    // TDS 封包解析迴圈
    byte token = stateObj.ReadByte();
    switch (token)
    {
        case 0xA4: // TABLE_NAME token
            dataStream.TableNames = this.ProcessTableName(...); break;
        case 0xA5: // COL_METADATA token
            dataStream.SetMetaData(this.ProcessColInfo(...)); break;
        case 0xD1: // ROW token
            this.ProcessRow(stateObj._cleanupMetaData, ..., stateObj); break;
        case 0xFD/0xFE/0xFF: // DONE tokens
            this.ProcessDone(...); break;
    }
}

SqlDataReader 直接解析 TDS (Tabular Data Stream) 協議封包,逐行產生結果,零中間物件分配。

修正結論:比較對象應為「填充策略」而非「讀取器」

原標題比較有誤導性:

  • SqlDataReader:資料讀取器 (茶杯)
  • DataSet:資料容器模型 (茶壺 + 茶杯 + 茶盤)
  • SqlDataAdapter:填充器 (倒茶動作)

真實比較應為:SqlDataReader 直接讀取 vs SqlDataAdapter.Fill 透過 DataReader 填充 DataSet。後者額外負擔:DataTable 建構、DataRow 建立、條件約束驗證、索引維護、事件觸發。

實務選型決策矩陣

查詢條件筆數DataReaderDataSet差距倍數
id <= 1100-
id <= 1010111x
id <= 100100231.5x
id <= 1,0001,000561.2x
id <= 10,00010,0008506.25x
場景推薦理由
唯讀、順向、大量資料、報表/匯出SqlDataReader最低記憶體、最高吞吐
需隨機存取、關聯查詢、離線操作、資料繫結 UIDataSet豐富物件模型支援
單筆/少量資料、簡單 CRUD任一均可差異可忽略,優先開發效率
高併發 Web API、微服務SqlDataReader / Dapper降低 GC 壓力、延遲

現代替代方案

  • Dapper:微型 ORM,直接對應 SqlDataReader 到 POCO,效能接近原生
  • Entity Framework Core:追蹤變更、LINQ 查詢,開發效率優先
  • SqlBulkCopy:大量寫入專用,繞過 EF/ADO.NET 管線

工具無絕對優劣,理解底層機制、匹配業務場景、測試驗證才是架構師的核心能力。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved