C# ASP.NET核心web应用中ADO.NET大数据检索的性能
应用程序是用编写的.NET Core 2.1和数据库是SQL Server 2014 有一个页面必须从SQL Server检索8万到10万行数据。我已经为我认为合适的列添加了索引,查询可以在大约5秒钟内返回所有行。因此,由于应用程序必须迭代所有这些行并创建视图模型对象,因此需要另外6-8秒来循环并执行所有必要的逻辑。这导致总加载时间约为16-17秒 在任何人建议使用分页之前,这个视图被设计成一个Excel工作表,并通过一系列CSS有趣的操作使它看起来更美观。由于我在后端找不到任何更多的循环优化,并且我已经压缩了通过IIS Compress Dynamic选项返回的视图,所以我认为唯一可以节省一些时间的方法是批量查询数据库。我测试了一次执行10000个批处理的运行,结果在不到1秒的时间内返回到SQL管理中。因此,我编写了一些代码来启动一个C# ASP.NET核心web应用中ADO.NET大数据检索的性能,c#,sql-server,asp.net-core,ado.net,C#,Sql Server,Asp.net Core,Ado.net,应用程序是用编写的.NET Core 2.1和数据库是SQL Server 2014 有一个页面必须从SQL Server检索8万到10万行数据。我已经为我认为合适的列添加了索引,查询可以在大约5秒钟内返回所有行。因此,由于应用程序必须迭代所有这些行并创建视图模型对象,因此需要另外6-8秒来循环并执行所有必要的逻辑。这导致总加载时间约为16-17秒 在任何人建议使用分页之前,这个视图被设计成一个Excel工作表,并通过一系列CSS有趣的操作使它看起来更美观。由于我在后端找不到任何更多的循环优化,
任务
,该任务创建了自己的数据库连接,并对其10000条记录进行了查询,然后在一个循环中执行此操作,创建足够的任务来覆盖需要返回的记录量。这适用于上一个查询,该查询返回给定记录范围的最小和最大标识值,该范围不在付款期的日期范围内。然后,我对每个任务执行一个wait任务。WhenAll()
,认为这样可以缩短从SQL中最初提取数据时的等待时间。我发现这会导致几乎相同甚至更糟的等待时间
在这一点上,我对如何提高绩效没有任何想法,希望有人能提出另一个想法。我将提供我的任务
方法来调用数据库以获取数据块,这样您就可以看到我是否做错了什么
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;
}
}
}
}
}
public async Task GetOverViewInfoChunk(列表规则ID、列表状态、DateTime startWeek、DateTime endWeek、long firstId、long lastId、int batchNumber=10000)
{
DateTime now=DateTime.now;
字符串procName=_config.GetValue(nameof(GetOverViewInfoChunk));
IList tablelistTasks=新列表();
long rangeCounter=firstId+batchNumber-1;
IEnumerable tables=新列表();
while(true)
{
Dictionary@params=newdictionary();
@params.Add(“@prodictions”,_sqlHelper.getprodictionsfortopandbottom3(prodictioneids.ToString());
@参数Add(“@startWeekDate”,startWeek.ToString(“yyyy-MM-dd”);
@参数Add(“@lastwekdate”,endWeek.ToString(“yyyy-MM-dd”));
@params.Add(“@jobStatuses”,_sqlHelper.getjobsoverviewstatuses(statuses.ToString());
@Add(“@firstId”,firstId.ToString());
@Add(“@lastId”,rangeCounter>lastId?lastId.ToString():rangeCounter.ToString());
tablelistTasks.Add(new Classes.SQLActionHelper(_config.GetValue(“Data:XXXX”)).GetBatch(procName,@params));
//增量范围变量
firstId+=批次号;
rangeCounter+=批次号;
如果(第一个ID>最后一个ID)
打破
}
尝试
{
tables=等待任务.WhenAll(tablelistTasks);
}
捕获(例外情况除外)
{
}
TimeSpan基准=DateTime.Now-Now;
返回表;
}
//用于批量查询数据的类
公共类SQLActionHelper
{
私有字符串_connStr;
公共SQLActionHelper(字符串connStr)
{
_connStr=connStr;
}
公共异步任务GetBatch(字符串procName,Dictionary@params)
{
数据表dt=null;
//创建连接对象和命令对象
使用(SqlConnection conn=newsqlconnection(_connStr))
{
使用(SqlCommand=newsqlcommand(procName,conn))
{
//打开连接
等待连接OpenAsync();
//这是一个存储过程
command.CommandType=System.Data.CommandType.StoredProcess;
//向命令中添加键值对,以便将参数传递给sql proc或查询
foreach(@params中的KeyValuePair kvp)
{
command.Parameters.AddWithValue(kvp.Key,kvp.Value);
}
使用(SqlDataReader=await命令.ExecuteReaderAsync())
{
dt=新的System.Data.DataTable();
dt.负载(读卡器);
返回dt;
}
}
}
}
}
响应时间慢的原因不是因为查询计划不好,或者数据库表没有索引,或者需要分页或技术效率低下。问题是我忽略了最基本的问题多余数据
虽然我承认我忽略了这样一个明显的设计缺陷有点尴尬,但我认为重要的是要记住,在存储数据时,尤其是当数据需要检索并在以后呈现给用户时,首先要看的是:“什么是必要的?”。虽然现在数据库存储相对便宜,但检索和显示数据库的成本却不高
我理解这篇文章的全部内容都是导致关闭的原因,因为它本身不是可复制的或编码问题,而是设计缺陷。也许它会提出一些对SO用户有用的建议。我将把这个决定留给社区和版主。感谢那些提出意见的人。没有一个真正的人可以一次处理80k-100K行?当然有一些过滤的可能?TBH在17秒内将这么多行放在前端似乎很合理?有去b的