Як я обмежую збережену процедуру SQL, яку повинен запускати одна людина одночасно?


12

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

Поки ця процедура працює, я не хочу, щоб хто-небудь інший міг її запустити, проте я не хочу серіалізацію, інша людина запускає процедуру після того, як я закінчую її.

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

Я намагався використовувати sp_getapplock, проте не можу повністю зупинити людину від запуску процедури.

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

Який найкращий спосіб для мене це зробити?


3
Чи можете ви зробити крок назад і надати додаткову інформацію про те, що робить процедура, і чому ви хочете уникати кількох людей одночасно? Може бути техніка кодування, яка виключає цю вимогу, або якась черга, яку ви могли б застосувати для обробки речей.
AMtwo

Відповіді:


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

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 хвилин у своєму прикладі. Ви можете зафіксувати свою логіку роботи в блоці спробу / лову та розблокувати улов, якщо хочете також. Я використовую COALESCE за звичкою, але жоден @@ ROWCOUNT не може бути NULL. провідна напівкрапка походить від роботи з проектами баз даних Visual Studio, вона скаржиться, якщо її немає. ніякої шкоди ні в якому разі.
Джонатан Фійт

-1

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

Для захисту від різного роду невідповідностей баз даних стандарт SQL має чотири рівні ізоляції транзакцій:

  • ЧИТАЙТЕ НЕЗАПАСНО, коли в основному транзакції втрачають свою цінність, а інші транзакції бачать брудні дані. Не використовуйте це!
  • ЧИТАЙТЕ ЗВ'ЯЗАНО, якщо транзакції бачать лише скоєні дані, але можуть виникнути невідповідності, коли дві транзакції можуть переходити на пальці один одного
  • ПОВТОРЕННЕ ЧИТАТИ, коли вирішується один вид непослідовності, не повторюваного читання
  • СЕРІАЛІЗАЛЬНІ, що гарантує існування певного віртуального порядку, в якому здійснення транзакцій призведе до результатів, до яких призвело їх виконання

Однак стандарт SQL має підхід, заснований на блокуванні, для цих невідповідностей баз даних, і з міркувань продуктивності багато баз даних використовують підхід, заснований на знімку знімків, який має такі рівні:

  • ЧИТАЙТЕ ЗВ'ЯЗАНО, що саме в блокуванні баз даних
  • ІЗОЛЯЦІЯ СНАПШОТУ, де база даних бачить знімок усіх даних, і якщо вона намагається оновити рядок, який був оновлений якоюсь іншою транзакцією, її скасовують, але все ж є деякі відомі аномалії, які можуть мати місце
  • СЕРІАЛІЗАЦІЙНА, яка є такою ж, як і в блокуванні баз даних, але цього разу реалізується по-іншому, не беручи блокування, а забезпечуючи відсутність порушень серіалізації, і якщо таке порушення виявлено, скасовуючи транзакцію

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

Ви хочете - рівень ІРІАЛІЗАЦІЙНОГО ізоляції: це забезпечує, якщо транзакції, виконані незалежно одна за одною, призводять до хорошого стану, будь-яке паралельне виконання транзакцій також призводить до хорошого стану. На щастя, Майкл Кейхіл у своїй докторській дисертації з’ясував, як рівень ізоляції СЕРІАЛІЗАЦІЙНО може бути підтриманий за допомогою знімків ізольованих баз даних з невеликими зусиллями.

Якщо використовувати рівень ізоляції СЕРІАЛІЗАЦІЙНОГО МОЖЛИВОСТІ у базі даних, що базується на знімку, якщо двоє людей одночасно намагаються запустити збережену процедуру, і вони будуть наступати на пальці ніг, одна з транзакцій буде скасована.

Тепер, чи справді SQL Server підтримує рівень ізоляції СЕРІАЛІЗАЦІЙНОГО (замість маскування ізоляції знімків за ключовим словом СЕРІАЛІЗАБІЛЬНІ )? Відверто кажучи, я не знаю: єдина база даних, яку я знаю, що підтримує її - це PostgreSQL.

Незважаючи на те, що мені не вдалося дати конкретних порад SQL Server, я все-таки публікую цю відповідь, оскільки користувачі PostgreSQL та користувачі інших баз даних, які можуть розглянути можливість переходу на PostgreSQL, можуть отримати користь від моєї відповіді. Також користувачі баз даних не PostgreSQL, які не можуть перейти на PostgreSQL, можуть тиснути на свого улюбленого постачальника баз даних, щоб запропонувати справжній рівень ІРІАЛІЗАЦІЙНОЇ ізоляції.


Я вважаю, що це означає, що хтось досліджував, чи має рівень SQL Server рівень ізоляції СЕРІАЛІЗАЦІЙНО, і з'ясував, що він не працює.
juhist

-2

Я усвідомлюю, що "справжня" проблема може бути складнішою.

У випадку, якщо це не так: якщо ви здійснюєте архівування за допомогою вставки та / або тригерів оновлення, ви можете уникнути проблеми, яку ви намагаєтеся вирішити.

Сподіваюся, що це допомагає, -
Кріс С.


1
Що ви маєте на увазі під «негайно»? Відразу після чого? Після вставки? Отже, як тільки приходить новий рядок, він негайно відправляється в архів? Або ви мали на увазі після оновлення? Тож будь-яка зміна даних запускає архівацію? Можливо, ви повинні бути більш конкретними щодо того, який сценарій ви маєте на увазі, пропонуючи це.
Андрій М

Архів може бути занадто дорогим та / або занадто рідко бажаним, щоб його варто було робити на кожному вкладиші, особливо якщо таблиця джерел часто вставляється та / або безпека транзакцій між нею та архівом потребує дорогих блокувань.
підкреслюй_d

@underscore_d Так, це може бути занадто дорогим або не завжди потрібним. Ось чому я почав свою відповідь із твердження, що the 'real' problem may be more complex. У випадку, якщо це не так, тригери є хорошим рішенням. Плюс це, можливо, буде простіше протестувати та підтримувати, оскільки це особливість бази даних замість спеціального рішення.
Дж. Кріс Комптон

@AndriyM Я відразу видалив слово, замінивши посилання для вставки / оновлення тригерів. Вибачте за непорозуміння.
Дж. Кріс Комптон

1
Я перечитав питання і, думаю, я можу побачити джерело моєї плутанини. Те, що ви тут пропонуєте, більше схоже на аудит, ніж на архівування. Як я розумію, архівування даних передбачає переміщення даних (наприклад, з однієї таблиці в іншу). Однак, хоча ОП узагальнила функцію їх процедури як "свого роду архівування", вони ніколи не заявляли, що дані будуть видалені з джерела, лише що вони будуть відібрані з нього та вставлені в ціль. Тож я здогадуюсь, що ви припускали, що ОП потребує копіювання , а не переміщення , своїх даних, і в цьому випадку використання тригерів, мабуть, має сенс.
Андрій М
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.