Можно ли создать один SQL запрос для десятков операторов UPDATE? Должно ли это повысить производительность? Как это должно выглядеть в моем случае? - PullRequest
0 голосов
/ 09 апреля 2020

У меня проблема. В приведенном ниже коде есть foreach l oop, где создается строка запроса для каждого объекта truck внутри trucks коллекции. L oop повторяется 100 раз (например, 100 объектов в коллекции). И метод с l oop вызывается 2000 раз.

Я отказался от использования ORM для повышения производительности. Но, к сожалению, я все еще немного разочарован скоростью выполнения кода.

Обратите внимание, что для каждого повторяющегося объекта создается string query, который, например, выглядит следующим образом:

UPDATE dbo.Trucks 
SET OfficialNumber = '5124095' 
, Status = 'Undefined' 
, PerformancesUpdate = @PerformancesUpdate
, PictureLink = 'http://www.somewebsite.com/photos/middle////.jpg'
WHERE TruckId = 405664;

и затем, после анализа command.Parameters, если они существуют (в данном случае @PerformancesUpdate), запрос отправляется библиотечным методом с int rows = command.ExecuteNonQuery(); //execute SQL. И так, 100 раз. Обратите внимание, что строка запроса зависит от свойств объекта, поэтому каждый случай будет отличаться.

Вот мои вопросы . Мне интересно, возможно ли создать один запрос для всех 100 объектов в l oop и отправить его сразу? Если да, должно ли это повысить производительность? Если да, как должен выглядеть пример запроса?

А вот код, который создает l oop, где создаются и выполняются SQL запросов:

using (SqlConnection connection = GetConnection(_connectionString))
                using (SqlCommand command = connection.CreateCommand())
                {
                    connection.Open();


                    foreach (SomeObject truck in trucks)
                    {
                        Console.WriteLine("Updating " + counter++ + " of " + trucks.Count);

                        //clean up string for basic data
                        truck.Status = CleanUpStringCases(truck.Status);
                        truck.Destination = CleanUpStringCases(truck.Destination);
                        //clean up string for full AIS data
                        if (dataType == "full")
                        {
                            truck.TruckType = CleanUpStringCases(truck.TruckType);
                        }

                        //PARSE

                        SomeObject existing = new SomeObject();

                        //find existing truck to be updated
                        if (truck.OfficialNumber > 0) existing = _context.trucks.Where(v => v.OfficialNumber == truck.OfficialNumber).FirstOrDefault();
                        StringBuilder querySb = new StringBuilder();

                        if (existing != null)
                        {
                            //update for basic data
                            querySb.Append("UPDATE dbo." + _trucksTableName + " SET OfficialNumber = '" + truck.OfficialNumber + "'");
                            if (existing.MNCI == 0) if (truck.MNCI.HasValue) querySb.Append(" , MNCI = '" + truck.MNCI + "'");
                            if (truck.LatestActivity.HasValue) querySb.Append(" , LatestActivity = @LatestActivity");
                            if (truck.ETA.HasValue) querySb.Append(" , ETA = @ETA");
                            if (!string.IsNullOrEmpty(truck.Status)) querySb.Append(" , Status = '" + truck.Status + "'");
                            if (!string.IsNullOrEmpty(truck.Destination)) querySb.Append(" , Destination = '" + truck.Destination + "'");
                            if (!string.IsNullOrEmpty(truck.Area)) querySb.Append(" , Area = '" + truck.Area + "'");

                            if (truck.HeadingTo.HasValue) querySb.Append(" , HeadingTo = @HeadingTo");
                            if (truck.Lat.HasValue) querySb.Append(" , Lat = @Lat");
                            if (truck.Lon.HasValue) querySb.Append(" , Lon = @Lon");
                            if (truck.Speed.HasValue)
                            {
                                querySb.Append(" , Speed = @Speed");
                                if ((existing.SpeedMax < truck.Speed || existing.SpeedMax == null) && truck.Speed != 0) querySb.Append(" , SpeedMax = @Speed"); //update speed max
                            }

                            //string for full AIS data
                            if (dataType == "full")
                            {
                                if (truck.PerformancesUpdate.HasValue) querySb.Append(" , PerformancesUpdate = @PerformancesUpdate");
                                if (!string.IsNullOrEmpty(truck.TruckType)) querySb.Append(" , TruckType = '" + truck.TruckType + "'");
                                if (!string.IsNullOrEmpty(truck.PictureLink)) querySb.Append(" , PictureLink = '" + truck.PictureLink + "'");
                                if (truck.LOA.HasValue) querySb.Append(" , LOA = '" + truck.LOA + "'");
                                if (truck.Height.HasValue) querySb.Append(" , Height = '" + truck.Height + "'");
                            }

                            querySb.Append(" WHERE truckId = " + existing.truckId + "; ");
                        }

                        try
                        {
                            string query = querySb.ToString();

                            command.CommandText = query;
                            if (query.Contains("LatestActivity ="))
                                command.Parameters.AddWithValue("@LatestActivity", truck.LatestActivity);
                            if (query.Contains("ETA ="))
                                command.Parameters.AddWithValue("@ETA", truck.ETA);
                            if (query.Contains("PerformancesUpdate ="))
                                command.Parameters.AddWithValue("@PerformancesUpdate", truck.PerformancesUpdate);
                            if (query.Contains("HeadingTo ="))
                                command.Parameters.AddWithValue("@HeadingTo", truck.HeadingTo);
                            if (query.Contains("Lat ="))
                                command.Parameters.AddWithValue("@Lat", truck.Lat);
                            if (query.Contains("Lon ="))
                                command.Parameters.AddWithValue("@Lon", truck.Lon);
                            if (query.Contains("Speed ="))
                                command.Parameters.AddWithValue("@Speed", truck.Speed);

                            command.CommandTimeout = 30;
                            command.CommandType = CommandType.Text;

                            int rows = command.ExecuteNonQuery(); //execute SQL
                            command.Parameters.Clear();
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                        }
                    }

                    connection.Close();
                }

