ADO.NET 中 DataReader 各种读取方式性能比较/测试

 很早就做了这么一个测试,一直没有发布出来,觉得 ADO.NET 星球原著民,应该都知道 DataReader.GetXXX(<<ColumnIndex>>)Convert.ToXXX(DataReader[<<ColumnName>>]) 性能佳,且前者是诸多读取方式最佳的。

刚才看到 http://community.csdn.net/Expert/TopicView3.asp?id=5696773 中关于 box/unbox 问题,以及前几天有朋友问,(<<Type>>)DataReader[<<ColumnIndex>>]Convert.ToXXX(DataReader[<<ColumnName>>]) 的差别,就整理了一下,与大家分享,希望对 .NET 新手而又喜欢嘀咕:“为什么你的程序就是跑得比我快”的朋友有帮助40.gif

限于篇,此篇不讨论各种读取方式适用场景了,诸位大虾楼下拍砖吧smile.gif

A.测试结论
非官方,俺自己总结的 13.gif(以下序号越大,性能越低)
None.gif 1 . DataReader.GetXXX(<<ColumnIndex>>)
None.gif
None.gif
2 . DataReader.GetXXX(Dictionary<string ,  int> [ <<ColumnName>> ] )
None.gif
[ Dictionary<string, int>.Add(<<ColumnName>>, DataReader.GetOrdinal(<<ColumnName>>)) ]  
None.gif
None.gif
3 . DataReader.GetXXX((Int32)Hashtable [ <<ColumnName>> ] )
None.gif
[ Hashtable.Add(<<ColumnName>>, DataReader.GetOrdinal(<<ColumnName>>)) ]
None.gif
None.gif
4 . (<<Type>>)DataReader [ <<ColumnIndex>> ]
None.gif
None.gif
5 . DataReader.GetXXX(DataReader.GetOrdinal(<<ColumnName>>))
None.gif
None.gif
6 . Convert.ToXXX(DataReader [ <<ColumnIndex>> ] )
None.gif
None.gif
7 . (<<Type>>)DataReader [ <<ColumnName>> ]
None.gif
None.gif
8 . Convert.ToXXX(DataReader [ <<ColumnName>> ]

说明:
1.  按 Index(SELECT 子句中列索引) 读取比按 Name(列名)读取快
2.  DataReader.GetXXX(<<ColumnIndex>>)  遥遥领先,基于两点:
         a. 此方法内部直接访问对应的数据库类型,不存在 box/unbox 。
         b. 基于索引访问。
3.  (<<Type>>)DataReader[<<ColumnIndex>>] 比 Convert.ToXXX(DataReader[<<ColumnIndex>>]) 快
         a. 前者属于编程语言特有的强制类型转换,对于值类型,存在 unbox 过程。
         b. 后者使用的是.NET FCL提供的一组与语言无关的类型转换方法。静态类 Convert  中的类型转换方法,调用源类型实现的 IConvertible 接口进行目标类型的转换。也就是说,只有实现了 IConvertible 接口接口的类型才能用 Convert.ToXXX  进行类型转换。如, Convert.ToInt32(object)

None.gif public   static   int  ToInt32( object  value)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
if (value != null)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
return ((IConvertible) value).ToInt32(null);
ExpandedSubBlockEnd.gif    }

InBlock.gif    
return 0;
ExpandedBlockEnd.gif}

None.gif
None.gif

4. 由于使用 Index 方式访问,容易出错,可维护性差,一种折中方式,是先根据 Name 读取 Index,然后调用 GetXXX 方法。如,

None.gif int  id  =  DataReader.GetInt32(DataReader.GetOrdinal( " ID " ));

5. 当循环读取所有行时,直接使用方式 4 ,每此都要读取列名(DataReader.GetOrdinal 内部也是一个 Lookup 的过程),因此需要改进。考虑首次读取后将 Name 缓存起来,以后直接读缓存,由于需要 index/name 成对关联,考虑性能,Hashtable 是不二选择,NET 2.0 还提供了泛型版本 System.Collections.Generic.Dictionary<TK,TV> 。
对于非泛型的 Hashtable 只有一个 key 存在 box/unbox,两者性能差别,并不十分明显。
但相对于,各种按 Name 访问方式,有明显的性能优势。

6. 以上各组读取方式比较,随着数据量的增加,性能差异越来越明显,少量数据读取无法看出明显差别。

