C# -SQL: как выполнить пакет StoredProcedure?

Обновлено:
Моя проблема больше не проблема: я повторил свои тесты производительности, и у меня есть фатальная глупая ошибка: я забыл x1000, чтобы получить секунды из миллисекунд: / Простите за это, ребята. Для информации:
- Я делаю около 1900 обновлений в секунду с моего ПК на сервер базы данных в локальной сети. - 3.200 обновлений в секунду, если программы находятся на той же машине, что и DB.
. - 3.500 обновлений в секунду с моего ПК на сервере базы данных. Я не создаю заново и не открываю новый SQLConnection.
- 5,800 обновлений в секунду с пакетным текстом. Для моих 10.000 строк, если это займет 5 секунд, это нормально для моих программ. Извините за беспокойство.

На самом деле я использую хранимую процедуру SQL для создания строки в моей базе данных, чтобы избежать SQL-инъекции. В C# у меня есть следующий метод:

public void InsertUser(string userCode)
{
   using (SqlConnection sqlConnection = new SqlConnection(this.connectionString))
   {
      SqlCommand sqlCommand = new SqlCommand("InsertUser", sqlConnection);
      sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
      sqlCommand.Parameters.Add(new SqlParameter("@UserCode", userCode));
      sqlConnection.Open();
      sqlCommand.ExecuteNonQuery();///0.2 seconds !ERROR HERE! 0.2ms here,NOT 0.2sec!!!
   }
} 

Он отлично работает, когда мне нужно вставить одну или две строки. Но если мне нужно создать 1.000 пользователей, 10.000 продуктов и 5000 домашних животных, это не лучшее решение: я потеряю огромное количество времени на сетевой транспорт.

Я считаю, что без проверки я могу использовать только ограниченное количество обратных вызовов. Поэтому я не хочу звонить 10.000 раз:

sqlCommand.BeginExecuteNonQuery()

Другой способ - создать пакетный текст, но есть риск SQL-инъекции (и это некрасиво).

Есть ли объект SqlCommandList, который управляет этим в .Net? Как мне делать большие записи в базе данных? Что хорошего для этого?

Я бы рекомендовал использовать sqlCommand.Prepare (); с типом параметра, который должен улучшить время выполнения как минимум второго запроса.

J Pollack 21.05.2012 11:22
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
17 495
9

Ответы 9

Рассматривали ли вы возможность передать XML-документ в хранимую процедуру, а затем выполнить итерацию по нему, чтобы найти данные для вставки?

Лично я, если я регулярно ожидаю делать довольно большие вставки (10 000 строк определенно подходят ...), я мог бы подумать о создании отдельной таблицы для входящих данных и использовать SqlBulkCopy для заполнения этой таблицы. Затем вы просто выполняете одну хранимую процедуру, которая перемещает данные в реальную таблицу.

Другой подход - отправить xml в базу данных и использовать sqlxml для его анализа (намного проще с SQL2005 и выше), но это требует дополнительной работы на сервере db.

"это не лучшее решение: я потеряю кучу времени в сетевом транспорте" Вы можете жить с потерей?

Если это то, что вы делаете нечасто, какое это имеет значение? Сначала измерьте это, если это проблема, то исправьте ее, лично я, вероятно, выбрал бы таблицу Marc Gravells для входящих вставок. Другой вариант - запускать вставки асинхронно, тогда вам не нужно ждать завершения каждой, прежде чем начинать следующую.

На это у меня ушли годы, но, наконец, я понял, что не стоит тратить время на оптимизацию кода, который в этом не нуждается.

Надеюсь, это поможет (хотя я не думаю, что это поможет, извините).

Это должно работать немного быстрее:

public void InsertUser(IEnumerable<string> userCodes)
{
   using (SqlConnection sqlConnection = new SqlConnection(this.connectionString), 
             SqlCommand sqlCommand = new SqlCommand("InsertUser", sqlConnection))
   {
      sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
      SqlParameter param = sqlCommand.Parameters.Add("@UserCode", SqlDbTypes.VarChar);
      sqlConnection.Open();

      foreach(string code in userCodes)
      {
          param.Value = code;
          sqlCommand.ExecuteNonQuery();///0.2 seconds
      }
   }
}

