標籤:
原文連結 作者寫得非常好,我學到了許多東西,這裡只是轉載!
我們知道在.NET平台上有很多種資料存放區,檢索解決方案-ADO.NET Entity Framework,ASP.NET Dynamic Data,XML, NHibernate,LINQ to SQL 等等,但是由於一些原因,如平台限制,比如說必須基於.NET Framework2.0及以下平台;遺留的或者第三方資料介面採用的就是DataTable等等,仍然需要使用DataTable作為資料存放區結構。另一方面DataTable比較容易使用,一些資料訪問的介面可能直接採用了DataTable結構。在使用DataTable進行資料檢索的時候,有一些需要注意的地方,這些地方會嚴重的影響對資料的檢索效率。
本人最近工作中需要對大量的DataTable進行拼接。介面的資料是以DataSet然后里面放DataTable的方式提供的,暫不提是否合理,同時進行多個請求的時,服務端會返回一個DataSet,其中包含每個請求的結果DataTable,這些DataTable中有一列相當於”關鍵字”列。現在需要按照這個關鍵字,將這些DataTable中的列合并到一個DataTable,然後展現到介面上來。
最開始,我使用的是DataTable的Select方法來迴圈遍曆拼接實現的,發現很慢,於是總結了一下對DataTable進行查詢等操作的一些經驗,和大家分享。
一
情境
為了簡化問題,有兩張DataTable,名為表A,表B,欄位分別為
表A,儲存股票的最高價資訊, 表B儲存股票最低價資訊
SecurityCode High SecurityCode Low
000001.SZ 20 000001.SZ 18.5
000002.SZ 26 000002.SZ 56
現在需要,將這兩張表拼接到一張表中,這張表有三欄欄位,SecurityCode High Low,之前採用的方法是,建立一張含有這三個欄位的DataTable 表C,然後複製Security欄位,然後遍曆另外兩張表,對其採用Select方法尋找對應的SecurityCode,然後複製給C中對應欄位。發現效率很慢,問題出現在Select方法上,於是需要進行最佳化。
二 DataTable
的查詢效率
DataTable提供了兩個查詢資料的介面,DataTable.Select和DataTable.Rows.Find方法。
DataTable的Select方法通過傳入一系列條件,然後返回一個DataRow[ ]類型的資料,他需要遍曆整個表,然後挨個匹配條件,然後返回所有匹配的值。很顯然在策略上,之前的DataTable拼接採用Select方法存在問題,因為我們只需要尋找匹配上的一條記錄即可。
DataTable.Rows 的Find尋找第一個匹配上的唯一一條記錄。在指定了主鍵的基礎上,尋找會採用二叉樹的方式尋找,效率高。要建立主鍵,需要指定DataTable的PrimaryKey欄位如下:
dtA.PrimaryKey = new DataColumn[] { dtA.Columns["SecurityCode"] };
當然,建立主鍵會增加時間消耗,這也分為在資料填充前建立和資料填充後建立。在資料量大的情況下,建立主鍵的消耗是需要考慮進去的。下面的圖中顯示了在填充資料之前建立主鍵,之後建立主鍵,以及建立Dictionary所需的時間。可以看到:
ArraySize |
PreIndex Creation Time |
PostIndex Creation Time |
Dictionary Creation Time |
10 |
0 |
0 |
0 |
50 |
0 |
0 |
0 |
100 |
1 |
0 |
0 |
500 |
6 |
1 |
0 |
1000 |
15 |
2 |
0 |
5000 |
107 |
16 |
2 |
10000 |
261 |
42 |
5 |
50000 |
1727 |
271 |
31 |
100000 |
3525 |
544 |
47 |
500000 |
20209 |
2895 |
240 |
1000000 |
43382 |
5919 |
517 |
作圖如下:
從可以得到:
- 在填充資料之前建立主鍵,然後填充資料,比填充資料完之後建立主鍵消耗的時間要多。這是由於,建立主鍵後,再向其中添加資料,會導致需要重建索引,這和資料庫中,不適合在頻繁變動的欄位上建立主鍵的原理是一樣的。在我的筆記本 (Win7 32bit,CPU T6600 2.0GHZ,RAM 2GB)上,為100萬條記錄的DataTable建立索引大約需要5秒鐘,所以在資料量大的情況下,需要考慮索引的建立時間。
- 建立DataTable然後建立主鍵與直接建立和該DataTable相同的Dictionary結構相比,建立Dictionary所需要的時間要少的多,而且幾乎不隨著記錄條數規模的變大而變大。
建立完成之後,下面來測試幾種情況下的DataTable的檢索效率。為此,在建立主鍵和沒有建立索引的條件下,測試了在不同規模下 DataTable.Select, DataTable.Rows.Find 的查詢速度,由於在DataTable比較小的時候,時間不能很好的顯示,所以測試採用的單位是StopWatch的Tick數。每個方法在資料規模不同的情況下,各執行了10次,然後取平均值,結果如下:
| ArraySize |
Dictionary Create |
Dictionary Search |
Table Select |
Indexed Table Select |
Table Rows Find |
LINQ |
| 10 |
13 |
3 |
40 |
25 |
8 |
16 |
| 50 |
27 |
2 |
69 |
37 |
8 |
27 |
| 100 |
51 |
3 |
112 |
38 |
9 |
39 |
| 500 |
210 |
3 |
589 |
51 |
11 |
155 |
| 1000 |
461 |
4 |
1175 |
60 |
14 |
328 |
| 5000 |
2264 |
14 |
8412 |
85 |
17 |
1540 |
| 10000 |
6235 |
7 |
16806 |
99 |
20 |
3354 |
| 50000 |
23768 |
8 |
150133 |
138 |
26 |
15824 |
| 100000 |
49133 |
7 |
259794 |
147 |
26 |
31525 |
| 500000 |
252103 |
51 |
1547935 |
181 |
30 |
158317 |
| 1000000 |
494647 |
9 |
2736616 |
209 |
30 |
315716 |
作圖如下:
可以看到:
- 在沒有建立主鍵的條件下,對DataTable執行Select操作時比較低效的。在建立主鍵之後,僅對主鍵所在列執行Select操作,速度提高了很多,這種差距在資料量大的情況下尤其明顯,在集合大小規模為1000時,該差異達到了近20倍。
- LINQ對DataTable的查詢效率比DataTale.Select方法要高,但是仍然比DataTable.Rows.Find方法效率要低。
- 在對主鍵進行唯一性尋找時,我們應該使用DataTable.Rows.Find操作,在DataTable建立主鍵,並且僅對主鍵進行操作的情況下,Find方法會比Select方法快3-6倍,這可能是由於Select方法需要對裡面的過濾字串進行解析及判斷。因為Select方法可以接受多個條件的查詢以及以一些比較複雜的運算式,處理及解析可能需要耗費一些時間。並且在一般條件下Select是完全搜尋,即尋找整個集合找到所有滿足條件的記錄。而Find方法則僅對主鍵欄位進行檢索,如果沒有設定主鍵,那麼調用Find方法就會報錯。
- 採用Dictionary來代替DataTable結構來進行檢索,能達到最快的速度,且幾乎不受規模的影響,但是在資料量較大的情況下,將DataTable轉換為對應的Dictionary結構可能需要花費時間,如果操作頻繁,諸如在進行多個DataTable基於關鍵字進行拼接的情況下,對目標DataTable使用Dictionary<String,DataRow> 的方式進行儲存,能夠使用ContainsKey的基於Hash的方式對關鍵字進行尋找,這能極大地提高效率。並且在DataTable列有重複欄位,不能建立主鍵的情況下,可以採用Dictionary<string,List<DataRow>>能夠解決DataTable無法建立主鍵,從而導致尋找效能下降的問題。
三
實施效果
基於上面的分析,在實際中的工作中,替換了Select方法,建立了一個類型為Dictionary<String,DataRow>的包含目標合并後DataTable對象的所有行的結構C,其中關鍵字為SecurityCode,DataRow為包含SecurityCode,High,Low三列資料的行。在合并的時候,直接遍曆表A的所有行,然後判斷在C中是否包含該行中的SecurityCode,如果包含,取出,直接賦值。然後遍曆表B。整個過程使得DataTable合并的效率至少提高了10倍。
四
結語
本文簡要介紹了DataTable中檢索資料的兩種方法,DataTable.Select 和DataTable.Rows.Find方法。在測試方法的執行效率之前介紹了如何為DataTable設定主鍵,並比較了在資料填充之前和資料填充之後設定主鍵花費的時間,結果表明,在資料填充完成之後,設定主鍵要比在填充資料之前設定主鍵效率要高的多。設定主鍵之後,比較了在有無主鍵的情況下,DataTable.Select 方法在僅對主鍵欄位進行過濾時的效能,結果表明,在僅對主鍵進行檢索時,設定主鍵之後使用DataTable.Select 方法會比沒有主鍵的情況下的檢索速度會快非常多。在相同條件下,如果僅需要尋找某一條記錄,使用DataTable.Rows.Find會比DataTable.Select快很多。在某些需要頻繁操作DataTable查詢的時候,要避免在迴圈體內調用DataTable.Select方法,採用將DataTable轉換為等價的Dictionary結構,能夠有效解決由於索引值重複導致不能建立主鍵的問題,並且Dicitonary的採用雜湊表的方式尋找能夠極大地提高查詢效率。
DataTable資料檢索的效能分析[轉]