В sybase как заблокировать хранимую процедуру, которая выполняет, и изменить таблицу, которую возвращает хранимая процедура? - PullRequest
0 голосов
/ 28 июня 2019

У меня есть следующая таблица:

id    status
--    ------
1     pass
1     fail
1     pass
1     na
1     na

Также у меня есть хранимая процедура, которая возвращает таблицу с топ-100 записями, имеющими статус 'na'.Хранимая процедура может вызываться несколькими узлами в среде, и я не хочу, чтобы они извлекали дублирующиеся данные.Итак, я хочу заблокировать хранимую процедуру во время ее выполнения и установить состояние записей, полученных из хранимой процедуры, равным «В процессе» и вернуть эту таблицу, а затем снять блокировку, чтобы разные узлы не извлекали одно и то жеданные.Как мне это сделать?

Уже есть решение для аналогичного вопроса в MS SQL, но оно показывает ошибки при использовании в Sybase.

Ответы [ 3 ]

1 голос
/ 28 июня 2019
  1. Создайте еще одну таблицу proc_lock с одной строкой
  2. Когда элемент управления войдет в хранимую процедуру, запустите транзакцию и выполните выбор для обновления строки в proc_lock (см. this ссылка).Если это не работает для Sybase, вы можете попробовать метод из этого ответа , чтобы заблокировать строку.
  3. Перед завершением процедуры обязательно совершите транзакцию.

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

1 голос
/ 29 июня 2019

Предполагается, что Sybase ASE ...

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

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

Я предполагаю, что вы a) не хотите блокировать всю таблицу и b) вы можете разрешить нескольким процессам одновременно извлекать строки из таблицы.

1 - если возможно, убедитесь, чтотаблица использует блокировку datarows (по умолчанию обычно allpages );это уменьшит степень детализации блокировок до уровня строки (в отличие от уровня страницы для allpages );таблица должна иметь значение datarows , если вы хотите разрешить нескольким процессам одновременно находить / обновлять строки в таблице

2 - убедитесь, что параметр повышения блокировки в таблице достаточно высок, чтобыубедитесь, что обновление 100 строк одного процесса не блокирует таблицу (sp_setpglockpromote для всех страниц , sp_setrowlockpromote для datarows );ключевой момент здесь - убедиться, что ваш update не переходит в блокировку на уровне таблицы!

3 - когда придет время захватить ваш набор из 100 строк, который вы захотите ... внутритранзакция ... update 100 строк со значением status, которое является уникальным для вашего сеанса, выберите соответствующие id, а затем снова обновите status до «В процессе»

Суть операции выглядит следующим образом:

declare @mysession varchar(10)

select  @mysession = convert(varchar(10),@@spid)  -- replace @@spid with anything that
                                                  -- uniquely identifies your session
set rowcount 100  -- limit the update to 100 rows

begin tran get_my_rows

    -- start with an update so that get exclusive access to the desired rows;
    -- update the first 100 rows you find with your @@spid

    update mytable
    set    status = @mysession   -- need to distinguish your locked rows from
                                 -- other processes; if we used 'In Progress'
                                 -- we wouldn't be able to distinguish between
                                 -- rows update earlier in the day or updated
                                 -- by other/concurrent processes

    from   mytable readpast      -- 'readpast' allows your query to skip over
                                 -- locks held by other processes but it only
                                 -- works for datarows tables
    where  status = 'na'

    -- select your reserved id's and send back to the client/calling process

    select  id
    from    mytable
    where   status = @mysession

    -- update your rows with a status of 'In Progress'

    update mytable
    set    status = 'In Progress'
    where  status = @mysession

commit            -- close out txn and release our locks

set rowcount 0    -- set back to default of 'unlimited' rows