Это откроет только одно соединение и создаст только одну команду, даже если вы передадите ей 1000 пользователей. Тем не менее, он по-прежнему будет делать каждую вставку отдельно. И, конечно, если userCode не является строкой, вы захотите соответствующим образом перефакторить ее. Вы также можете изучить команду SQL Server ОБЪЕМНАЯ ВСТАВКА.

Если бы вы действительно были обеспокоены этим, вы могли бы (как вы сказали) объединить команды в следующие строки:

var cmd = new SqlCommand();
cmd.Connection = sqlConnection;
for (int i = 0; i < batchSize; i++) {
    cmd.CommandText += String.Format("EXEC InsertUser @UserCode{0};", i);
    cmd.Parameters.AddWithValue("@UserCode" + i.ToString(), XXXXX);
    //... etc ...
}

Поскольку в этой схеме вы будете использовать параметр, у вас не будет большего риска SQL-инъекции, чем если бы вы использовали сохраненную процедуру. Но я сомневаюсь, что вы действительно сэкономите на этом заметное количество времени. ИМО, вы должны просто упростить и сделать так, как вы это делаете сейчас.

При повторном создании команды и постоянном открытии и закрытии новых подключений возникает МНОГО накладных расходов.

Joel Coehoorn 14.10.2008 17:12

Кроме того, если вы собираетесь сделать это таким образом, используйте конструктор строк для сборки команды.

Joel Coehoorn 14.10.2008 17:14

Джоэл, я думаю, вы обнаружите, что объединение подключений значительно ускоряет работу. А что касается StringBuilder, это правда, что это ускорит процесс и уменьшит использование памяти, но все зависит от того, сколько команд вы хотите в своем пакете ...

Dave Markle 14.10.2008 18:15

Основываясь на ответе Джоэла, это самое быстрое решение, за исключением использования SqlBulkCopy или создания больших строк беспорядочного SQL и выполнения. (Я добавил транзакцию, которая значительно улучшит производительность)

public void InsertUser(IEnumerabler<string> userCodes)
{
   using (SqlConnection sqlConnection = new SqlConnection(this.connectionString))
   {
      sqlConnection.Open();
      SqlTransaction transaction = connection.BeginTransaction();
      SqlCommand sqlCommand = new SqlCommand("InsertUser", sqlConnection);
      sqlCommand.Transaction = transaction;
      sqlCommand.CommandType = System.Data.CommandType.StoredProcedure;
      SqlParameter param = sqlCommand.Parameters.Add("@UserCode", SqlDbTypes.VarChar);

      foreach(string code in userCodes)
      {
          param.Value = code;
          sqlCommand.ExecuteNonQuery();
      }      
      transaction.Commit();
   }
}

Транзакции включают блокировку ... если это выполняется изолированно, это будет быстрее. Но если на сервере есть другая активность, это может действительно сильно замедлить работу. Хотя признаю, что выполнение только вставок вряд ли приведет к конфликту.

Joel Coehoorn 14.10.2008 17:31

Согласно некоторым из приведенных выше ответов, наиболее заметное увеличение производительности при минимальных усилиях включает 2 изменения в существующем коде:

  1. Обертывание обновлений в транзакции
  2. Открытие только одного соединения и многократный вызов процедуры с разными параметрами.

BULK INSERT - вариант, но, вероятно, излишний для того, что вы хотите сделать.

А как насчет UpdateBatchSize для SQLDataAdaptor?

Наши специалисты по интерфейсу используют это, чтобы разбить несколько 10000 вызовов процессов на куски.

Статья

MSDN

Наша среда запрещает права "bulkadmin", поэтому мы не можем использовать BULKINSERT / bcp и т. д.

Думаю, это довольно старый вопрос.

В SQL Server 2008 теперь можно использовать параметр значения таблицы. Короче говоря, передайте все свои переменные в используемом определенном типе (таблице).

В SQL теперь вы можете обрабатывать все записи как отдельные элементы ... Фактически, используйте логику набора и получите реальную производительность.

Другие вопросы по теме