DataSet和SqlDataReader的比較的文章和帖子網上已經很多,我也看了很多前輩的隨筆.自己在面試和實際工作中也遇到這樣的問題.但是始終是沒深入進行學習.最近在工作空閒之余,特地查閱了一些文章,也包括msdn的官方文檔.自己建立數據庫和測試程序,對DataSet和SqlDataReader做了比較.
首先關於兩者比較主流的觀點就是:
1.DataReader使用時始終占用SqlConnection,在線操作數據庫.DataSet則是將數據一次性加載在內存中.支持數據庫訪問的斷開連接模型.
2.DataReader每次只在內存中加載一條數據,節約內存.DataSet將數據全部加載在內存中.比較消耗內存.
3.DataReader單向只讀.DataSet支持查詢\修改\刪除等操作,比較靈活.
4.DataReader與 SqlCommand搭配.DataSet與DataAdapter 結合使用.
為什麼會這樣呢?我們就來分析一下具體的原因.查看一下msdn關於 兩者的不同描述:
1>SqlDataReader 類提供一種從 SQL Server 數據庫讀取行的只進流的方式。無法繼承此類。命名空間:System.Data.SqlClient程序集:System.Data(在 System.Data.dll 中).可以使用 ADO.NET DataReader 從數據庫中檢索只讀、只進的數據流。查詢結果在查詢執行時返回,在並存儲在客戶端的網絡緩沖區中,直到您使用 DataReader 的 Read 方法對它們發出請求。使用 DataReader 可以提高應用程序的性能,原因是它只要數據可用就立即檢索數據,並且(默認情況下)一次只在內存中存儲一行,減少了系統開銷。
2>DataSet 對象是支持 ADO.NET 的斷開式、分布式數據方案的核心對象。DataSet 是數據的內存駐留表示形式,無論數據源是什麼,它都會提供一致的關系編程模型。它可以用於多種不同的數據源,用於 XML 數據,或用於管理應用程序本地的數據。DataSet 表示包括相關表、約束和表間關系在內的整個數據集。下圖將顯示 DataSet 對象模型。
從上面的描述可以看出,DataReader和DataSet具有不同的結構模型.在數據的方式處理上也存在顯著的差別.SqlDataReader 會避免創建不必要的對象或復制不必要的數據.DataSet 可以表示完整的數據模型,包括表格、約束條件和表關系.在對象的創建和銷毀等環節需要消耗更多的資源,因此在性能上也稍顯遜色.因此很多文章得出的結論也是在只進行讀數據操作的情況下,DataReader的性能要強於DataSet.但是很多文章都沒有相應的測試,就盲目下結論.
但是好奇心理的驅使使我很想來做個試驗來驗證一下這個結論,到底DataReader比DataSet在查詢數據的時候,性能會不會勝出,如果前者更優的話那麼會超出多少?我自己寫了個小程序,自己建立的數據庫進行了實驗. 實驗測試環境如下:
硬件:
CPU Intel T2300 1.66GHz 內存 Kingston DDR2 667 1G 硬盤 80G 5400轉 8m
軟件:
操作系統 Windows Server 2003 數據庫系統 SQL Server 2005 Enterprise 數據規模 1000000條數據
數據庫表結構
Test
名稱 類型 備注 id int 標志\聚集索引 name nvarchar(50) 非聚集索引 birthday datetime 生日 height int 身高 sex int 性別 address nvarchar(100) 地址 lastlogintime datetime 非聚集索引
具體的實驗程序c#代碼如下,使用了using System.Data.SqlClient;
using System.Diagnostics;兩個namespace下的類,Stopwatch對象用來進行計時.
測試DataSet的代碼:
1 using (SqlConnection conn = new SqlConnection("Data Source=127.0.0.1;Initial Catalog=Test;Integrated Security=True"))
2 {
3 Stopwatch st = new Stopwatch();
4 st.Start();
5 conn.Open();
6 SqlDataAdapter da = new SqlDataAdapter(sQuery, conn);
7 st.Stop();
8 //textBox3.Text = st.ElapsedMilliseconds.ToString();
9
10 st.Start();
11 DataSet ds = new DataSet("test");
12 da.Fill(ds);
13 st.Stop();
14 long ltimes = st.ElapsedMilliseconds;
15 //textBox1.Text = ltimes.ToString();
16 conn.Close();
17 /**////textBox3.Text = sizeof(DataSet);
18 }
19
測試DataReader的代碼:
1 using (SqlConnection conn = new SqlConnection("Data Source=127.0.0.1;Initial Catalog=Test;Integrated Security=True"))
2 {
3
4 Stopwatch st = new Stopwatch();
5 st.Start();
6 conn.Open();
7 SqlCommand Comm = new SqlCommand(sQuery, conn);
8 st.Stop();
9 textBox4.Text = st.ElapsedMilliseconds.ToString();
10 st.Start();
11 SqlDataReader reader = Comm.ExecuteReader();
12 while (reader.Read())
13 {
14
15 }
16 st.Stop();
17 long ltimes = st.ElapsedMilliseconds;
18 textBox2.Text = ltimes.ToString();
19 conn.Close();
20 //textBox4.Text = sizeof(SqlDataReader);
21 }
測試語句和時間ms分別如下:
語句 DataReader費時 DataSet費時 string sQuery = "SELECT * FROM dbo.test where id <= 1" 0ms 0ms string sQuery = "SELECT * FROM dbo.test where id <=10" 1ms 1ms string sQuery = "SELECT * FROM dbo.test where id <=100" 2ms 3ms string sQuery = "SELECT * FROM dbo.test where id <=1000" 5ms 6ms string sQuery = "SELECT * FROM dbo.test where id <=10000" 8ms 50ms從試驗結果分析可以得出如下結論:
1.在查詢數據量很少的情況下100條內,DataReader和DataSet幾乎沒有什麼明顯的性能差別.
2.數據量過大,接近10000條的時候的數據查詢,DataReader的性能要明顯優於DataSet.
對於兩者的性能對比,不應該輕易下結論,除了參考實驗設備,也要考慮實際的數據規模.
具體項目應用中,選擇適合具體需求的對象進行數據處理,才能有效的提高系統的性能.
本文的測試環境和結果可能存在偏差,但是希望能給大家帶來一些幫助,一起交流學習.謝謝.
【2】補充:
DataAdapter的Fill方法的源碼,我們使用反射器查看,代碼如下:
protected virtual int Fill(DataSet dataSet, string srcTable, IDataReader dataReader, int startRecord, int maxRecords) { int num; IntPtr ptr; Bid.ScopeEnter(out ptr, "<comm.DataAdapter.Fill|API> %d#, dataSet, srcTable, dataReader, startRecord, maxRecords\n", this.ObjectID); try { if (dataSet == null) { throw ADP.FillRequires("dataSet"); } if (ADP.IsEmpty(srcTable)) { throw ADP.FillRequiresSourceTableName("srcTable"); } if (dataReader == null) { throw ADP.FillRequires("dataReader"); } if (startRecord < 0) { throw ADP.InvalidStartRecord("startRecord", startRecord); } if (maxRecords < 0) { throw ADP.InvalidMaxRecords("maxRecords", maxRecords); } if (dataReader.IsClosed) { return 0; } DataReaderContainer container = DataReaderContainer.Create(dataReader, this.ReturnProviderSpecificTypes); num = this.FillFromReader(dataSet, null, srcTable, container, startRecord, maxRecords, null, null); } finally { Bid.ScopeLeave(ref ptr); } return num; }
另外一個SqlCommand 的ExecuteReader方法最終調用Run方法代碼如下:
internal bool Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) { if ((TdsParserState.Broken == this.State) || (this.State == TdsParserState.Closed)) { return true; } bool flag = false; Label_0016: if (stateObj._internalTimeout) { runBehavior = RunBehavior.Attention; } if ((TdsParserState.Broken == this.State) || (this.State == TdsParserState.Closed)) { goto Label_06DF; } byte token = stateObj.ReadByte(); if ((((((token != 170) && (token != 0xab)) && ((token != 0xad) && (token != 0xe3))) && (((token != 0xac) && (token != 0x79)) && ((token != 160) && (token != 0xa1)))) && ((((token != 0x81) && (token != 0x88)) && ((token != 0xa4) && (token != 0xa5))) && (((token != 0xa9) && (token != 0xd3)) && ((token != 0xd1) && (token != 0xfd))))) && ((((token != 0xfe) && (token != 0xff)) && ((token != 0x39) && (token != 0xed))) && (((token != 0xae) && (token != 0x7c)) && ((token != 120) && (token != 0xed))))) { this._state = TdsParserState.Broken; this._connHandler.BreakConnection(); throw SQL.ParsingError(); } int tokenLength = this.GetTokenLength(token, stateObj); switch (token) { case 0xa4: if (dataStream == null) { this.SkipBytes(tokenLength, stateObj); } else { dataStream.TableNames = this.ProcessTableName(tokenLength, stateObj); } goto Label_06AF; case 0xa5: if (dataStream == null) { this.SkipBytes(tokenLength, stateObj); } else { _SqlMetaDataSet metaData = this.ProcessColInfo(dataStream.MetaData, dataStream, stateObj); dataStream.SetMetaData(metaData, false); dataStream.BrowseModeInfoConsumed = true; } goto Label_06AF; case 0xa9: this.SkipBytes(tokenLength, stateObj); goto Label_06AF; case 170: case 0xab: { if (token == 170) { stateObj._errorTokenReceived = true; } SqlError error = this.ProcessError(token, stateObj); if (RunBehavior.Clean == (RunBehavior.Clean & runBehavior)) { if (error.Class >= 20) { this.Errors.Add(error); } } else if (((this._connHandler != null) && (this._connHandler.Connection != null)) && (this._connHandler.Connection.FireInfoMessageEventOnUserErrors && (error.Class <= 0x10))) { this.FireInfoMessageEvent(stateObj, error); } else if (error.Class < 11) { this.Warnings.Add(error); } else if (error.Class <= 0x10) { this.Errors.Add(error); if ((dataStream != null) && !dataStream.IsInitialized) { runBehavior = RunBehavior.UntilDone; } } else { this.Errors.Add(error); runBehavior = RunBehavior.UntilDone; } goto Label_06AF; } case 0xac: { SqlReturnValue rec = this.ProcessReturnValue(tokenLength, stateObj); if (cmdHandler != null) { cmdHandler.OnReturnValue(rec); } goto Label_06AF; } case 0xad: { SqlLoginAck ack = this.ProcessLoginAck(stateObj); this._connHandler.OnLoginAck(ack); goto Label_06AF; } case 0x88: { if (stateObj._cleanupAltMetaDataSetArray == null) { stateObj._cleanupAltMetaDataSetArray = new _SqlMetaDataSetCollection(); } _SqlMetaDataSet altMetaDataSet = this.ProcessAltMetaData(tokenLength, stateObj); stateObj._cleanupAltMetaDataSetArray.Add(altMetaDataSet); if (dataStream != null) { dataStream.SetAltMetaDataSet(altMetaDataSet, 0x88 != stateObj.PeekByte()); } goto Label_06AF; } case 0x79: { int status = stateObj.ReadInt32(); if (cmdHandler != null) { cmdHandler.OnReturnStatus(status); } goto Label_06AF; } case 0x81: if (tokenLength != 0xffff) { stateObj._cleanupMetaData = this.ProcessMetaData(tokenLength, stateObj); } else if (cmdHandler != null) { stateObj._cleanupMetaData = cmdHandler.MetaData; } if (dataStream != null) { byte num5 = stateObj.PeekByte(); dataStream.SetMetaData(stateObj._cleanupMetaData, (0xa4 == num5) || (0xa5 == num5)); } else if (bulkCopyHandler != null) { bulkCopyHandler.SetMetaData(stateObj._cleanupMetaData); } goto Label_06AF; case 0xd1: if (bulkCopyHandler == null) { if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) { this.SkipRow(stateObj._cleanupMetaData, stateObj); } break; } this.ProcessRow(stateObj._cleanupMetaData, bulkCopyHandler.CreateRowBuffer(), bulkCopyHandler.CreateIndexMap(), stateObj); break; case 0xd3: if (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior)) { int num8 = stateObj.ReadUInt16(); this.SkipRow(stateObj._cleanupAltMetaDataSetArray[num8], stateObj); } flag = true; goto Label_06AF; case 0xe3: { SqlEnvChange[] changeArray = this.ProcessEnvChange(tokenLength, stateObj); for (int i = 0; i < changeArray.Length; i++) { if (changeArray[i] == null) { continue; } switch (changeArray[i].type) { case 8: case 11: this._currentTransaction = this._pendingTransaction; this._pendingTransaction = null; if (this._currentTransaction == null) { break; } this._currentTransaction.TransactionId = changeArray[i].newLongValue; goto Label_048E; case 9: case 12: case 0x11: this._retainedTransactionId = 0L; goto Label_04D1; case 10: goto Label_04D1; default: goto Label_0551; } TransactionType type = (8 == changeArray[i].type) ? TransactionType.LocalFromTSQL : TransactionType.Distributed; this._currentTransaction = new SqlInternalTransaction(this._connHandler, type, null, changeArray[i].newLongValue); Label_048E: if ((this._statistics != null) && !this._statisticsIsInTransaction) { this._statistics.SafeIncrement(ref this._statistics._transactions); } this._statisticsIsInTransaction = true; this._retainedTransactionId = 0L; continue; Label_04D1: if (this._currentTransaction != null) { if (9 == changeArray[i].type) { this._currentTransaction.Completed(TransactionState.Committed); } else if (10 == changeArray[i].type) { if (this._currentTransaction.IsDistributed && this._currentTransaction.IsActive) { this._retainedTransactionId = changeArray[i].oldLongValue; } this._currentTransaction.Completed(TransactionState.Aborted); } else { this._currentTransaction.Completed(TransactionState.Unknown); } this._currentTransaction = null; } this._statisticsIsInTransaction = false; continue; Label_0551: this._connHandler.OnEnvChange(changeArray[i]); } goto Label_06AF; } case 0xfd: case 0xfe: case 0xff: this.ProcessDone(cmdHandler, dataStream, ref runBehavior, stateObj); if ((token == 0xfe) && (cmdHandler != null)) { cmdHandler.OnDoneProc(); } goto Label_06AF; case 0xed: this.ProcessSSPI(tokenLength); goto Label_06AF; default: goto Label_06AF; } if (this._statistics != null) { this._statistics.WaitForDoneAfterRow = true; } flag = true; Label_06AF: if ((stateObj._pendingData && (RunBehavior.ReturnImmediately != (RunBehavior.ReturnImmediately & runBehavior))) || ((!stateObj._pendingData && stateObj._attentionSent) && !stateObj._attentionReceived)) { goto Label_0016; } Label_06DF: if (!stateObj._pendingData && (this.CurrentTransaction != null)) { this.CurrentTransaction.Activate(); } if (stateObj._attentionSent && stateObj._attentionReceived) { stateObj._attentionSent = false; stateObj._attentionReceived = false; if ((RunBehavior.Clean != (RunBehavior.Clean & runBehavior)) && !stateObj._internalTimeout) { this.Errors.Add(new SqlError(0, 0, 11, this._server, SQLMessage.OperationCancelled(), "", 0)); } } if ((this._errors != null) || (this._warnings != null)) { this.ThrowExceptionAndWarning(stateObj); } return flag; }
我已經使用Reflector查看了SQLDataAdapter類型的Fill方法以及SqlCommand.ExecuteReader 方法代碼。確實有使用DataReader的地方。他們是真正負責處理查詢並裝在數據。DataReader和DataSet應該都是數據容器。做個類比的話,應該是茶壺和茶杯的關系。至於查詢數據,除了取決於容器,還和具體負責查詢和裝載數據的類有關系。 我文章的標題,應該說是有問題的,這種比較忽略了一個數據查詢和裝載的過程。有不合理的地方。現在更正。