Асинхронный считыватель ADO.NET(обработка очереди)

66
6

У меня есть большая таблица, записи 1B +, ​​которые мне нужно снять и запустить алгоритм на каждой записи. Как я могу использовать ADO.NET для асинхронного выполнения "select * from table" и начать чтение строк один за другим, пока ado.net получает данные?


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


Мои источники данных - оракул и mssql. Я должен сделать это для нескольких источников данных.

спросил(а) 2009-09-04T18:35:00+04:00 10 лет, 10 месяцев назад
1
Решение
117

Для этого вам следует использовать SSIS.


Вам нужно немного узнать о том, как работают поставщики данных ADO.Net, чтобы понять, что вы можете делать, и что вы не можете сделать. Возьмем, например, поставщика SqlClient. Верно, что асинхронно выполнять запросы с BeginExecuteReader, но это асинхронное выполнение выполняется до тех пор, пока запрос не начнет возвращать результаты. На уровне проводки текст SQL отправляется на сервер, сервер начинает опрокидывать выполнение запроса и в конечном итоге начнет толкать строки результатов обратно клиенту. Как только первый пакет возвращается клиенту, выполняется асинхронное выполнение и выполняется обратный вызов завершения. После этого клиент использует метод SqlDataReader.Read () для продвижения набора результатов. В SqlDataReader нет асинхронных методов. Эта модель работает с запросами на сложные запросы, которые возвращают мало результатов после того, как будет выполнена серьезная обработка. Пока сервер занят производством результата, клиент простаивает без блокировки потоков. Однако для простых запросов, которые производят большие результирующие наборы (как вам кажется), все совершенно по-другому: сервер будет немедленно производить обновления и будет продолжать возвращать их клиенту. Асинхронный обратный вызов будет почти мгновенным, и основная часть времени будет потрачена клиентом, итератором которого является SqlDataReader.


Вы говорите, что думаете о размещении записей в очереди памяти в первую очередь. Какова цель очереди? Если обработка алгоритма медленнее, чем пропускная способность результирующего набора результатов DataReader, то эта очередь начнет нарастать. Он будет потреблять живую память и, в конечном итоге, будет исчерпывать память на клиенте. Чтобы этого не произошло, вам нужно будет построить механизм управления потоком, т.е. если размер очереди больше N, не помещайте в него больше записей. Но для этого вам придется приостановить итерацию чтения данных, и если вы сделаете это, вы нажмете управление потоком на сервер, который приостановит запрос до тех пор, пока канал связи не будет доступен снова (пока вы не начнете читать с читателя). В конечном счете, управление потоком необходимо прокладывать до сервера, что всегда имеет место в любом соотношении производителей и потребителей, продюсер должен остановиться, иначе промежуточные очереди заполняются. Ваша очередь в очереди не имеет никакой цели, кроме усложнения. Вы можете просто обрабатывать элементы из считывателя по одному, и если скорость обработки слишком медленная, считыватель данных будет применять управление потоком в запросе, запущенном на сервере. Это происходит автоматически, потому что вы не вызываете метод DataReader.Read.


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

Теперь трудная часть.


Является ли ваша обработка какой-либо обновлением в базе данных? Если да, то у вас есть гораздо большие проблемы:


    Вы не можете использовать одно и то же соединение для записи результата, потому что он занят устройством чтения данных. SqlClient для SQL Server поддерживает MARS, но это решает проблему только с SQL 2005/2008.
    Если вы собираетесь регистрировать чтение и обновление в транзакции, если ваши обновления происходят в другом соединении (см. выше), то это означает использование распределенных транзакций (даже если две связанные конфликты указывают на тот же сервер), Распределенные транзакции медленны.
    Вам нужно разделить обработку на несколько партий, потому что очень плохо обрабатывать записи 1B + в одной транзакции. Это также означает, что вы должны будете иметь возможность возобновить обработку прерывистой партии, что означает, что вы должны иметь возможность идентифицировать записи, которые уже были обработаны (если обработка не является идемпотентной).

ответил(а) 2009-09-04T19:53:00+04:00 10 лет, 10 месяцев назад
55

Комбинация DataReader и блок итератора (aka generator) должен хорошо подходить для этой проблемы. По умолчанию DataReaders, предоставленные Microsoft, извлекают данные одна запись за раз из источника данных.


Вот пример в С#:

static IEnumerable<User> RetrieveUsers(DbDataReader reader)
{
while (reader.NextResult())
{
User user = new User
{
Name = reader.GetString(0),
Surname = reader.GetString(1)
};
yield return user;
}
}

ответил(а) 2009-09-04T18:39:00+04:00 10 лет, 10 месяцев назад
39

Просто используйте DbDataReader (точно так же, как сказал damnoob). Это простой способ прокрутки полученных данных. Вам не нужно удалять данные, потому что DbDataReader доступен только вперед.


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


Это несколько сложнее:


Oracle (и, вероятно, MySQL) будет извлекать несколько 100 строк за раз, чтобы уменьшить количество обратных рейсов в базу данных. Вы можете настроить размер выборки datareader. В большинстве случаев неважно, вы получаете 100 строк или 1000 строк за поездку в оба конца. Однако очень низкое значение, такое как 1 или 2 строки, замедляет работу, потому что при низком значении, которое требуется для получения данных, требуется много раундов.


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


edit1: См. здесь пример Oracle: http://www.oracle.com/technology/oramag/oracle/06-jul/o46odp.html

ответил(а) 2009-09-05T10:16:00+04:00 10 лет, 10 месяцев назад
39

Хорошим подходом к этому было бы отбросить данные в блоках, повторить их добавление в очередь, а затем снова вызвать. Это будет лучше, чем ударить БД для каждой строки. Если вы потянете их обратно с помощью числового ПК, это будет легко, если вам нужно заказать что-то, что вы можете использовать ROW_NUMBER(), чтобы сделать это.

ответил(а) 2009-09-04T18:54:00+04:00 10 лет, 10 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема