-
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万条测试数据(可通过循环或批量插入工具实现)
-
工具类:性能与内存统计
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}");
}
}
}
}
-
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}");
}
}
}
-
测试对比
csharp
class Program {
static void Main() {
Console.WriteLine("=== 大数据量读取对比测试 ===");
// DataReader测试
PerformanceHelper.MeasureAction("DataReader 读取", DataReaderDemo.ReadLargeDataWithDataReader);
// DataSet测试
PerformanceHelper.MeasureAction("DataSet 读取", DataSetDemo.ReadLargeDataWithDataSet);
}
}
逐行讲解
-
性能统计工具类
MeasureAction:接收一个动作(如读取数据),用Stopwatch计时,GC.GetTotalMemory(true)强制垃圾回收后统计内存变化,输出耗时和内存占用。 -
DataReader实现细节
只读向前:reader.Read()每次只加载一行数据到内存,处理完即释放,内存占用始终保持低位;
批量处理:每1000条记录输出一次进度,避免频繁IO操作;
资源管理:using包裹SqlConnection、SqlCommand、SqlDataReader,确保资源及时释放;
低内存:10万条记录仅占用几十MB内存(取决于单条记录大小)。 -
DataSet实现细节
一次性加载:adapter.Fill(ds)将所有数据加载到内存中的DataTable,内存占用随数据量线性增长(10万条可能占用几百MB);
离线操作:加载后可断开数据库连接,支持修改、排序、过滤等操作;
高内存开销:适合小批量数据,大数据量下易导致内存不足(OOM)。 -
测试结果预期
DataReader:耗时短(如100ms),内存占用低(如50MB);
DataSet:耗时长(如500ms),内存占用高(如300MB);
差异原因:DataReader是“流”式读取,DataSet是“快照”式读取。
基础知识拓展 -
DataReader vs DataSet核心对比
| 特性 | DataReader | DataSet |
| ---- | ---- | ---- |
| 读取方式 | 只读向前(Sequential Access) | 离线快照(Disconnected) |
| 内存占用 | 极低(每行加载) | 高(全量加载) |
| 性能 | 极快(无中间层) | 较慢(序列化/反序列化开销) |
| 数据修改 | 不支持 | 支持(可更新回数据库) |
| 连接状态 | 需保持连接 | 可断开连接 |
| 适用场景 | 大数据量读取、批量处理 | 小批量数据、离线操作、数据展示 | -
大数据量读取优化技巧
分页读取:用TOP+OFFSET或ROW_NUMBER()实现分页,避免一次性读取所有数据;
批量处理:如DataReader中每1000条写入文件/数据库,减少内存累积;
连接池优化:增大连接池最大连接数(Max Pool Size),避免连接等待;
避免不必要的列:只查询需要的列(如SELECT OrderId, TotalAmount FROM Orders),减少数据传输量;
使用异步读取:reader.ReadAsync()结合async/await,提高并发性能。 -
DataReader高级用法
类型映射:用GetOrdinal()获取列索引,避免硬编码列顺序(如reader.GetInt32(reader.GetOrdinal("OrderId")));
批量插入:结合SqlBulkCopy,用DataReader读取源数据直接批量插入目标表,性能远超单条插入;
关闭连接:DataReader必须在连接关闭前关闭,否则会导致连接泄漏(using自动处理)。 -
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










