VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • DataReader与DataSet——用C#进行大数据量读取优化

第一部分:C#基础入门
DataReader与DataSet——大数据量读取优化
实例介绍
在处理数据库数据时,DataReader和DataSet是ADO.NET中最常用的两个组件,但它们的设计目标和性能表现差异巨大。对于大数据量(如10万+记录)的读取场景,选择合适的组件直接影响程序的性能和稳定性:
DataReader:以只读向前的数据流方式读取数据,内存占用极低,性能最优;
DataSet:将数据一次性加载到内存中形成“离线数据库”,支持修改和关系操作,但内存占用高,适合小批量数据。
本节通过“订单数据批量读取”场景,对比两者在大数据量下的性能、内存占用差异,带你掌握优化策略。
需求分析
设计一个大数据量读取工具,核心需求:
1.模拟大数据量:创建包含10万条订单记录的表;
2.对比读取性能:分别用DataReader和DataSet读vb.net教程C#教程python教程SQL教程access 2010教程取数据,记录耗时;
3.对比内存占用:统计两种方式的内存使用情况;
4.优化实践:演示DataReader的批量处理技巧;
5.规范实现:资源管理、异常处理的最佳实践。
代码实现
前置条件:
创建数据库ECommerceDB,新建Orders表并插入10万条测试数据:
sql

	CREATE TABLE Orders (
	OrderId INT PRIMARY KEY IDENTITY(1,1),
	CustomerId INT NOT NULL,
	OrderDate DATETIME NOT NULL DEFAULT GETDATE(),
	TotalAmount DECIMAL(18,2) NOT NULL
	);
	-- 插入10万条测试数据(可通过循环或批量插入工具实现)
  1. 工具类:性能与内存统计
    csharp
	using System;
	using System.Diagnostics;
	using System.Data.SqlClient;
	using System.Data;
	
	public static class PerformanceHelper {
	public static void MeasureAction(string actionName, Action action) {
	Stopwatch sw = new Stopwatch();
	sw.Start();
	// 记录初始内存
	long initialMemory = GC.GetTotalMemory(true);
	action();
	// 记录结束内存
	long finalMemory = GC.GetTotalMemory(true);
	sw.Stop();
	Console.WriteLine($"[{actionName}] 耗时:{sw.ElapsedMilliseconds}ms,内存占用:{(finalMemory - initialMemory)/1024/1024:F2}MB");
	}
	}
2. DataReader实现:大数据量读取
csharp 
	public static class DataReaderDemo {
	private static readonly string ConnStr = @"Data Source=(localdb)MSSQLLocalDB;Initial Catalog=ECommerceDB;Integrated Security=True;";
	
	public static void ReadLargeDataWithDataReader() {
	using (SqlConnection conn = new SqlConnection(ConnStr)) {
	try {
	conn.Open();
	string sql = "SELECT OrderId, CustomerId, OrderDate, TotalAmount FROM Orders";
	using (SqlCommand cmd = new SqlCommand(sql, conn)) {
	using (SqlDataReader reader = cmd.ExecuteReader()) {
	// 批量处理计数器
	int batchCount = 0;
	const int BatchSize = 1000; // 每1000条处理一次
	while (reader.Read()) {
	// 读取数据(这里仅模拟处理,实际可写入文件/队列)
	int orderId = reader.GetInt32(0);
	int customerId = reader.GetInt32(1);
	DateTime orderDate = reader.GetDateTime(2);
	decimal totalAmount = reader.GetDecimal(3);
	
	batchCount++;
	if (batchCount % BatchSize == 0) {
	Console.WriteLine($"已处理 {batchCount} 条记录...");
	}
	}
	Console.WriteLine($"DataReader 共处理 {batchCount} 条记录");
	}
	}
	} catch (Exception ex) {
	Console.WriteLine($"DataReader 错误:{ex.Message}");
	}
	}
	}
	}
  1. DataSet实现:大数据量读取
    csharp
	public static class DataSetDemo {
	private static readonly string ConnStr = @"Data Source=(localdb)MSSQLLocalDB;Initial Catalog=ECommerceDB;Integrated Security=True;";
	
	public static void ReadLargeDataWithDataSet() {
	try {
	using (SqlConnection conn = new SqlConnection(ConnStr)) {
	string sql = "SELECT OrderId, CustomerId, OrderDate, TotalAmount FROM Orders";
	SqlDataAdapter adapter = new SqlDataAdapter(sql, conn);
	DataSet ds = new DataSet();
	
	adapter.Fill(ds, "Orders"); // 一次性加载所有数据到DataSet
	
	DataTable dt = ds.Tables["Orders"];
	Console.WriteLine($"DataSet 共加载 {dt.Rows.Count} 条记录");
	
	// 处理数据(示例:遍历所有行)
	int batchCount = 0;
	const int BatchSize = 1000;
	foreach (DataRow row in dt.Rows) {
	int orderId = (int)row["OrderId"];
	int customerId = (int)row["CustomerId"];
	DateTime orderDate = (DateTime)row["OrderDate"];
	decimal totalAmount = (decimal)row["TotalAmount"];
	
	batchCount++;
	if (batchCount % BatchSize == 0) {
	Console.WriteLine($"已处理 {batchCount} 条记录...");
	}
	}
	}
	} catch (Exception ex) {
	Console.WriteLine($"DataSet 错误:{ex.Message}");
	}
	}
	}
  1. 测试对比
    csharp
	class Program {
	static void Main() {
	Console.WriteLine("=== 大数据量读取对比测试 ===");
	// DataReader测试
	PerformanceHelper.MeasureAction("DataReader 读取", DataReaderDemo.ReadLargeDataWithDataReader);
	// DataSet测试
	PerformanceHelper.MeasureAction("DataSet 读取", DataSetDemo.ReadLargeDataWithDataSet);
	}
	}

