Как преобразовать медленные параметризованные вставки в быстрое массовое копирование (даже из памяти) - PullRequest
3 голосов
/ 24 сентября 2008

У меня было что-то подобное в моем коде (.Net 2.0, MS SQL)

SqlConnection connection = new SqlConnection(@"Data Source=localhost;Initial
Catalog=DataBase;Integrated Security=True");
  connection.Open();

  SqlCommand cmdInsert = connection.CreateCommand();
  SqlTransaction sqlTran = connection.BeginTransaction();
  cmdInsert.Transaction = sqlTran;

  cmdInsert.CommandText =
     @"INSERT INTO MyDestinationTable" +
      "(Year, Month, Day, Hour,  ...) " +
      "VALUES " +
      "(@Year, @Month, @Day, @Hour, ...) ";

  cmdInsert.Parameters.Add("@Year", SqlDbType.SmallInt);
  cmdInsert.Parameters.Add("@Month", SqlDbType.TinyInt);
  cmdInsert.Parameters.Add("@Day", SqlDbType.TinyInt);
  // more fields here
  cmdInsert.Prepare();

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] {' '};
  String[] records;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    cmdInsert.Parameters["@Year"].Value = Int32.Parse(records[0].Substring(0, 4));
    cmdInsert.Parameters["@Month"].Value = Int32.Parse(records[0].Substring(5, 2));
    cmdInsert.Parameters["@Day"].Value = Int32.Parse(records[0].Substring(8, 2));
    // more here complicated stuff here
    cmdInsert.ExecuteNonQuery()
  }
  sqlTran.Commit();
  connection.Close();

С cmdInsert.ExecuteNonQuery () закомментировано, этот код выполняется менее чем за 2 секунды. При выполнении SQL это занимает 1 м 20 сек. Есть около 0,5 миллионов записей. Стол опорожняется раньше. Задача потока данных служб SSIS с аналогичной функциональностью занимает около 20 секунд.

  • Массовая вставка была не вариант (см. Ниже). Во время этого импорта я сделал кое-что необычное.
  • Мой тестовый компьютер - Core 2 Duo с 2 ГБ ОЗУ.
  • При просмотре в диспетчере задач ЦП не был полностью урезан. IO, похоже, также не был полностью использован.
  • Схема проста, как ад: одна таблица с AutoInt в качестве основного индекса и менее 10 дюймов, крошечные числа и символы (10).

После некоторых ответов здесь я обнаружил, что можно выполнить массовое копирование из памяти ! Я отказывался использовать массовые копии, потому что я думал, что это должно быть сделано из файла ...

