Как ограничить хранимую процедуру SQL для запуска одним человеком одновременно?

12

У меня есть хранимая процедура, которая в основном выбирает значения из одной таблицы и вставляет их в другую, что-то вроде архивирования. Я хочу, чтобы несколько человек не делали это одновременно.

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

Я хочу, чтобы другой человек, пытающийся запустить его, получил ошибку, пока я выполняю процедуру.

Я пытался использовать sp_getapplock, однако мне не удается полностью остановить человека от выполнения процедуры.

Я также попытался найти процедуру с sys.dm_exec_requests и заблокировать процедуру, хотя это работает, я думаю, что это не оптимально, потому что на некоторых серверах у меня нет прав на запуск sys.dm_exec_sql_text (sql_handle).

Какой лучший способ для меня это сделать?

twoheadedmona
источник
3
Можете ли вы сделать один шаг назад и предоставить больше информации о том, что делает процедура, и почему вы хотите избежать одновременного запуска нескольких людей? Там может быть метод кодирования, который устраняет это требование, или какой-то вид очередей, которые вы могли бы реализовать для обработки вещей.
2

Ответы:

15

Чтобы добавить ответ @ Tibor-Karaszi, установка таймаута блокировки на самом деле не приводит к ошибке (я отправил PR против документов). sp_getapplock просто возвращает -1, поэтому вы должны проверить возвращаемое значение. Ну вот так:

create or alter procedure there_can_be_only_one 
as
begin
begin transaction

  declare @rv int
  exec @rv = sp_getapplock 'only_one','exclusive','Transaction',0
  if @rv < 0
   begin
      throw 50001, 'There is already an instance of this procedure running.', 10
   end

  --do stuff
  waitfor delay '00:00:20'


commit transaction
end
Дэвид Браун - Microsoft
источник
8

Используйте sp_getapplock в начале процедуры и установите для тайм-аута блокировки очень низкое значение. Таким образом, вы получаете ошибку, когда вы заблокированы.

Тибор Караси
источник
7

Другой вариант - создать таблицу для контроля доступа к процедуре. В приведенном ниже примере показана возможная таблица, а также процедура, которая может ее использовать.

CREATE TABLE dbo.ProcedureLock
    (
    ProcedureLockID INT NOT NULL IDENTITY(1,1)
    , ProcedureName SYSNAME NOT NULL
    , IsLocked BIT NOT NULL CONSTRAINT DF_ProcedureLock_IsLocked DEFAULT (0)
    , UserSPID INT NULL
    , DateLockTaken DATETIME2(7) NULL
    , DateLockExpires DATETIME2(7) NULL
    , CONSTRAINT PK_ProcedureLock PRIMARY KEY CLUSTERED (ProcedureLockID)
    )

CREATE UNIQUE NONCLUSTERED INDEX IDXUQ_ProcedureLock_ProcedureName
    ON dbo.ProcedureLock (ProcedureName)

INSERT INTO dbo.ProcedureLock
    (ProcedureName, IsLocked)
VALUES ('dbo.DoSomeWork', 0)

GO

CREATE PROCEDURE dbo.DoSomeWork
AS
BEGIN

    /** Take Lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 1
        , UserSPID = @@SPID
        , DateLockTaken = SYSDATETIME()
        , DateLockExpires = DATEADD(MINUTE, 10, SYSDATETIME())
    WHERE ProcedureName = 'dbo.DoSomeWork'
        AND (IsLocked = 0
            OR (IsLocked = 1 AND DateLockExpires < SYSDATETIME())
            )

    IF COALESCE(@@ROWCOUNT, 0) = 0
    BEGIN
        ;THROW 50000, 'This procedure can only be run one at a time, please wait', 1;
    END

    /** DO WHATEVER NEEDS TO BE DONE */

    /** Release the lock */
    UPDATE dbo.ProcedureLock
    SET IsLocked = 0
        , UserSPID = NULL
        , DateLockTaken = NULL
        , DateLockExpires = NULL
    WHERE ProcedureName = 'dbo.DoSomeWork'

END
Джонатан Файт
источник
1
Это очень похоже на (или, может быть, то же самое, что) то, о чем я сразу подумал после прочтения вопроса, но у меня возникла проблема с этой идеей, которая я не был полностью уверен, как обратиться, и не вижу, что она адресована в вашем ответе или. Меня беспокоит, что если что-то случится во время части «делай, что нужно»? Как бы вы сбросили IsLockedсостояние до 0 в этом случае? Мне также любопытно ваше использование COALESCEздесь. Может @@ROWCOUNTбыть нуль после утверждения нравится UPDATE? И, наконец, просто незначительный прыщ, зачем ставить точку с запятой перед THROWутверждением в этом конкретном случае?
Андрей М
Истечение срока блокировки является одним из способов справиться с этим. Это должно быть установлено в разумные сроки, я установил его на 10 минут в моем примере. Вы можете инкапсулировать свою логику работы в блоке try / catch и разблокировать в catch, если хотите. Я использую COALESCE по привычке, но никакая @@ ROWCOUNT не может быть NULL. ведущая точка с запятой происходит от работы с проектами баз данных Visual Studio, он жалуется, если его там нет. в любом случае никакого вреда.
Джонатан Файт
-1

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