Решение: Подход Мартина помечен как решение. И, отвечая на вопрос о повышении производительности, после тестирования его подхода к локальной БД я могу сказать, что скорость обновления БД увеличивается прямо пропорционально количеству обновляемых строк в одном запросе. Например, при обновлении 300 строк мне потребовалось около 30 с вместе с запросами API. При обновлении 1000 строк все это занимает около 80-х годов. И, как уже упоминалось, единственным ограничением является количество параметров , поэтому я просто конвертирую double и DateTime в формат SQL, избавляюсь от параметров и voila .

Ответы [ 2 ]

1 голос
/ 09 апреля 2020

Да, можно отправлять несколько команд обновления за одну операцию.

Вам необходимо добавить команды в свой построитель строк и пронумеровать параметры SQL.

SqlCommand command = connection.CreateCommand()
var querySb = new Stringbuilder();

for(int i = 0; i < trucks.Count; i++)
{
    [...]

    if (truck.Speed.HasValue)
    {
        querySb.Append(" , Speed = @Speed" + i);
        command.Parameters.AddWithValue("@Speed" + i, truck.Speed);
    }

    querySb.AppendLine();
}
command.CommandText = querySb.ToString();
command.ExecuteNonQuery();

Вы можете создавать меньшие пакеты строк (не отправлять все строки сразу), максимальное количество параметров в одной команде - 2098.

0 голосов
/ 09 апреля 2020

Я рекомендую использовать некоторые виды массовых операций, так как несколько отдельных циклов обработки дБ приводят к избыточным накладным расходам (как вы заметили).

Массовые обновления могут быть реализованы, т. Е. Следующими методами:

  • Используя внешнюю библиотеку , которая специализируется на высокоскоростных массовых операциях
  • Создавая sql хранимую процедуру, которая принимает массив данных в качестве параметра. Дополнительную информацию о табличных параметрах можно найти в https://docs.microsoft.com/en-us/sql/relational-databases/tables/use-table-valued-parameters-database-engine
  • Сохранить все строки с помощью операции BULK INSERT во временной таблице и запустить T- SQL MERGE операция из временной таблицы в фактическую таблицу данных с одной транзакцией базы данных.
...