WedX - журнал о программировании и компьютерных науках

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

Правка:
Моя проблема больше не проблема: я повторил свои тесты производительности и сделал фатальную глупую ошибку: я забыл x1000, чтобы получить секунды из миллисекунд :/ Извините за это, ребята.
Для информации:
- Я делаю около 1900 обновлений в секунду со своего ПК на сервер базы данных в локальной сети.
- 3200 обновлений в секунду, если программы находятся на той же машине, что и БД.
- 3500 обновлений в секунду с моего ПК на сервере базы данных я не создаю заново и повторно не открываю новый SQLConnection.
- 5.800 обновлений в секунду с пакетным текстом. Для моих 10 000 строк, если это займет 5 секунд, это нормально для моих программ. Извините, что побеспокоил вас.

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

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!!!
   }
} 

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

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

sqlCommand.BeginExecuteNonQuery()

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

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


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

Ответы:


1

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

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 BULK INSERT.

14.10.2008

2

Как насчет UpdateBatchSize SQLDataAdaptor?

Наши ребята из внешнего интерфейса используют это для группировки нескольких 10 000 вызовов процедур в куски.

Статья

MSDN

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

14.10.2008

3

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

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

14.10.2008

4

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

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, чем если бы вы использовали хранимую процедуру. Но я сомневаюсь, действительно ли вы сэкономите значительное количество времени, делая это. ИМО, вы должны просто сделать это простым и сделать так, как вы это делаете сейчас.

14.10.2008
  • При повторном создании команды и постоянном открытии и закрытии новых соединений возникает МНОГО накладных расходов. 14.10.2008
  • Кроме того, если вы собираетесь сделать это так, используйте построитель строк для сборки команды. 14.10.2008
  • Джоэл, я думаю, вы обнаружите, что пул соединений значительно ускоряет работу. А что касается StringBuilder, это правда, что он ускорит работу и приведет к меньшему использованию памяти, но все зависит от того, сколько команд вы хотите в своем пакете... 14.10.2008

  • 5

    Судя по ответу Джоэла, это самое быстрое решение, за исключением использования 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();
       }
    }
    
    14.10.2008
  • Транзакции включают блокировку... если это работает изолированно, это будет быстрее. Но если на сервере есть другая активность, это может очень сильно замедлить работу. Хотя я признаю, что выполнение только вставок вряд ли приведет к конфликту. 14.10.2008

  • 6

    Я предполагаю, что это довольно старый вопрос.

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

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

    12.12.2011

    7

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

    14.10.2008

    8

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

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

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

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

    14.10.2008

    9

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

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

    МАССОВЫЕ ВСТАВКИ - это вариант, но, вероятно, излишний для того, что вы хотите сделать.

    14.10.2008
    Новые материалы

    Как проанализировать работу вашего классификатора?
    Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

    Работа с цепями Маркова, часть 4 (Машинное обучение)
    Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

    Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
    Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

    Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
    Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

    Учебные заметки: создание моего первого пакета Node.js
    Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

    Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
    Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..

    ИИ в аэрокосмической отрасли
    Каждый полет – это шаг вперед к великой мечте. Чтобы это происходило в их собственном темпе, необходима команда астронавтов для погони за космосом и команда технического обслуживания..


    Для любых предложений по сайту: [email protected]