Для защиты от различных видов несоответствий базы данных стандарт SQL имеет четыре уровня изоляции транзакций:

  • READ UNCOMMITTED, где в основном транзакции теряют свою ценность, другие транзакции видят грязные данные. Не используйте это!
  • READ COMMITTED, где транзакции видят только зафиксированные данные, но могут быть несоответствия, когда две транзакции могут переступить через пальцы друг друга
  • REPEATABLE READ, где решается один тип непоследовательности, неповторяемое чтение
  • SERIALIZABLE, который гарантирует, что существует некоторый виртуальный порядок, в котором выполнение транзакций приведет к результатам, которые привели к их выполнению

Тем не менее, в стандарте SQL предусмотрен подход на основе блокировок для этих несоответствий базы данных, и по причинам производительности многие базы данных используют подход на основе моментальной копии, который в основном имеет эти уровни:

  • READ COMMITTED - то же самое, что и в базах на основе блокировок
  • ИЗОЛЯЦИЯ SNAPSHOT, где база данных видит моментальный снимок всех данных, и если она пытается обновить строку, которая была обновлена ​​какой-либо другой транзакцией, она отменяется, однако существуют некоторые хорошо известные аномалии, которые могут иметь место
  • SERIALIZABLE, то же самое, что и в базах данных на основе блокировок, но на этот раз реализовано по-другому, не путем взятия блокировок, а путем обеспечения отсутствия нарушений сериализации, и, если такое нарушение обнаружено, отмена транзакции

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

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

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

Теперь, действительно ли SQL Server поддерживает уровень изоляции SERIALIZABLE (вместо маскировки изоляции моментального снимка за ключевым словом SERIALIZABLE )? Честно говоря, я не знаю: единственная база данных, которую я знаю, которая поддерживает это PostgreSQL.

Несмотря на то, что мне не удалось дать конкретный совет по SQL Server, я все равно публикую этот ответ, так как пользователи PostgreSQL и пользователи других баз данных, которые могут рассмотреть возможность перехода на PostgreSQL, могут извлечь пользу из моего ответа. Кроме того, пользователи баз данных, не относящихся к PostgreSQL, которые не могут переключиться на PostgreSQL, могут оказать давление на своих любимых поставщиков баз данных, чтобы они предложили подлинный СЕРИАЛИЗИРУЕМЫЙ уровень изоляции.

juhist
источник
Я полагаю, это означает, что кто-то исследовал, имеет ли SQL Server уровень изоляции SERIALIZABLE, и обнаружил, что это не так.
юхист
-2

Я понимаю, что «настоящая» проблема может быть более сложной.

Если это не так: если вы выполняете архивирование с помощью триггеров вставки и / или обновления, вы можете избежать проблемы, которую пытаетесь решить.

Надеюсь, это поможет,
Крис С.

Дж. Крис Комптон
источник
1
Что вы подразумеваете под "немедленно"? Сразу после чего? После вставки? Итак, как только появляется новая строка, она сразу отправляется в архив? Или ты имел ввиду после обновления? Таким образом, любое изменение данных вызывает архивирование? Возможно, вы должны быть более конкретными о том, какой сценарий вы предполагаете, предлагая это.
Андрей М
Архивирование может быть слишком дорогим и / или слишком редко желательным, чтобы его стоило делать при каждой вставке, особенно если исходная таблица часто вставляется, и / или безопасность транзакций между ней и архивом требует дорогих блокировок.
underscore_d
@underscore_d Да, это может быть слишком дорого или не всегда требуется. Вот почему я начал свой ответ с заявления, что the 'real' problem may be more complex. В случае, если это не так, триггеры являются хорошим решением. Плюс, вероятно, будет проще протестировать и поддерживать, потому что это особенность базы данных, а не пользовательское решение.
Дж. Крис Комптон
@AndriyM Я немедленно удалил слово, заменив на ссылку триггеры вставки / обновления. Извините за путаницу.
Дж. Крис Комптон
1
Я перечитал вопрос и думаю, что вижу источник своего замешательства. То, что вы предлагаете здесь, больше похоже на одитинг, чем на архивирование. Как я понимаю, архивирование данных подразумевает перемещение данных (например, из одной таблицы в другую). Тем не менее, даже несмотря на то, что ОП суммировали функцию своей процедуры как «своего рода архивирование», они никогда не говорили, что данные будут удалены из источника, только что они будут отобраны из него и вставлены в цель. Поэтому я предполагаю, что вы предположили, что OP должен копировать , а не перемещать свои данные, и в этом случае использование триггеров, вероятно, имеет смысл.
Андрей М