[Data Access] DataReader vs. DataAdapter

其實這種 DataReader vs. DataAdapter 的文己經夠多了,隨便 Google 一下就能看到一堆,以往我們接收到的訊息都是 DataReader 會比 DataAdapter 要快,這個說法在早期的 .NET 版本應該適用,不過在較新的 Framework 版本可就不一定適用了。

昨天在 BS 的論壇中看到一個討論,很有意思:http://www.blueshop.com.tw/board/FUM20041006152735ZFS/BRD201207111118546OA.html

其實這種 DataReader vs. DataAdapter 的文己經夠多了,隨便 Google 一下就能看到一堆,以往我們接收到的訊息都是 DataReader 會比 DataAdapter 要快,這個說法在早期的 .NET 版本應該適用,不過在較新的 Framework 版本可就不一定適用了。

為了要證實這一點,我寫了支 Windows Forms 的測試程式,資料庫取材於 AdventureWorksDW (SQL Server 2012),裡面有個 FactProductInventory 資料表,資料筆數為 776,286 筆 (我手上沒有百萬筆級的表格...),然後用下面這支程式來測試:

private void TestingAccessDb()
{
    OleDbConnection connection = new OleDbConnection(
        "Provider=Microsoft.ACE.OLEDB.12.0; Data Source=sample.accdb");
    OleDbCommand cmd = new OleDbCommand("SELECT * FROM FactProductInventory", connection);
    DataTable tableReader = new DataTable();
    DataTable tableAdapter = new DataTable();

    connection.Open();

    // test data reader.
    Stopwatch swDataReader = new Stopwatch();
    Stopwatch swDataAdapter = new Stopwatch();

    swDataReader.Start();

    OleDbDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);
    //tableReader.Load(reader);

    for (int i = 0; i < reader.FieldCount; i++)
        tableReader.Columns.Add(reader.GetName(i), reader.GetFieldType(i));

    while (reader.Read())
    {
        object[] items = new object[reader.FieldCount];
        reader.GetValues(items);
        tableReader.LoadDataRow(items, true);
    }

    reader.Close();

    swDataReader.Stop();           

    swDataAdapter.Start();

    OleDbDataAdapter adapter = new OleDbDataAdapter(cmd);
    adapter.Fill(tableAdapter);

    swDataAdapter.Stop();

    connection.Close();

    this._testCounters.Add(new TestCounter()
    {
        RowCount = tableAdapter.Rows.Count,
        DataAdapterLoadSeconds = swDataAdapter.Elapsed.TotalSeconds,
        DataReaderLoadSeconds = swDataReader.Elapsed.TotalSeconds
    });
}

private void TestingSqlDb()
{
    SqlConnection connection = new SqlConnection(
        "initial catalog=AdventureWorksDW2012_Data; integrated security=SSPI");
    SqlCommand cmd = new SqlCommand("SELECT * FROM FactProductInventory", connection);
    DataTable tableReader = new DataTable();
    DataTable tableAdapter = new DataTable();

    connection.Open();

    // test data reader.
    Stopwatch swDataReader = new Stopwatch();
    Stopwatch swDataAdapter = new Stopwatch();

    swDataReader.Start();

    SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess);

    tableReader.BeginLoadData();

    for (int i = 0; i < reader.FieldCount; i++)
        tableReader.Columns.Add(reader.GetName(i), reader.GetFieldType(i));

    while (reader.Read())
    {
        object[] items = new object[reader.FieldCount];
        reader.GetValues(items);
        tableReader.LoadDataRow(items, true);
    }

    tableReader.EndLoadData();

    reader.Close();

    swDataReader.Stop();

    swDataAdapter.Start();

    SqlDataAdapter adapter = new SqlDataAdapter(cmd);
    adapter.Fill(tableAdapter);

    swDataAdapter.Stop();

    connection.Close();

    this._testCounters.Add(new TestCounter()
    {
        RowCount = tableAdapter.Rows.Count,
        DataAdapterLoadSeconds = swDataAdapter.Elapsed.TotalSeconds,
        DataReaderLoadSeconds = swDataReader.Elapsed.TotalSeconds
    });
}

Access 的結果是:

image

SQL Server 的結果是:

image

其實只有跑一次是不準的,我們用同一個程式跑十次,結果分別是:

image

image

注意到了吧,DataAdapter 硬是比 DataReader 要快。

在網頁上也差不多:

image

深入探討原因的話,我認為有兩個因素:

  1. DataReader 的 CommandBehavior 的設定,如果使用 SequentialAccess 的話會在順序存取時加快速度,DataAdapter 內部也會自動套用這個設定。
  2. DataAdapter 天生就是用來填 DataTable/DataSet,所以在這部份有做最佳化,但 DataTable.Load() 沒有特別做最佳化,所以如果用 DataTable.Load() 的話,速度一定輸給 DataAdapter.Fill()。

但是在實務上,我還是推薦 DataReader,原因是:

  1. DataReader 的自由度高,可以控制是否要使用游標所在的資料列,但 DataTable 是不論是否要都會載入。
  2. DataReader 較省記憶體,只會儲存目前的游標,但 DataTable 會在記憶體中儲存所有的資料。
  3. DataReader 透過適當的處理,可以直接和 DTO/entity 搭配,但 DataTable 還要再多一道轉型程序。

若一定要用 DataReader 和 DataTable.Load(),那麼可以考慮改用 DataTable.BeginDataLoad()/EndDataLoad() 加上 DataTable.LoadDataRow() 來做,可以加快載入速度。

Reference:

http://stackoverflow.com/questions/1676753/sqldataadapter-vs-sqldatareader

http://msdn.microsoft.com/zh-tw/library/ms254931.aspx