其實這種 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 的結果是:
SQL Server 的結果是:
其實只有跑一次是不準的,我們用同一個程式跑十次,結果分別是:
注意到了吧,DataAdapter 硬是比 DataReader 要快。
在網頁上也差不多:
深入探討原因的話,我認為有兩個因素:
- DataReader 的 CommandBehavior 的設定,如果使用 SequentialAccess 的話會在順序存取時加快速度,DataAdapter 內部也會自動套用這個設定。
- DataAdapter 天生就是用來填 DataTable/DataSet,所以在這部份有做最佳化,但 DataTable.Load() 沒有特別做最佳化,所以如果用 DataTable.Load() 的話,速度一定輸給 DataAdapter.Fill()。
但是在實務上,我還是推薦 DataReader,原因是:
- DataReader 的自由度高,可以控制是否要使用游標所在的資料列,但 DataTable 是不論是否要都會載入。
- DataReader 較省記憶體,只會儲存目前的游標,但 DataTable 會在記憶體中儲存所有的資料。
- DataReader 透過適當的處理,可以直接和 DTO/entity 搭配,但 DataTable 還要再多一道轉型程序。
若一定要用 DataReader 和 DataTable.Load(),那麼可以考慮改用 DataTable.BeginDataLoad()/EndDataLoad() 加上 DataTable.LoadDataRow() 來做,可以加快載入速度。
Reference:
http://stackoverflow.com/questions/1676753/sqldataadapter-vs-sqldatareader