逐行讲解

  1. 性能统计工具类
    MeasureAction:接收一个动作(如读取数据),用Stopwatch计时,GC.GetTotalMemory(true)强制垃圾回收后统计内存变化,输出耗时和内存占用。

  2. DataReader实现细节
    只读向前:reader.Read()每次只加载一行数据到内存,处理完即释放,内存占用始终保持低位;
    批量处理:每1000条记录输出一次进度,避免频繁IO操作;
    资源管理:using包裹SqlConnection、SqlCommand、SqlDataReader,确保资源及时释放;
    低内存:10万条记录仅占用几十MB内存(取决于单条记录大小)。

  3. DataSet实现细节
    一次性加载:adapter.Fill(ds)将所有数据加载到内存中的DataTable,内存占用随数据量线性增长(10万条可能占用几百MB);
    离线操作:加载后可断开数据库连接,支持修改、排序、过滤等操作;
    高内存开销:适合小批量数据,大数据量下易导致内存不足(OOM)。

  4. 测试结果预期
    DataReader:耗时短(如100ms),内存占用低(如50MB);
    DataSet:耗时长(如500ms),内存占用高(如300MB);
    差异原因:DataReader是“流”式读取,DataSet是“快照”式读取。
    基础知识拓展

  5. DataReader vs DataSet核心对比
    | 特性 | DataReader | DataSet |
    | ---- | ---- | ---- |
    | 读取方式 | 只读向前(Sequential Access) | 离线快照(Disconnected) |
    | 内存占用 | 极低(每行加载) | 高(全量加载) |
    | 性能 | 极快(无中间层) | 较慢(序列化/反序列化开销) |
    | 数据修改 | 不支持 | 支持(可更新回数据库) |
    | 连接状态 | 需保持连接 | 可断开连接 |
    | 适用场景 | 大数据量读取、批量处理 | 小批量数据、离线操作、数据展示 |

  6. 大数据量读取优化技巧
    分页读取:用TOP+OFFSET或ROW_NUMBER()实现分页,避免一次性读取所有数据;
    批量处理:如DataReader中每1000条写入文件/数据库,减少内存累积;
    连接池优化:增大连接池最大连接数(Max Pool Size),避免连接等待;
    避免不必要的列:只查询需要的列(如SELECT OrderId, TotalAmount FROM Orders),减少数据传输量;
    使用异步读取:reader.ReadAsync()结合async/await,提高并发性能。

  7. DataReader高级用法
    类型映射:用GetOrdinal()获取列索引,避免硬编码列顺序(如reader.GetInt32(reader.GetOrdinal("OrderId")));
    批量插入:结合SqlBulkCopy,用DataReader读取源数据直接批量插入目标表,性能远超单条插入;
    关闭连接:DataReader必须在连接关闭前关闭,否则会导致连接泄漏(using自动处理)。

  8. DataSet高级用法
    数据关系:DataSet.Relations.Add()建立表之间的关系(如Orders和OrderDetails);
    数据验证:DataTable.Columns["TotalAmount"].AllowDBNull = false设置列约束;
    更新数据库:adapter.Update(ds)将修改后的DataTable同步回数据库(需设置UpdateCommand)。
    总结
    在大数据量读取场景下,DataReader是最优选择,它以极低的内存占用和极快的速度处理海量数据;而DataSet适合小批量数据的离线操作。实际开发中,应根据需求选择:
    若仅需读取数据并批量处理(如导出Excel、生成报表),用DataReader;
    若需对数据进行复杂操作(如修改、排序、关联),且数据量小,用DataSet;
    大数据量优化的核心是减少内存占用和避免一次性加载,结合分页、批量处理等技巧可进一步提升性能。
    掌握两者的差异和适用场景,是高效处理数据库数据的关键!

本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49436.html


相关教程