#c# #sql-server #asp.net-core #ado.net
Вопрос:
Приложение написано на .NET Core 2.1, а база данных-SQL Server 2014.
Существует страница, которая должна извлекать 80-100 тысяч строк данных с SQL Server. Я добавил индексы к столбцам, которые посчитал подходящими, и запрос может вернуть все строки примерно за 5 секунд. Таким образом, поскольку приложению необходимо затем повторить все эти строки и создать объекты модели представления, для выполнения цикла и выполнения всей необходимой логики требуется еще 6-8 секунд. Это приводит к тому, что общее время загрузки составляет около 16-17 секунд.
Прежде чем кто-либо предложит использовать разбиение на страницы, это представление должно выглядеть как рабочий лист Excel и выполнять множество функций CSS, чтобы оно выглядело презентабельно. Поскольку я не могу найти больше оптимизации цикла в бэкэнде, и я сжал представление, возвращаемое с помощью динамической опции сжатия IIS, я подумал, что единственное, что еще можно сделать, чтобы, возможно, сократить время загрузки, — это запросить базу данных пакетами. Я протестировал выполнение пакета из 10000 за раз, и это вернулось в управление SQL менее чем за 1 секунду. Поэтому я закодировал вещи, чтобы запустить Task<DataTable>
приложение, которое создает собственное подключение к базе данных и запрашивает пакет из 10000 записей, и делаю это в цикле, создавая достаточное количество задач, чтобы покрыть количество записей, которые необходимо вернуть. Это работает с предыдущим запросом, который возвращает МИНИМАЛЬНОЕ и МАКСИМАЛЬНОЕ значение идентификатора для данного диапазона записей, диапазон не относится к диапазону дат периодов оплаты. Затем я выполняю 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;
}
}
}
}
}
Комментарии:
1. Ни один реальный человек не может иметь дело с 80-100 тысячами строк одновременно? Конечно, возможна какая-то фильтрация? TBH получение такого количества строк на переднем конце за 17 секунд кажется вполне разумным? При перемещении такого количества данных возникнут значительные задержки…
2. @DaleK Спасибо вам за ваш ответ. Я пересмотрел свой дизайн и считаю, что нашел лучший способ управления данными. Приятно знать, что я, по крайней мере, был в курсе времени отклика, когда имел дело с таким количеством данных. Я могу удалить или закрыть это сообщение, как только увижу, устраняет ли новый дизайн всю проблему.
3. Одновременный вызов базы данных несколько раз не будет быстрее, если узким местом является ввод-вывод. Вместо этого вам, вероятно, понадобится какая-то разбивка на страницы в пользовательском интерфейсе. Посмотрите на разбиение набора ключей на страницы, оно намного эффективнее, чем другие методы. Вы не должны передавать параметры даты в виде строк, передавайте их как
date
илиdatetime
.4. Не видя фактического запроса, определений таблиц и индексов, а также плана запроса, я не могу дать дальнейших рекомендаций
5. Похоже на случай, когда клиент не знает, о чем он просит. Если в представлении электронной таблицы содержится более 2000 или около того заданий (строк), они не смогут эффективно управлять этими данными. Опять же… есть наука, чтобы доказать это.
Ответ №1:
Причина медленного времени отклика не была связана с плохими планами запросов, или отсутствием индексации таблиц базы данных, или необходимостью разбиения на страницы или неэффективной технологией. Проблема заключалась в том, что я просмотрел самую основную проблему. Лишние данные.
Хотя я признаю, что мне несколько неловко, что я упустил из виду такой вопиющий недостаток дизайна, я думаю, что важно помнить, что при хранении данных, особенно если их необходимо извлечь и представить пользователю позже, первое, на что нужно обратить внимание, это: «Что необходимо?». В то время как хранение базы данных в наши дни относительно недорого, затраты на ее извлечение и представление не являются таковыми.
Я понимаю, что этот пост в целом является причиной для закрытия, поскольку он не воспроизводим или является проблемой кодирования как таковой, но скорее недостатком дизайна. Возможно, он выскажет некоторые предложения, которые могут быть полезны пользователям SO. Я оставлю это решение на усмотрение сообщества и модераторов. Спасибо тем, кто высказал свои замечания.