在 ASP.NET 核心网络应用程序中 ADO.NET 大数据检索的性能
Performance with ADO.NET large data retrieval in ASP.NET Core web app
Application是用.NET Core 2.1写的,Database是SQL 服务器 2014.
有一个页面必须从SQL 服务器检索80-10 万行数据。我已将索引添加到我认为合适的列中,查询可以在大约 5 秒内 return 所有行。因此,由于应用程序随后必须迭代所有这些行并创建视图模型对象,因此还需要 6-8 秒来循环并执行所有必要的逻辑。这导致总加载时间约为 16-17 秒。
在有人建议使用分页之前,这个视图被设计成看起来像一个 Excel 工作表,并且做了很多 CSS 有趣的事情来让它看起来像样。由于我在后端找不到更多的循环优化,并且我已经通过 IIS Compress Dynamic 选项压缩了正在 returned 的视图,我认为唯一要做的另一件事可能是减少负载时间将是批量查询数据库。我测试了 运行 一次处理 10000 个批次,并在不到 1 秒的时间内 return 进入 SQL 管理。所以我编写了一些代码来触发一个 Task<DataTable>
创建它自己的数据库连接并查询其 10000 条记录的批次并在一个循环中执行此操作创建足够的任务来覆盖需要 return 的记录数量编辑。这解决了先前的查询,该查询 returns 给定记录范围的 MIN 和 MAX 标识值,该范围不在支付期的日期范围内。然后,我对这些任务中的每一个都执行了 await Task.WhenAll()
,认为这会缩短从 SQL 初始拉取数据时的等待时间。我发现这导致等待时间大致相同,甚至更糟。
目前我不知道如何提高性能,希望有人能提出另一个想法。我将提供我的 Task<DataTable>
调用数据库获取数据块的方法,以便您可以查看我是否做错了什么。
public async Task<IEnumerable<DataTable>> GetOverViewInfoChunk(List<int> disciplineIds, List<KeyValuePair<string, bool>> statuses, DateTime startWeek, DateTime endWeek, long firstId, long lastId, int batchNumber = 10000)
{
DateTime now = DateTime.Now;
string procName = _config.GetValue<string>(nameof(GetOverViewInfoChunk));
IList<Task<DataTable>> tablelistTasks = new List<Task<DataTable>>();
long rangeCounter = firstId + batchNumber - 1;
IEnumerable<DataTable> tables = new List<DataTable>();
while (true)
{
Dictionary<string, string> @params = new Dictionary<string, string>();
@params.Add("@disciplines", _sqlHelper.GetDisciplinesForTopAndBottom3(disciplineIds).ToString());
@params.Add("@startWeekDate", startWeek.ToString("yyyy-MM-dd"));
@params.Add("@lastWeekDate", endWeek.ToString("yyyy-MM-dd"));
@params.Add("@jobStatuses", _sqlHelper.GetJobOverviewStatuses(statuses).ToString());
@params.Add("@firstId", firstId.ToString());
@params.Add("@lastId", rangeCounter > lastId ? lastId.ToString() : rangeCounter.ToString());
tablelistTasks.Add(new Classes.SQLActionHelper(_config.GetValue<string>("Data:XXXX")).GetBatch(procName, @params));
//Increment range vars
firstId += batchNumber;
rangeCounter += batchNumber;
if (firstId > lastId)
break;
}
try
{
tables = await Task.WhenAll(tablelistTasks);
}
catch (Exception ex)
{
}
TimeSpan benchMark = DateTime.Now - now;
return tables;
}
//class for querying the data in batches
public class SQLActionHelper
{
private string _connStr;
public SQLActionHelper(string connStr)
{
_connStr = connStr;
}
public async Task<DataTable> GetBatch(string procName, Dictionary<string, string> @params)
{
DataTable dt = null;
//create the connection object and command object
using (SqlConnection conn = new SqlConnection(_connStr))
{
using (SqlCommand command = new SqlCommand(procName, conn))
{
//Open the connection
await conn.OpenAsync();
//its a stored procedure
command.CommandType = System.Data.CommandType.StoredProcedure;
//Add key value pairs to the command for passing parameters to sql proc or query
foreach (KeyValuePair<string, string> kvp in @params)
{
command.Parameters.AddWithValue(kvp.Key, kvp.Value);
}
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
dt = new System.Data.DataTable();
dt.Load(reader);
return dt;
}
}
}
}
}
响应时间慢的原因不是由于糟糕的查询计划、数据库表上没有索引、分页的必要性或低效技术。问题是我忽略了最基本的问题。 多余的数据。
虽然我承认我忽略了这样一个明显的设计缺陷有点尴尬,但我认为重要的是要记住,在存储数据时,尤其是如果以后需要检索并呈现给用户时,首先要做的是要看的是:“什么是必要的?”。虽然现在数据库存储相对便宜,但检索和呈现它的成本却不是。
我理解这个 post 整体是关闭的原因,因为它不可重现或本身是编码问题,但更多的是设计缺陷。也许它会提出一些对 SO 用户有帮助的建议。我会将这个决定留给社区和版主。感谢那些提出意见的人。
Application是用.NET Core 2.1写的,Database是SQL 服务器 2014.
有一个页面必须从SQL 服务器检索80-10 万行数据。我已将索引添加到我认为合适的列中,查询可以在大约 5 秒内 return 所有行。因此,由于应用程序随后必须迭代所有这些行并创建视图模型对象,因此还需要 6-8 秒来循环并执行所有必要的逻辑。这导致总加载时间约为 16-17 秒。
在有人建议使用分页之前,这个视图被设计成看起来像一个 Excel 工作表,并且做了很多 CSS 有趣的事情来让它看起来像样。由于我在后端找不到更多的循环优化,并且我已经通过 IIS Compress Dynamic 选项压缩了正在 returned 的视图,我认为唯一要做的另一件事可能是减少负载时间将是批量查询数据库。我测试了 运行 一次处理 10000 个批次,并在不到 1 秒的时间内 return 进入 SQL 管理。所以我编写了一些代码来触发一个 Task<DataTable>
创建它自己的数据库连接并查询其 10000 条记录的批次并在一个循环中执行此操作创建足够的任务来覆盖需要 return 的记录数量编辑。这解决了先前的查询,该查询 returns 给定记录范围的 MIN 和 MAX 标识值,该范围不在支付期的日期范围内。然后,我对这些任务中的每一个都执行了 await Task.WhenAll()
,认为这会缩短从 SQL 初始拉取数据时的等待时间。我发现这导致等待时间大致相同,甚至更糟。
目前我不知道如何提高性能,希望有人能提出另一个想法。我将提供我的 Task<DataTable>
调用数据库获取数据块的方法,以便您可以查看我是否做错了什么。
public async Task<IEnumerable<DataTable>> GetOverViewInfoChunk(List<int> disciplineIds, List<KeyValuePair<string, bool>> statuses, DateTime startWeek, DateTime endWeek, long firstId, long lastId, int batchNumber = 10000)
{
DateTime now = DateTime.Now;
string procName = _config.GetValue<string>(nameof(GetOverViewInfoChunk));
IList<Task<DataTable>> tablelistTasks = new List<Task<DataTable>>();
long rangeCounter = firstId + batchNumber - 1;
IEnumerable<DataTable> tables = new List<DataTable>();
while (true)
{
Dictionary<string, string> @params = new Dictionary<string, string>();
@params.Add("@disciplines", _sqlHelper.GetDisciplinesForTopAndBottom3(disciplineIds).ToString());
@params.Add("@startWeekDate", startWeek.ToString("yyyy-MM-dd"));
@params.Add("@lastWeekDate", endWeek.ToString("yyyy-MM-dd"));
@params.Add("@jobStatuses", _sqlHelper.GetJobOverviewStatuses(statuses).ToString());
@params.Add("@firstId", firstId.ToString());
@params.Add("@lastId", rangeCounter > lastId ? lastId.ToString() : rangeCounter.ToString());
tablelistTasks.Add(new Classes.SQLActionHelper(_config.GetValue<string>("Data:XXXX")).GetBatch(procName, @params));
//Increment range vars
firstId += batchNumber;
rangeCounter += batchNumber;
if (firstId > lastId)
break;
}
try
{
tables = await Task.WhenAll(tablelistTasks);
}
catch (Exception ex)
{
}
TimeSpan benchMark = DateTime.Now - now;
return tables;
}
//class for querying the data in batches
public class SQLActionHelper
{
private string _connStr;
public SQLActionHelper(string connStr)
{
_connStr = connStr;
}
public async Task<DataTable> GetBatch(string procName, Dictionary<string, string> @params)
{
DataTable dt = null;
//create the connection object and command object
using (SqlConnection conn = new SqlConnection(_connStr))
{
using (SqlCommand command = new SqlCommand(procName, conn))
{
//Open the connection
await conn.OpenAsync();
//its a stored procedure
command.CommandType = System.Data.CommandType.StoredProcedure;
//Add key value pairs to the command for passing parameters to sql proc or query
foreach (KeyValuePair<string, string> kvp in @params)
{
command.Parameters.AddWithValue(kvp.Key, kvp.Value);
}
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
dt = new System.Data.DataTable();
dt.Load(reader);
return dt;
}
}
}
}
}
响应时间慢的原因不是由于糟糕的查询计划、数据库表上没有索引、分页的必要性或低效技术。问题是我忽略了最基本的问题。 多余的数据。
虽然我承认我忽略了这样一个明显的设计缺陷有点尴尬,但我认为重要的是要记住,在存储数据时,尤其是如果以后需要检索并呈现给用户时,首先要做的是要看的是:“什么是必要的?”。虽然现在数据库存储相对便宜,但检索和呈现它的成本却不是。
我理解这个 post 整体是关闭的原因,因为它不可重现或本身是编码问题,但更多的是设计缺陷。也许它会提出一些对 SO 用户有帮助的建议。我会将这个决定留给社区和版主。感谢那些提出意见的人。