Теперь я использую это, и это занимает около 20 секунд (как задача SSIS)

  DataTable dataTable = new DataTable();

  dataTable.Columns.Add(new DataColumn("ixMyIndex", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Year", System.Type.GetType("System.Int32")));   
  dataTable.Columns.Add(new DataColumn("Month", System.Type.GetType("System.Int32")));
  dataTable.Columns.Add(new DataColumn("Day", System.Type.GetType("System.Int32")));
 // ... and more to go

  DataRow dataRow;
  object[] objectRow = new object[dataTable.Columns.Count];

  Stream stream = new FileStream(fileName, FileMode.Open, FileAccess.Read);

  StreamReader reader = new StreamReader(stream);
  char[] delimeter = new char[] { ' ' };
  String[] records;
  int recordCount = 0;
  while (!reader.EndOfStream)
  {
    records = reader.ReadLine().Split(delimeter, StringSplitOptions.None);

    dataRow = dataTable.NewRow();
    objectRow[0] = null; 
    objectRow[1] = Int32.Parse(records[0].Substring(0, 4));
    objectRow[2] = Int32.Parse(records[0].Substring(5, 2));
    objectRow[3] = Int32.Parse(records[0].Substring(8, 2));
    // my fancy stuf goes here

    dataRow.ItemArray = objectRow;         
    dataTable.Rows.Add(dataRow);

    recordCount++;
  }

  SqlBulkCopy bulkTask = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, null);
  bulkTask.DestinationTableName = "MyDestinationTable"; 
  bulkTask.BatchSize = dataTable.Rows.Count;
  bulkTask.WriteToServer(dataTable);
  bulkTask.Close();

Ответы [ 12 ]

9 голосов
/ 24 сентября 2008

Вместо вставки каждой записи по отдельности, попробуйте использовать класс SqlBulkCopy для массовой вставки всех записей одновременно.

Создайте DataTable и добавьте все свои записи в DataTable, а затем используйте SqlBulkCopy . WriteToServer для массовой вставки всех данных сразу.

3 голосов
/ 24 сентября 2008

Требуется ли сделка? Для использования транзакции требуется гораздо больше ресурсов, чем для простых команд.

Также, если вы уверены, что введенные значения являются базовыми, вы можете использовать BulkInsert.

2 голосов
/ 24 сентября 2008

1 минута звучит довольно разумно для 0,5 миллиона записей. Это запись каждые 0,00012 секунды.

Есть ли в таблице индексы? Удаление и повторное применение после массовой вставки улучшит производительность вставок, если это возможно.

1 голос
/ 24 сентября 2008

Если какая-либо форма массовой вставки не подходит, другим способом будет использование нескольких потоков, каждый из которых имеет собственное подключение к базе данных.

Проблема с текущей системой заключается в том, что у вас есть 500 000 обращений в базу данных, и вы ожидаете завершения первого обхода, прежде чем начинать следующее - любой тип задержки (то есть сеть между машинами) будет означать что большая часть вашего времени уходит на ожидание.

Если вы можете разделить работу, возможно, с помощью какой-либо формы настройки производителя / потребителя, вы можете обнаружить, что вы можете получить гораздо большее использование всех ресурсов.

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

Служба SSIS будет работать быстро, потому что она использует метод массовой вставки - сначала выполните всю сложную обработку, сгенерируйте окончательный список данных для вставки и передайте их одновременно для массовой вставки.

1 голос
/ 24 сентября 2008

Если вам нужна лучшая скорость, вы можете рассмотреть возможность массовой вставки:

http://msdn.microsoft.com/en-us/library/ms188365.aspx

1 голос
/ 24 сентября 2008

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

0 голосов
/ 14 октября 2008

BULK INSERT = bcp из разрешения

Вы можете группировать ВСТАВКИ, чтобы уменьшить количество витков SQLDataAdaptor.UpdateBatchSize = 10000 дает 50 поездок туда и обратно

У вас все еще есть вставки по 500 КБ ...

Статья

1012 * MSDN *

0 голосов
/ 24 сентября 2008

Лучше всего это сделать с помощью команды bcp. Если это не доступно, рекомендации по использованию BULK INSERT выше - ваш лучший выбор. Вы совершаете 500 000 обращений в базу данных и записываете 500 000 записей в файлы журнала, не говоря уже о том, какое место нужно выделить для файла журнала, таблицы и индексов.

Если вы вставляете в порядке, отличном от вашего кластерного индекса, вам также нужно учитывать время, необходимое для реорганизации физических данных на диске. Здесь много переменных, которые могут замедлить выполнение вашего запроса, чем вы хотели бы.

~ 10000 транзакций в секунду - не страшно для отдельных вставок, возвращающихся из кода /

0 голосов
/ 24 сентября 2008

У меня была похожая проблема в моем последнем контракте. Вы делаете 500 000 поездок в SQL, чтобы вставить свои данные. Для значительного увеличения производительности вы хотите исследовать метод BulkInsert в пространстве имен SQL. У меня были процессы «перезагрузки», которые длились от 2 с лишним часов, чтобы восстановить пару десятков таблиц, до 31 секунды после того, как я реализовал групповой импорт.

0 голосов
/ 24 сентября 2008

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

  1. Первичный ключ - это GUID, а первичный индекс - CLUSTERED.
  2. На множестве полей есть какой-то УНИКАЛЬНЫЙ индекс.
  3. В таблице слишком много индексов.

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...