KB-當心SqlDataReader.Close時的額外資料傳輸量
正在寫一段由SqlDataReader取回資料逐一處理的程式,由於資料筆數很多,我就用了以下的程式,先小小Read()個三筆試算,就Close SqlDataReader及SqlConnection結束作業。
static void Test()
{
using (SqlConnection cn =
new SqlConnection("Data Source=mySql;
User Id=blah; Password=blahblah; Initial Catalog=MyDB;"))
{
cn.Open();
int i = 0;
SqlCommand cmd = new SqlCommand(
"SELECT * FROM BigTable", cn);
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read() && i < 3)
{
Console.WriteLine(dr["Col1"].ToString());
/* Do something... */
i++;
}
dr.Close();
cn.Close();
}
}
沒想到程式在dr.Close()時會卡住一陣子,甚至以timeout收場。問過幾個.NET老鳥(雞老大、小熊子,怪了,我的好朋友有很多都屬於動物界),我們都一致認為依SqlDataReader的設計理念,應該會讀多少筆記錄,傳多少資料。不過用Ethereal偵測的結果,讓我大吃一驚!!
不管我Read()幾次,Client與SQL Server間的往來封包數都跟全部讀完一樣多。也就是說,SqlDataReader.Close()的同時,會將沒讀完的資料全部傳完。
雞老大隨後Pass給我摘自MS SqlDataReader.Close()的說明:
The Close method fills in the values for output parameters, return values and RecordsAffected, increasing the time that it takes to close a SqlDataReader that was used to process a large or complex query. When the return values and the number of records affected by a query are not significant, the time that it takes to close the SqlDataReader can be reduced by calling the Cancel method of the associated SqlCommand object before calling the Close method.
看來為了要計算RecordsAffected,SqlDataReader會堅持將全部資料讀完。在ADO時代,ForwardOnly Recordset的一大特點就是無法得知總筆數RecordCount。而我一直把DataReader當成ForwardOnly Recordset的傳人,想也想不到它會對計算資料筆數這麼堅持,這是所謂的"青出於藍"嗎? (摩亞風)
總之,因資料筆數在此完全沒用,我們可以依文件的建議,在dr.Close()前加上個cmd.Cancel(),就可避免不必要的統計操作。測了一下,加了cmd.Cancel之後,SqlDataReader果然就不再愚公移山了,不論SELECT母體大小為何,往來的封包數改由dr.Read()的次數決定。
【2007-04-23 補充】
以上所提事項,僅適用於SqlDataReader!! 用Reflector追蹤的結果,System.Data.OracleClient.OracleDataReader的Close()行為看起來沒有統計筆數這段,OracleCommand.Cancel()則沒做什麼事。至於ODP.NET 9207更好玩,如果你膽敢呼叫OracleCommand.Cancel(),它會賞你一個NotSupportedException。