Эффективное преобразование данных с использованием .Net - PullRequest
0 голосов
/ 02 августа 2009

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

Теперь, скажем, я породил 3 темы этого приложения. Первый поток читает 500 строк и начинает их обрабатывать. Могу ли я заблокировать эти строки, которые уже были прочитаны, чтобы следующий поток не забрал их? Я пытаюсь найти некоторые статьи по этому поводу в Интернете, но, возможно, я не ищу правильные термины в Google.

Есть идеи? или ссылки на статьи, которые могут быть полезны?

Ответы [ 6 ]

3 голосов
/ 02 августа 2009

Вам действительно нужно приложение для этого? Наиболее эффективным способом будет просто выполнить оператор SQL на сервере, который передает данные между таблицами.

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

Пример: входная таблица A с одним столбцом типа float и выходная таблица B с одним столбцом типа float. Мы извлечем все числа из таблицы A и вставим квадратный корень каждого неотрицательного числа в таблицу B.

class SqrtingDataDecorator : IDataReader
{
    private readonly IDataReader _decorated;
    private double _input;

    public SqrtingDataDecorator(IDataReader decorated)
    {
         _decorated = decorated;
    }
    public bool Read()
    {
        while (_decorated.Read())
        {
            _input = _decorated.GetDouble(0);
            if (_input >= 0)
                return true;
        }
        return false;
    }
    public object GetValue(int index)
    {
        return Math.Sqrt(_input);
    }
    public int FieldCount { get { return 1; } }
    //other IDataReader members just throw NotSupportedExceptions,
    //return null or do nothing. Omitted for clarity.
}

Вот бит, который делает работу

//get the input datareader
IDataReader dr = ///.ExecuteDataReader("select floatCol from A", or whatever
using (SqlTransaction tx = _connection.BeginTransaction())
{
    try
    {
        using (SqlBulkCopy sqlBulkCopy =
            new SqlBulkCopy(_connection, SqlBulkCopyOptions.Default, tx))
            {
                sqlBulkCopy.DestinationTableName = "B";
                SetColumnMappings(sqlBulkCopy.ColumnMappings);
                //above method omitted for clarity, easy to figure out

                //now wrap the input datareader in the decorator
                var sqrter = new SqrtingDataDecorator(dr);
                //the following line does the data transfer.
                sqlBulkCopy.WriteToServer(sqrter);
                tx.Commit();
            }
    }
    catch
    {
        tx.Rollback();
        throw;
    }
}
1 голос
/ 02 августа 2009

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

0 голосов
/ 02 августа 2009

Что заставляет вас думать, что многопоточность сделает это быстрее? Узким местом, вероятно, является диск вашего Sql Sever; и многопоточность сделает пропускную способность диска ниже, а не выше. Sql Sever должен будет смешивать запросы от 3 потоков на диск.

Если вам нужно сделать многопоточность, вы можете разделить работу по идентификатору строки. Например, первый поток выполняет строки 1-333, 1000-1333, 2000-2333 и т. Д.

0 голосов
/ 02 августа 2009

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

0 голосов
/ 02 августа 2009

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

что-то вроде:

public void InsertNextRecord(){
  while(true){
  int recordID = this.PopRecordID();
  if(recordID == -1)
     return;//exit thread
   ///do whatever it is that you need to do to select the record and re-insert it.

  }
}
public int PopRecordID(){
  lock(this._queue){
    if(this._queue.Count == 0)
      return -1;
     return this._queue.Dequeue();
  }
}

Итак, создайте сколько угодно потоков и попросите их выполнить метод InsertNextRecord () до завершения.

0 голосов
/ 02 августа 2009

Если вам нужно вставить тысячи записей с SQL Server, взгляните на массовые вставки , ваш запрос на выборку не должен вызывать особых проблем. Но все это может быть излишним, если это однократная операция, копирование 500 000 записей не займет много времени.

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