В SQL Server, как я могу заблокировать одну строку способом, аналогичным Oracle «SELECT FOR UPDATE WAIT»? - PullRequest
18 голосов
/ 29 февраля 2012

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

В версии Oracle я использую «SELECT FOR UPDATE WAIT» для блокировки определенных нужных мне строк. Я использую его в ситуациях, когда обновление основано на результате SELECT, и другие сеансы могут абсолютно не изменять его одновременно, поэтому они должны сначала заблокировать его вручную. Система сильно подвержена сеансам, пытающимся получить доступ к одним и тем же данным одновременно.

Например:
Два пользователя пытаются извлечь строку в базе данных с наивысшим приоритетом, пометить ее как занятую, выполнить над ней операции и снова пометить как доступную для дальнейшего использования. В Oracle логика будет выглядеть примерно так:

BEGIN TRANSACTION;
SELECT ITEM_ID FROM TABLE_ITEM WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1'
    ITEM_STATUS = 'available' AND ROWNUM = 1 FOR UPDATE WAIT 5;
UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';
COMMIT TRANSACTION;

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

Итак, наконец, возникает вопрос: как мне добиться таких же результатов в SQL Server? Я искал подсказки блокировки, которые, теоретически, кажутся, что они должны работать. Однако единственными блокировками, которые предотвращают другие блокировки, являются «UPDLOCK» И «XLOCK», которые работают только на уровне таблицы.
Те подсказки блокировки, которые работают на уровне строк, являются общими блокировками, которые также не удовлетворяют моим потребностям (оба пользователя могут заблокировать одну и ту же строку одновременно, оба помечают ее как недоступную и выполняют избыточные операции с соответствующим элементом).

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

Ответы [ 4 ]

15 голосов
/ 29 февраля 2012

Вы, вероятно, ищете with (updlock, holdlock).Это сделает select захват exclusive блокировки, который требуется для обновлений, вместо shared блокировки.Подсказка holdlock указывает SQL Server сохранять блокировку до завершения транзакции.

FROM TABLE_ITEM with (updlock, holdlock)
7 голосов
/ 29 февраля 2012

В SQL Server есть подсказки о блокировке, но они не охватывают свои операторы, как в приведенном вами примере Oracle.Способ сделать это в SQL Server - установить уровень изоляции транзакции, содержащей операторы, которые вы хотите выполнить.См. эту страницу MSDN , но общая структура будет выглядеть примерно так:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

BEGIN TRANSACTION;

    select * from ...

    update ...

COMMIT TRANSACTION;

SERIALIZABLE - это самый высокий уровень изоляции.Смотрите ссылку для других вариантов.Из MSDN:

SERIALIZABLE Указывает следующее:

Операторы не могут читать данные, которые были изменены, но еще не зафиксированы другими транзакциями.

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

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

5 голосов
/ 15 декабря 2016

Как Документация Сказано:

XLOCK

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

Итак, решение использует WITH(XLOCK, ROWLOCK):

BEGIN TRANSACTION;

SELECT ITEM_ID
FROM TABLE_ITEM
WITH(XLOCK, ROWLOCK)
WHERE ITEM_PRIORITY > 10 AND ITEM_CATEGORY = 'CT1' AND ITEM_STATUS = 'available' AND ROWNUM = 1;

UPDATE [locked item_id] SET ITEM_STATUS = 'unavailable';

COMMIT TRANSACTION;
1 голос
/ 29 февраля 2012

Вы пробовали С (ROWLOCK) ?

BEGIN TRAN

   UPDATE your_table WITH (ROWLOCK)
   SET your_field = a_value
   WHERE <a predicate>

COMMIT TRAN
...