Переподключение отброшенного SqlConnection без отката транзакции после изменения IP - PullRequest
4 голосов
/ 12 декабря 2011

У меня есть несколько клиентов, подключающихся к SQL Server по ненадежной (беспроводной / gprs) сети и выполняющих большое количество небольших запросов и операций вставки в течение нескольких минут. Если во время процесса разрывается сетевое соединение, вся транзакция откатывается и требует перезапуска. Из-за требований бизнеса, процесс должен быть транзакционным (то есть другие клиенты видят либо полный набор данных от других клиентов, либо не видят его вообще).

Я хотел бы иметь возможность обнаруживать разрыв соединения и иметь возможность повторно подключиться к SQL Server и продолжить процесс в той же транзакции , которая только что была прервана, и избежать перезапуска с самого начала. В данный момент я использую sp_getbindtoken сразу после открытия соединения, задаю CommandTimeout небольшое значение (намного меньшее, чем TCP KeepAlive), и если я получаю таймаут во время ExecuteNonQuery, я открываю новое соединение с сервером и вызываю sp_bindsession с токеном с самого начала процесса. Затем я продолжаю процесс, используя новое соединение с сеансом, привязанным к транзакции предыдущего процесса.

Пока он работает почти идеально, но, согласно MSDN , этот API устарел и будет удален в будущих версиях SQL Server. Вопрос в том, как мне достичь тех же результатов без этих двух команд? Есть ли другой способ возобновить транзакцию с разорванного TCP-соединения?

Редактировать / дополнительная информация: Клиентское приложение работает на устройствах Windows CE со сканерами штрих-кода. Я предоставляю как устройства, так и программное обеспечение, поэтому я могу разместить там все, что мне нужно. БД размещается в защищенной среде третьей стороной, и ни я, ни клиент не могут ее контролировать. У меня есть около 50 МБ ежедневных данных о продажах для отправки. Я могу использовать SP для сохранения данных, но он все равно должен быть передан, и один вызов SP с одним большим аргументом имеет почти 0% шанс успеха по каналу GPRS / EDGE.

Поскольку все решение работает в производственной среде, я бы хотел, чтобы изменения были минимальными. Альтернативный API с такой же семантикой, как у sp_bindsession, был бы идеальным.

Ответы [ 2 ]

2 голосов
/ 15 декабря 2011

Я просто не покупаю, чтобы ~ 50 МБ ежедневных данных о продажах приходилось на одну транзакцию.Я покупаю, что отдельные транзакции продаж должны быть обернуты в sql-транзакции, но они будут больше похожи на 1K каждая.Вы уверены, что не можете запустить несколько небольших транзакций на сервере в хранимой процедуре?Если это должно быть все или ничего от каждого устройства, то загрузите устройство в промежуточные таблицы с помощью небольших транзакций.Когда устройство выполнено, используйте хранимую процедуру на сервере в транзакциях для очистки промежуточных таблиц.Или просто добавьте логический столбец для завершения загрузки, а затем установите этот флаг в одном обновлении после завершения загрузки.Транзакция 50 МБ действительно забила бы журнал транзакций и заблокировала бы другие обновления.

0 голосов
/ 12 декабря 2011

Ссылка MSDN, которую вы указали, предлагает использовать MARS . По статье:

MARS позволяет использовать соединение как для операций чтения, так и для операций на языке манипулирования данными (DML) с несколькими ожидающими операциями. Эта функция избавляет приложение от необходимости обрабатывать ошибки, связанные с подключением. Кроме того, MARS может заменить пользователя серверных курсоров, которые обычно потребляют больше ресурсов. Наконец, поскольку несколько операций могут работать с одним подключением, они могут использовать один и тот же контекст транзакции, что устраняет необходимость использования системных хранимых процедур sp_getbindtoken и sp_bindsession.

Таким образом, вы можете использовать BeginTrasaction, и транзакция будет автоматически отменена, если вы явно не подтвердите ее. Вы можете отследить сбой оператора commit и рекурсивно попытаться повторить его, пока фиксация транзакции не вернется успешно. Просто идея. Может быть, что-то вроде этого:

private static void ExecuteSqlTransaction(string connectionString)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    {
        connection.Open();

        SqlCommand command = connection.CreateCommand();
        SqlTransaction transaction;

        // Start a local transaction.
        transaction = connection.BeginTransaction("SampleTransaction");

        // Must assign both transaction object and connection
        // to Command object for a pending local transaction
        command.Connection = connection;
        command.Transaction = transaction;

        try
        {
            command.CommandText =
                "Insert into Region (RegionID, RegionDescription) VALUES (100, 'Description')";
            command.ExecuteNonQuery();
            command.CommandText =
                "Insert into Region (RegionID, RegionDescription) VALUES (101, 'Description')";
            command.ExecuteNonQuery();

            // Attempt to commit the transaction.
            transaction.Commit();
            //Both records are written to database.
        }
        catch (Exception ex)
        {
            // Attempt to roll back the transaction.
            try
            {
                transaction.Rollback();
            }
            catch (Exception ex2)
            {
                // This catch block will handle any errors that may have occurred
                // on the server that would cause the rollback to fail, such as
                // a closed connection.
                ExecuteSqlTransaction(connectionString);
            }
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...