我在SQL server中有一个名为 Accounts 的sql表,其中数据按日期排序。该表有20列,包括AccountId
我想阅读每天的记录(约200K记录)。这样我就必须每天读取6个月的数据。
我需要做的是从Accounts Table中获取6个月的数据记录。所以我计划了我的代码,以便在每天的do while循环中从SQL Server Accounts
表中获取数据。
现在,每天都包含从数据库中提取200K记录。因此,我将这200K记录分成一天进行批量处理(让我们在一次读取中记录10000或20000条记录,这使得一天内有大约10批记录)。一旦我得到这些10k或20k记录,我想从数据库中获取这些值并将其转换为csv文件并将csv文件导出到某个位置。
现在,我的问题是这个程序耗费了太多时间(大约需要50分钟才能获取一天的记录,我需要获取6个月数据的记录。所以你可以想象需要花多少时间)
我正在考虑使用TPL来破解代码并处理成任务,但不知道如何去做。
请建议如何使用Task并行库来提高性能,以便我可以轻松获得6个月的数据。
我的C#代码如下所示:
public void Main()
{
do
{
done = true;
var accountsTableRecors = ReadsDatabaseForADay(lastId);
foreach (var accountsHistory in accountsTableRecors)
{
if (accountsHistory.accountsId != null) lastId = (long)accountsHistory.accountsId;
done = false;
recordCount++;
}
var flatFileDataList = ProcessRecords();
} while (!done);
}
ProcessRecords
上面的Main()
方法解析了一些xml并将获取的数据转换为csv。
private IEnumerable<AccountsTable> ReadsDatabaseForADay(long lastId)
{
var transactionDataRecords = DatabaseHelper.GetTransactions(lastId, 10000);
var accountsTableData = transactionDataRecords as IList<AccountsTable> ?? transactionDataRecords.ToList();
ListAccountsTable.AddRange(accountsTableData);
return accountsTableData;
}
DatabaseHelperClass
:
internal static IEnumerable<AccountsTable> GetTransactions(long lastTransactionId, int count)
{
const string sql = "SELECT TOP(@count) [accounts_id],[some_columns],[some_other_columns]. .....[all_other_columns] "
+ "FROM AccountsTable WHERE [accounts_id] > @LastTransactionId AND [creation_dt] > DATEADD(DAY,-1, GETDATE())" +
" ORDER BY [accounts_id]";
return GetData(connection =>
{
var command = new SqlCommand(sql, connection);
command.Parameters.AddWithValue("@count", count);
command.Parameters.AddWithValue("@LastTransactionId", lastTransactionId);
return command;
}, DataRecordToTransactionHistory);
}
private static IEnumerable<T> GetData<T>(Func<SqlConnection, SqlCommand> commandBuilder, Func<IDataRecord, T> dataFunc)
{
using (var connection = GetOpenConnection())
{
using (var command = commandBuilder(connection))
{
command.CommandTimeout = 0;
using (IDataReader reader = command.ExecuteReader())
{
while (reader.Read())
{
var record = dataFunc(reader);
yield return record;
}
}
}
}
}
答案 0 :(得分:0)
您的代码中存在一些问题,这可能会导致性能下降。首先,每次获得另一批帐户表时,您都会从IEnumerable
创建一个列表。 List<T>
在内部使用一个数组,默认情况下会转到大对象堆,并且该堆不经常GC,所以到时候你会遇到内存问题和性能缺陷。
第二个问题是您的代码是高度面向I / O的,但是您不使用async
方法,因此等待数据库响应会阻止您的线程。您应该切换到async
版本并仅在Main
入口点内使用屏蔽方法,但在这种情况下,您将无法yield
您的结果,{{1} }不能是迭代器,您需要编辑async Task<IEnumerable>
函数或在DataRecordToTransactionHistory
函数内本地创建列表:
GetData
然而,这两种选择都有其缺点。至于我,你应该尝试使用这样的处理管道TPL Dataflow
库:
private static async Task GetData(Func<SqlConnection, SqlCommand> commandBuilder,
Action<IDataRecord> dataFunc)
{
using (var connection = new SqlConnection())
{
await connection.OpenAsync();
using (var command = commandBuilder(connection))
{
command.CommandTimeout = 0;
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
dataFunc(reader);
}
}
}
}
}
// OR
private static async Task<IEnumerable<T>> GetData<T>(Func<SqlConnection, SqlCommand> commandBuilder,
Func<IDataRecord, T> dataFunc)
{
using (var connection = new SqlConnection())
{
await connection.OpenAsync();
using (var command = commandBuilder(connection))
{
command.CommandTimeout = 0;
using (var reader = await command.ExecuteReaderAsync())
{
// linked list to avoid allocation of an array
var result = new LinkedList<T>();
while (await reader.ReadAsync())
{
result.AddLast(dataFunc(reader));
}
return result;
}
}
}
}
// default linking options
var options = new DataflowLinkOptions { PropagateCompletion = true };
// store all the ids you need to process
var idBlock = new BufferBlock<long>();
// transform id to list of AccountsTable
var transformBlock = new TransformBlock<long, Task<IEnumerable<AccountsTable>>>(async id =>
{
return await ReadsDatabaseForADay(id);
});
// connect the blocks between each other
idBlock.LinkTo(transformBlock, options);
// flatten the already completed task to enumerable
var flattenBlock = new TransformManyBlock<Task<IEnumerable<AccountsTable>>, AccountsTable>(async tables =>
{
return (await tables).Select(t => t);
});
transformBlock.LinkTo(flattenBlock, options);
// gather results in batches of 10000
var batchBlock = new BatchBlock<AccountsTable>(10000);
flattenBlock.LinkTo(batchBlock);
// export resulting array to csv file
var processor = new ActionBlock<AccountsTable[]>(a =>
{
ExportToCSV(a);
});
// populate the pipeline with needed ids
foreach (var id in GetAllIds())
{
// await the adding for each id
await idBlock.SendAsync(id);
}
// notify the pipeline that all the data has been sent
idBlock.Complete();
// await whole ids to be processed
await processor.Completion;
在默认线程池中执行并使用它的所有优点。您可以调整块with MaxDegreeOfParrallelism
,以便它可以立即处理多个ID。
因此,您应该使用TPL Dataflow
方法,并且不应仅为存储数据创建太多缓冲区列表/数组。组装管道以充分利用迭代器和async
函数。