атомное сравнение и обмен в базе данных - PullRequest
8 голосов
/ 02 октября 2009

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


begin transaction
select * from table where pk = x and status = y
update table set status = z where pk = x
commit transaction
--(the row would be returned)

для двух или более одновременных запросов должно быть невозможно вернуть строку (при выполнении одного запроса строка будет видна, а ее состояние = y) - что-то вроде блокированной операции CompareAndExchange.

Я знаю, что приведенный выше код работает (для сервера SQL), но всегда ли подкачка будет атомарной?

Мне нужно решение, которое будет работать для SQL Server и Oracle

Ответы [ 4 ]

7 голосов
/ 02 октября 2009

Является ли PK первичным ключом? Тогда это не проблема, если вы уже знаете первичный ключ, что нет спорта. Если pk является первичным ключом, то напрашивается очевидный вопрос как знаете ли вы pk предмета для удаления из очереди ...

Проблема в том, что если вы не знаете первичный ключ и хотите удалить из очереди следующий «доступный» (т. Е. Status = y) и пометить его как удаленный (удалить его или установить status = z ).

Правильный способ сделать это - использовать одно утверждение. К сожалению, синтаксис отличается между Oracle и SQL Server. Синтаксис SQL Server:

update top (1) [<table>]
set status = z 
output DELETED.*
where  status = y;

Я недостаточно знаком с предложением Oracle RETURNING, чтобы привести пример, аналогичный примеру SQL OUTPUT.

Другие решения SQL Server требуют, чтобы подсказки блокировки на SELECT (с UPDLOCK) были правильными. В Oracle предпочтительным способом является использование FOR UPDATE, но в SQL Server это не работает, поскольку FOR UPDATE следует использовать вместе с курсорами в SQL.

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

2 голосов
/ 02 октября 2009

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

Типичный синтаксис для этого выглядит примерно так:

 select * from table where pk = x and status = y for update

но вам нужно было бы посмотреть, чтобы быть уверенным.

1 голос
/ 02 октября 2009

Попробуй это. Проверка в выражении UPDATE.

код

IF EXISTS (SELECT * FROM sys.tables WHERE name = 't1')
    DROP TABLE dbo.t1
GO
CREATE TABLE dbo.t1 (
    ColID       int         IDENTITY,
    [Status]    varchar(20)
)
GO

DECLARE @id             int
DECLARE @initialValue   varchar(20)
DECLARE @newValue       varchar(20)

SET @initialValue = 'Initial Value'

INSERT INTO dbo.t1 (Status) VALUES (@initialValue)
SELECT @id = SCOPE_IDENTITY()

SET @newValue = 'Updated Value'

BEGIN TRAN

UPDATE dbo.t1
SET
    @initialValue = [Status],
    [Status]      = @newValue
WHERE ColID    = @id
  AND [Status] = @initialValue

SELECT ColID, [Status] FROM dbo.t1

COMMIT TRAN

SELECT @initialValue AS '@initialValue', @newValue AS '@newValue'

Результаты

ColID Status
----- -------------
    1 Updated Value

@initialValue @newValue
------------- -------------
Initial Value Updated Value
1 голос
/ 02 октября 2009

У меня есть несколько приложений, которые следуют подобному шаблону. Есть такая таблица, как ваша, которая представляет очередь работы. В таблице есть два дополнительных столбца: thread_id и thread_date. Когда приложение запрашивает работу для очереди, оно отправляет идентификатор потока. Затем один оператор обновления обновляет все применимые строки с помощью столбца идентификатора потока с предоставленным идентификатором и столбца даты потока с текущим временем. После этого обновления он выбирает все строки с этим идентификатором потока. Таким образом, вам не нужно объявлять явную транзакцию. «Блокировка» происходит при первоначальном обновлении.

Столбец thread_date используется, чтобы гарантировать, что вы не получите осиротевшие рабочие элементы. Что произойдет, если элементы будут извлечены из очереди, а затем произойдет сбой вашего приложения? Вы должны иметь возможность попробовать эти рабочие элементы снова. Таким образом, вы можете извлечь все элементы из очереди, которые не были отмечены как завершенные, но были назначены потоку с датой потока в далеком прошлом. Вам решать, чтобы определить "отдаленный".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...