Производительность с ADO.NET поиск больших данных в ASP.NET Основное веб-приложение

#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. Я оставлю это решение на усмотрение сообщества и модераторов. Спасибо тем, кто высказал свои замечания.