7. 对于借助于自动化工具、代码生成工具开发应用程序,应该优先考虑 GetXXX 的方式读取。

8. xxxDataAdapter.Fill 方法内部使用对应 Data Provider 的 xxxDataReader  填充 DataTable。


B.测试实例
说明
1. 此测试,直接使用 ASP.NET(似乎不影响对比性),抱歉了,偶就会 WebForm,比较理想的当然整个 Console Applilcation 让她跑

2. 懒于准备样表数据,直接使用 SQL Server 2k. Northwind.Products 表,且只读取 ProductID 字段(INT 型),并由应用程序多次重复读取同一数据,模拟大数据量的效果

3. 测试结果为,用 SqlDataReader 分别读取

5,000
50,000
500,000
5,000,000
50,000,000

条记录(模拟)所花的总时间,单位为秒,

这里显示了连续 5 次的测试结果。

测试代码

ExpandedBlockStart.gif ContractedBlock.gif <% dot.gif @ Page Language="C#"  %>
ExpandedBlockStart.gifContractedBlock.gif
<% dot.gif @ Import Namespace="System.Collections.Generic"  %>
ExpandedBlockStart.gifContractedBlock.gif
<% dot.gif @ Import Namespace="System.Data"  %>
ExpandedBlockStart.gifContractedBlock.gif
<% dot.gif @ Import Namespace="System.Data.SqlClient"  %>
ExpandedBlockStart.gifContractedBlock.gif
<% dot.gif @ Import Namespace="System.IO"  %>
None.gif
None.gif
<! DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
None.gif
ExpandedBlockStart.gifContractedBlock.gif
< script  runat ="server" > dot.gif
InBlock.gif
InBlock.gif    protected 
void Button1_Click(object sender, EventArgs e)
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        
int i = 5;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
while (i-- > 0dot.gif{
InBlock.gif            ExecuteTest();
InBlock.gif            System.Threading.Thread.Sleep(
1000 * 10);
ExpandedSubBlockEnd.gif        }

ExpandedSubBlockEnd.gif    }

