Как отменить длительную операцию базы данных?

В настоящее время работает с Oracle, но также потребуется решение для MS SQL.

У меня есть графический интерфейс, который позволяет пользователям генерировать SQL, который будет выполняться в базе данных. Это может занять очень много времени, в зависимости от поиска, который они генерируют. Я хочу, чтобы GUI/ приложение реагировало во время этого поиска, и я хочу, чтобы пользователь мог отменить поиск.

Я использую фоновый рабочий поток.

Моя проблема в том, что, когда пользователь отменяет поиск, я не могу прервать звонок в базу данных. Он ожидает завершения и может опрашивать свойство "CancelationPending". Это не только тратит ресурсы базы данных, но и создает проблемы для моего кода.

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

Кажется, это действительно уродливый способ делать вещи. База данных продолжает работать, я создаю новые экземпляры фоновых рабочих.... когда я действительно хочу ОСТАНОВИТЬ вызов базы данных и повторно использовать того же самого рабочего.

Как я могу это сделать?

10 ответов

Решение

Я не думаю, что это возможно. Вот ссылка на обсуждение на веб-сайте Oracle по этой теме: http://forums.oracle.com/forums/thread.jspa?threadID=400492&start=15&tstart=0

Если вы используете ADO.NET и поставщик данных SQL, взгляните на метод SqlCommand.Cancel. Это то, что вы ищете. Тем не менее, он пытается отменить, и отмена может занять некоторое время. По сути, именно SQL Server должен решить, когда удовлетворить ваш запрос на отмену. Когда запрос отменен, вы должны получить SqlException, который указывает, что операция была отменена пользователем. По-видимому, вы не хотите рассматривать это исключение как исключение и обрабатывать его специально, например, если SqlException происходит из-за отмены операции пользователем, просто проглотите ее.

Я также заметил, что command.Cancel() на самом деле не прерывает команду. Для меня сработало закрытие соединения (транзакция отката, если вы ее используете), когда пользователь прерывает работу. Это вызовет исключение в фоновом потоке во время выполнения команды, поэтому вам нужно перехватить его и проверить там свойство CancellationPending, а не перебрасывать исключение в этом случае...

// When aborting
worker.CancelAsync();
command.Connection.Close();

// In your DoWork event handler
...
catch (Exception)
{
    if (worker.CancellationPending)
    {
        e.Cancel = true;
        return;
    }
    else
    {
        throw;
    }
}

// And in your RunWorkerCompleted event handler
if (e.Error == null && !e.Cancelled)
{
    ...
}

Я почти уверен, что это возможно- мы используем TOAD для Oracle, и это позволяет отменять долго выполняющиеся запросы, как описано здесь. Я не уверен, как они это делают, хотя.

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

Я думаю, что лучшее решение, кажется, убивает сессии через таблицу мониторинга.

С Oracle вы можете сделать это, как говорит Burnsys

В Firebird 2.5 это будет выглядеть так же

Я надеюсь, что нечто подобное существует в MS SQL

Если вы используете SQLCommand, вы можете попробовать вызвать его метод Cancel.

Что касается открытия нового соединения с базой данных, войдите в систему как sysdba и отправьте команду "ALTER SYSTEM KILL SESSION" sid,serial#' IMMEDIATE ”, указав SID процесса, который вы хотите завершить.

Чтобы получить sessionID: выберите sid из v$mystat, где rownum = 1

Чтобы получить Serial#: выберите sid, serial # из v$session, где sid =: SID

http://www.oracle-base.com/articles/misc/KillingOracleSessions.php

РЕДАКТИРОВАТЬ: идея WW для не Войти в систему как sysdba здесь: http://forums.oracle.com/forums/thread.jspa?threadID=620578

Я пытался отменить и закрыть с ADO 2.8 и SQLOLEDB или SQL Server родной клиент. При отмене набор записей прекращает выборку данных, но в фоновом режиме чтение с сервера продолжается и потребляет память из приложения. В 32-битном приложении может случиться так, что через несколько минут вы получите сообщение "недостаточно памяти". Когда я закрываю набор записей (или соединение, с или без отмены ранее), ADO 2.8 ждет, пока все записи не будут выбраны.

Я не знаю, делает ли ADO.NET это лучше, но я думаю, что это хорошая идея, чтобы отслеживать память и доступ к сети после Cancel/Close, чтобы быть уверенным, что ADO действительно прекращает чтение данных.

KILL SESSION был для меня единственным способом отменить длительный запрос. Я использую управляемый оракул, и OracleCommand.Cancel() работает несколько раз, но обычно он не работает. Кроме того, OracleCommand.CommandTimeout не соблюдается в соответствии с моими тестами. Некоторое время назад, когда я использовал неуправляемый оракул, при условии, что мне удалось отменить команды, но уже не с управляемой. Любой способ убить сессию был единственным вариантом. Запрос выполняется не в потоке пользовательского интерфейса, а в отдельном потоке. Команда отмены отправляется из потока пользовательского интерфейса. Это немного сложнее, потому что приложение использует посредник, используя WCF, но в конце дня я убиваю сессию. Конечно, при выполнении запроса мне нужно найти и сохранить сеанс, чтобы при необходимости убить его. Есть много способов найти sid и serial#, чтобы убить сеанс оракула, и я хочу потратить ваше время на объяснение того, что вы уже знаете.

Oracle представила ALTER SYSTEM CANCEL SQLв 18в. Вам нужно будет добавить какой-то комментарий с UID к вашему SQL, а затем искать его примерно так

SELECT S.SID||','||S.SERIAL#
                    FROM GV$SESSION S, V$SQL Q
                    WHERE S.USERNAME IS NOT NULL
                    AND S.STATUS = 'ACTIVE'
                    AND S.SQL_ID IS NOT NULL
                    AND Q.SQL_ID = S.SQL_ID
                    and sql_text like '%{queryId}%'

А затем запустите другую операцию из.NET ALTER SYSTEM CANCEL SQL 'SID, SERIAL'

Другие вопросы по тегам