Потенциальные проблемы:

  • , если ваша таблица велика и у вас нет индекса на status тогда выполнение ваших запросов может занять больше времени, чем необходимо;убедившись, что эскалация блокировки достаточно высока, и вы используете блокировку datarows (так что readpast работает), вы должны увидеть минимальное блокирование других процессов независимо от того, сколько времени потребуется, чтобы найти нужные строки

  • с индексом в столбце status, учтите, что все эти update s будут вызывать много обновлений индекса, что, вероятно, приведет к дорогостоящему отложенные обновления

  • при использовании datarows и если ваша эскалация блокировки слишком мала, тогда обновление может просмотреть всю таблицу, что приведет к другому (параллельному) процессуreadpast заблокировать таблицу и не найти строк для обработки

  • при использовании все страницы вы не сможете использовать readpast, поэтому параллельные процессы будут блокироватьсяна ваших блокировках (то есть они не смогут читать вокруг вашей блокировки)

  • , если у вас есть индекс на status, и несколько параллельных процессов блокируют разные строки встол, там может бытьвероятность возникновения взаимоблокировок (вероятно, в дереве индексов индекса в столбце status), что, в свою очередь, потребует кодирования вашего клиента / приложения для ожидания и устранения взаимоблокировок

Подумайте:

  • , если таблица относительно мала, так что сканирование таблицы не требует больших затрат, вы можете отбросить любой индекс в столбце status, и это должно уменьшитьнакладные расходы производительности отложенные обновления (связанные с обновлением индексов)

  • , если вы можете работать с определенным для сеанса значением status (например, 'In Progress - @mysession '), тогда вы можете исключить 2-й оператор update (может пригодиться, если вы выполняете отложенные обновления для индексированного столбца status)

  • если у вас есть другой столбец (столбцы) в таблице, который вы можете использовать для уникальной идентификации строк вашего сеанса (например, last_updated_by_spid = @@ spid, last_updated_date = @mydate - где @mydate изначально установите getdate()), тогда ваш первый update сможет установить статус = 'В процессе', select будет использовать @@ spid и @mydate для предложения where, а второй update не будет необходимо [ПРИМЕЧАНИЕ: фактически это то же самое, что Гордон пытается решить с помощью своей колонки session.]

  • при условии, что вы можете работать с определенным для сеанса значением status, рассмотрите возможность использования чего-либо, что позволит вам отслеживать и исправлять потерянные строки (например, строка status остается «In Progress - @mysession», потому что вызывающий процесс прервался и больше не возвращался (переустанавливал) статус

  • если вы можете передать список id обратно вызывающей программе в виде единой строки объединенных id значений, вы можете использовать описанный мною метод в этом ответе , чтобы добавить id в @variable во время первого обновления, что позволяет вам установить status = 'В процессе' в первом обновлении, а также позволяет исключить select и второе update

  • как бы вы сказали, какие строки были осиротевшими? Вы можете захотеть обновить (маленький) столбец даты и времени с getdate(), когда вы выдавали update; затем, если вы, как правило, ожидаете, что status будет обновлено, скажем, за 5 минут, у вас может быть процесс мониторинга, который ищет потерянные строки, где status = 'В процессе' и его было больше, скажем, 10 минут с момента последнего update

Если datarows , readpast, настройки эскалации блокировок и / или потенциал взаимоблокировки слишком велики, и вы можете жить с краткими блокировками на уровне таблицы, вы можете получить процесс, чтобы получить эксклюзивная блокировка на уровне таблицы перед выполнением операторов update и select; эксклюзивная блокировка должна быть получена в рамках пользовательской транзакции, чтобы «удерживать» блокировку на время вашей работы; быстрый пример:

begin tran get_my_rows

    -- request an exclusive table lock; wait until it's granted

    lock table mytable in exclusive mode

    update ...

    select ...

    update ...

commit
1 голос
/ 28 июня 2019

Я не уверен на 100%, как это сделать в Sybase.Но идея заключается в следующем.

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

Затем обновить строк:

update top (100) t
    set status = 'in progress',
        session = @session
    where status = 'na'
    order by ?;  -- however you define the "top" records

Затем вы можете вернуть или обработать 100 идентификаторов, которые "вПрогресс "для данного соединения.

...