InBlock.gif
InBlock.gif    private 
void ExecuteTest()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        const 
int COLUMN_INDEX_PRODUCT_ID = 0;
InBlock.gif        const string COLUMN_NAME_PRODUCT_ID 
= "ProductID";
InBlock.gif
InBlock.gif        StringBuilder sb 
= new StringBuilder();
InBlock.gif        
int loops = 100;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
for (int k = 0; k < 5; k++, loops *= 10dot.gif{
InBlock.gif            sb.AppendFormat(
"{0, 10:N0}\t", loops * 50);
InBlock.gif
InBlock.gif            
// 1. DataReader.GetXXX(<<ColumnIndex>>)
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        productId 
= dr.GetInt32(COLUMN_INDEX_PRODUCT_ID);
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
// 2. (<<Type>>)DataReader[<<ColumnIndex>>]
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        productId 
= (int)dr[COLUMN_INDEX_PRODUCT_ID];
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
// 3. Convert.ToXXX(DataReader[<<ColumnIndex>>])
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        productId 
= Convert.ToInt32(dr[0]);
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
// 4. (<<Type>>)DataReader[<<ColumnName>>]
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        productId 
= (int)dr[COLUMN_NAME_PRODUCT_ID];
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
// 5. Convert.ToXXX(DataReader[<<ColumnName>>]
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        productId 
= Convert.ToInt32(dr[COLUMN_NAME_PRODUCT_ID]);
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
// 6. DataReader.GetXXX(DataReader.GetOrdinal(<<ColumnName>>))
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        productId 
= dr.GetInt32(dr.GetOrdinal(COLUMN_NAME_PRODUCT_ID));
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
// 7. DataReader.GetXXX((Int32)Hashtable[<<ColumnName>>])
InBlock.gif
            //    Hashtable.Add(<<ColumnName>>, DataReader.GetOrdinal(<<ColumnName>>))
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
InBlock.gif                Hashtable columns 
= new Hashtable();
InBlock.gif
InBlock.gif                
int j = 0;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        
if (j++ == 0) columns.Add(COLUMN_NAME_PRODUCT_ID, dr.GetOrdinal("ProductID"));
InBlock.gif                        productId 
= dr.GetInt32((int)columns[COLUMN_NAME_PRODUCT_ID]);
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            
// 8. DataReader.GetXXX(Dictionary<string, int>[<<ColumnName>>])
InBlock.gif
            //    Dictionary<string, int>.Add(<<ColumnName>>, DataReader.GetOrdinal(<<ColumnName>>))
ExpandedSubBlockStart.gifContractedSubBlock.gif
            using (SqlDataReader dr = GetDataReader()) dot.gif{
InBlock.gif                
int productId, i;
InBlock.gif                DateTime start 
= DateTime.Now;
InBlock.gif                Dictionary
<string, int> columns = new Dictionary<string, int>();
InBlock.gif                
int j = 0;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
while (dr.Read()) dot.gif{
InBlock.gif                    i 
= loops;
ExpandedSubBlockStart.gifContractedSubBlock.gif                    
while (i-- > 0dot.gif{
InBlock.gif                        
if (j++ == 0) columns.Add(COLUMN_NAME_PRODUCT_ID, dr.GetOrdinal("ProductID"));
InBlock.gif                        productId 
= dr.GetInt32(columns[COLUMN_NAME_PRODUCT_ID]);
ExpandedSubBlockEnd.gif                    }

ExpandedSubBlockEnd.gif                }

InBlock.gif                DateTime end 
= DateTime.Now;
InBlock.gif                TimeSpan span 
= end - start;
InBlock.gif                sb.Append(span.TotalSeconds.ToString(
"f7")).Append("\t");
ExpandedSubBlockEnd.gif            }

InBlock.gif
InBlock.gif            sb.AppendLine();
ExpandedSubBlockEnd.gif        }

InBlock.gif        sb.AppendLine();
InBlock.gif
InBlock.gif        string path 
= Server.MapPath("result.txt");
InBlock.gif        File.AppendAllText(path, sb.ToString());
ExpandedSubBlockEnd.gif    }

InBlock.gif    
InBlock.gif    private SqlDataReader GetDataReader()
ExpandedSubBlockStart.gifContractedSubBlock.gif    
dot.gif{
InBlock.gif        string connStr 
= "server=.;database=Northwind;uid=sa;";
InBlock.gif        SqlConnection conn 
= new SqlConnection(connStr);
InBlock.gif        SqlCommand cmd 
= conn.CreateCommand();
InBlock.gif        cmd.CommandText 
= "SELECT TOP 50 ProductID FROM Products";
InBlock.gif        conn.Open();
InBlock.gif        
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
ExpandedBlockEnd.gif    }

None.gif
</ script >
None.gif
None.gif
< html  xmlns ="http://www.w3.org/1999/xhtml"   >
None.gif
< head  runat ="server" >
None.gif    
< title > Performance Testing when retrieving data with DataReader  </ title >
None.gif
</ head >
None.gif
< body >
None.gif    
< form  id ="form1"  runat ="server" >
None.gif    
< div >
None.gif        
< asp:Button  ID ="Button1"  runat ="server"  OnClick ="Button1_Click"  Text ="Run Test"   />         
None.gif    
</ div >
None.gif    
</ form >
None.gif
</ body >
None.gif
</ html >
None.gif

C.测试结果
读取方式,从左往右依次是:
None.gif 1 . DataReader.GetXXX(<<ColumnIndex>>)           
None.gif
2 . (<<Type>>)DataReader [ <<ColumnIndex>> ]
None.gif
3 . Convert.ToXXX(DataReader [ <<ColumnIndex>> ] )
None.gif
4 . (<<Type>>)DataReader [ <<ColumnName>> ]
None.gif
5 . Convert.ToXXX(DataReader [ <<ColumnName>> ]
None.gif
6 . DataReader.GetXXX(DataReader.GetOrdinal(<<ColumnName>>))
None.gif
7 . DataReader.GetXXX((Int32)Hashtable [ <<ColumnName>> ] )
None.gif   
[ Hashtable.Add(<<ColumnName>>, DataReader.GetOrdinal(<<ColumnName>>)) ]
None.gif
8 . DataReader.GetXXX(Dictionary<string ,  int> [ <<ColumnName>> ] )
None.gif   
[ Dictionary<string, int>.Add(<<ColumnName>>, DataReader.GetOrdinal(<<ColumnName>>)) ]



D.测试环境

AMD Athlon XP 1800+ 512M -> 好古董的 PC 啊 silence.gif
.NET 2.0 + Windows XP SP2

转载于:https://www.cnblogs.com/Jinglecat/archive/2007/08/08/847145.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值